webxos commited on
Commit
847343d
·
verified ·
1 Parent(s): 1df6124

Delete shadowclaw.c

Browse files
Files changed (1) hide show
  1. shadowclaw.c +0 -653
shadowclaw.c DELETED
@@ -1,653 +0,0 @@
1
- #define _GNU_SOURCE
2
- #include <stdio.h>
3
- #include <stdlib.h>
4
- #include <stdint.h>
5
- #include <string.h>
6
- #include <stdbool.h>
7
- #include <unistd.h>
8
- #include <time.h>
9
- #include <curl/curl.h>
10
- #include <dirent.h>
11
- #include "cJSON.h"
12
-
13
- // --------------------------------------------------------------------
14
- // Shadow Header + Arena (Tsoding/stb_ds style)
15
- // --------------------------------------------------------------------
16
- typedef struct {
17
- size_t capacity; // bytes available AFTER header
18
- size_t length; // bytes used AFTER header
19
- uint64_t tag; // magic: 0x534841444F57434C = "SHADOWCL"
20
- uint32_t version;
21
- uint32_t flags; // bit 0 = dirty
22
- } ShadowHeader;
23
-
24
- #define SHADOW_MAGIC 0x534841444F57434CULL
25
- #define SHADOW_SIZE (sizeof(ShadowHeader))
26
- #define ALIGN_UP(x, a) (((x) + (a)-1) & ~((a)-1))
27
-
28
- static inline ShadowHeader* shadow_header(void *data) {
29
- return (ShadowHeader*)((char*)data - SHADOW_SIZE);
30
- }
31
-
32
- typedef struct {
33
- void *data; // user‑facing payload pointer
34
- size_t reserved; // total malloc size (header + capacity)
35
- } ShadowArena;
36
-
37
- ShadowArena shadow_arena_create(size_t initial_capacity) {
38
- ShadowArena a = {0};
39
- size_t total = SHADOW_SIZE + initial_capacity;
40
- total = ALIGN_UP(total, 64);
41
-
42
- ShadowHeader *h = malloc(total);
43
- if (!h) abort();
44
-
45
- *h = (ShadowHeader){
46
- .capacity = initial_capacity,
47
- .length = 0,
48
- .tag = SHADOW_MAGIC,
49
- .version = 1,
50
- .flags = 0
51
- };
52
- a.data = (char*)h + SHADOW_SIZE;
53
- a.reserved = total;
54
- return a;
55
- }
56
-
57
- void shadow_arena_destroy(ShadowArena *a) {
58
- if (a->data) {
59
- free(shadow_header(a->data));
60
- a->data = NULL;
61
- }
62
- }
63
-
64
- void* shadow_arena_push(ShadowArena *a, const void *src, size_t bytes) {
65
- ShadowHeader *h = shadow_header(a->data);
66
-
67
- if (h->length + bytes > h->capacity) {
68
- size_t new_cap = h->capacity ? h->capacity * 2 : 4096;
69
- while (new_cap < h->length + bytes) new_cap *= 2;
70
- size_t new_total = SHADOW_SIZE + new_cap;
71
- new_total = ALIGN_UP(new_total, 64);
72
-
73
- ShadowHeader *new_h = realloc(h, new_total);
74
- if (!new_h) abort();
75
-
76
- new_h->capacity = new_cap;
77
- a->data = (char*)new_h + SHADOW_SIZE;
78
- a->reserved = new_total;
79
- h = new_h;
80
- }
81
-
82
- char *dst = (char*)a->data + h->length;
83
- if (src) memcpy(dst, src, bytes);
84
- else memset(dst, 0, bytes);
85
-
86
- h->length += bytes;
87
- h->flags |= 1; // dirty
88
- return dst;
89
- }
90
-
91
- size_t shadow_arena_len(const ShadowArena *a) {
92
- return shadow_header(a->data)->length;
93
- }
94
-
95
- void shadow_arena_clear(ShadowArena *a) {
96
- ShadowHeader *h = shadow_header(a->data);
97
- h->length = 0;
98
- h->flags &= ~1;
99
- }
100
-
101
- // --------------------------------------------------------------------
102
- // Blob Format (tagged, length‑prefixed items)
103
- // --------------------------------------------------------------------
104
- typedef struct {
105
- uint32_t size; // payload size (excluding this header)
106
- uint32_t kind; // 1=system,2=user,3=assistant,4=tool_call,5=tool_result,6=memory
107
- uint64_t id; // unique id (timestamp or counter)
108
- } BlobHeader;
109
-
110
- // Append a typed blob – returns offset from arena->data start
111
- ptrdiff_t blob_append(ShadowArena *a, uint32_t kind, uint64_t id,
112
- const void *payload, size_t payload_bytes)
113
- {
114
- size_t total = sizeof(BlobHeader) + payload_bytes;
115
- char *p = shadow_arena_push(a, NULL, total);
116
-
117
- BlobHeader bh = {
118
- .size = (uint32_t)payload_bytes,
119
- .kind = kind,
120
- .id = id
121
- };
122
- memcpy(p, &bh, sizeof(bh));
123
- if (payload_bytes) memcpy(p + sizeof(bh), payload, payload_bytes);
124
- return p - (char*)a->data;
125
- }
126
-
127
- // Iterate over all blobs: calls `f(blob_header, payload, userdata)`
128
- void blob_foreach(ShadowArena *a,
129
- void (*f)(const BlobHeader*, const char*, void*),
130
- void *userdata)
131
- {
132
- char *start = a->data;
133
- size_t len = shadow_header(a->data)->length;
134
- char *end = start + len;
135
- char *p = start;
136
- while (p < end) {
137
- BlobHeader *bh = (BlobHeader*)p;
138
- char *payload = p + sizeof(BlobHeader);
139
- f(bh, payload, userdata);
140
- p += sizeof(BlobHeader) + bh->size;
141
- }
142
- }
143
-
144
- // --------------------------------------------------------------------
145
- // Persistence: save / load the whole arena to a file
146
- // --------------------------------------------------------------------
147
- bool arena_save(const ShadowArena *a, const char *filename) {
148
- FILE *f = fopen(filename, "wb");
149
- if (!f) return false;
150
- size_t n = fwrite(shadow_header(a->data), 1, a->reserved, f);
151
- fclose(f);
152
- return n == a->reserved;
153
- }
154
-
155
- bool arena_load(ShadowArena *a, const char *filename) {
156
- FILE *f = fopen(filename, "rb");
157
- if (!f) return false;
158
-
159
- fseek(f, 0, SEEK_END);
160
- long size = ftell(f);
161
- fseek(f, 0, SEEK_SET);
162
- if (size < (long)SHADOW_SIZE) {
163
- fclose(f);
164
- return false;
165
- }
166
-
167
- void *block = malloc(size);
168
- if (!block) { fclose(f); return false; }
169
- if (fread(block, 1, size, f) != (size_t)size) {
170
- free(block);
171
- fclose(f);
172
- return false;
173
- }
174
- fclose(f);
175
-
176
- ShadowHeader *h = (ShadowHeader*)block;
177
- if (h->tag != SHADOW_MAGIC || h->version != 1) {
178
- free(block);
179
- return false;
180
- }
181
-
182
- shadow_arena_destroy(a);
183
- a->data = (char*)block + SHADOW_SIZE;
184
- a->reserved = size;
185
- return true;
186
- }
187
-
188
- // --------------------------------------------------------------------
189
- // Tools
190
- // --------------------------------------------------------------------
191
- typedef struct {
192
- char *name;
193
- char *(*func)(const char *args); // returns newly allocated string
194
- } Tool;
195
-
196
- // tool: shell command execution
197
- char* tool_shell(const char *args) {
198
- FILE *fp = popen(args, "r");
199
- if (!fp) return strdup("error: popen failed");
200
- char *result = NULL;
201
- size_t len = 0;
202
- FILE *out = open_memstream(&result, &len);
203
- char buf[256];
204
- while (fgets(buf, sizeof(buf), fp)) fputs(buf, out);
205
- pclose(fp);
206
- fclose(out);
207
- return result ? result : strdup("");
208
- }
209
-
210
- // tool: read file
211
- char* tool_read_file(const char *args) {
212
- FILE *fp = fopen(args, "rb");
213
- if (!fp) return strdup("error: cannot open file");
214
- char *content = NULL;
215
- size_t len = 0;
216
- FILE *out = open_memstream(&content, &len);
217
- char buf[4096];
218
- size_t n;
219
- while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
220
- fwrite(buf, 1, n, out);
221
- fclose(fp);
222
- fclose(out);
223
- return content ? content : strdup("");
224
- }
225
-
226
- // tool: write file (args: "filename\ncontent")
227
- char* tool_write_file(const char *args) {
228
- char *filename = strdup(args);
229
- char *newline = strchr(filename, '\n');
230
- if (!newline) {
231
- free(filename);
232
- return strdup("error: missing newline separator");
233
- }
234
- *newline = '\0';
235
- char *content = newline + 1;
236
- FILE *fp = fopen(filename, "wb");
237
- if (!fp) {
238
- free(filename);
239
- return strdup("error: cannot write file");
240
- }
241
- fwrite(content, 1, strlen(content), fp);
242
- fclose(fp);
243
- free(filename);
244
- return strdup("ok");
245
- }
246
-
247
- // tool: HTTP GET (args = URL)
248
- size_t write_cb(void *ptr, size_t size, size_t nmemb, void *stream) {
249
- size_t total = size * nmemb;
250
- fwrite(ptr, 1, total, (FILE*)stream);
251
- return total;
252
- }
253
- char* tool_http_get(const char *args) {
254
- CURL *curl = curl_easy_init();
255
- if (!curl) return strdup("error: curl init failed");
256
-
257
- char *response = NULL;
258
- size_t len = 0;
259
- FILE *out = open_memstream(&response, &len);
260
-
261
- curl_easy_setopt(curl, CURLOPT_URL, args);
262
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
263
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, out);
264
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
265
- CURLcode res = curl_easy_perform(curl);
266
- if (res != CURLE_OK) {
267
- fclose(out);
268
- curl_easy_cleanup(curl);
269
- return strdup("error: curl failed");
270
- }
271
- fclose(out);
272
- curl_easy_cleanup(curl);
273
- return response ? response : strdup("");
274
- }
275
-
276
- // tool: math expression (using bc)
277
- char* tool_math(const char *args) {
278
- char cmd[4096];
279
- snprintf(cmd, sizeof(cmd), "echo '%s' | bc 2>/dev/null", args);
280
- FILE *fp = popen(cmd, "r");
281
- if (!fp) return strdup("error: bc failed");
282
- char *result = NULL;
283
- size_t len = 0;
284
- FILE *out = open_memstream(&result, &len);
285
- char buf[256];
286
- while (fgets(buf, sizeof(buf), fp)) fputs(buf, out);
287
- pclose(fp);
288
- fclose(out);
289
- return result ? result : strdup("");
290
- }
291
-
292
- // tool: list directory
293
- char* tool_list_dir(const char *args) {
294
- DIR *d = opendir(args);
295
- if (!d) return strdup("error: cannot open directory");
296
- char *result = NULL;
297
- size_t len = 0;
298
- FILE *out = open_memstream(&result, &len);
299
- struct dirent *entry;
300
- while ((entry = readdir(d)) != NULL) {
301
- fprintf(out, "%s\n", entry->d_name);
302
- }
303
- closedir(d);
304
- fclose(out);
305
- return result ? result : strdup("");
306
- }
307
-
308
- // tool registry
309
- Tool tools[] = {
310
- {"shell", tool_shell},
311
- {"read_file", tool_read_file},
312
- {"write_file", tool_write_file},
313
- {"http_get", tool_http_get},
314
- {"math", tool_math},
315
- {"list_dir", tool_list_dir},
316
- {NULL, NULL}
317
- };
318
-
319
- char* execute_tool(const char *name, const char *args) {
320
- for (Tool *t = tools; t->name; t++) {
321
- if (strcmp(t->name, name) == 0) {
322
- return t->func(args);
323
- }
324
- }
325
- return strdup("error: unknown tool");
326
- }
327
-
328
- // --------------------------------------------------------------------
329
- // LLM interaction (Ollama)
330
- // --------------------------------------------------------------------
331
- typedef struct {
332
- char *data;
333
- size_t len;
334
- } ResponseBuffer;
335
-
336
- size_t write_response(void *ptr, size_t size, size_t nmemb, void *stream) {
337
- ResponseBuffer *buf = (ResponseBuffer*)stream;
338
- size_t total = size * nmemb;
339
- buf->data = realloc(buf->data, buf->len + total + 1);
340
- if (!buf->data) return 0;
341
- memcpy(buf->data + buf->len, ptr, total);
342
- buf->len += total;
343
- buf->data[buf->len] = '\0';
344
- return total;
345
- }
346
-
347
- // call Ollama generate endpoint, return JSON string (malloced)
348
- char* ollama_generate(const char *prompt, const char *model, const char *endpoint) {
349
- CURL *curl = curl_easy_init();
350
- if (!curl) return NULL;
351
-
352
- char url[256];
353
- snprintf(url, sizeof(url), "%s/api/generate", endpoint);
354
-
355
- cJSON *req_json = cJSON_CreateObject();
356
- cJSON_AddStringToObject(req_json, "model", model);
357
- cJSON_AddStringToObject(req_json, "prompt", prompt);
358
- cJSON_AddBoolToObject(req_json, "stream", false);
359
- char *req_str = cJSON_PrintUnformatted(req_json);
360
- cJSON_Delete(req_json);
361
-
362
- struct curl_slist *headers = NULL;
363
- headers = curl_slist_append(headers, "Content-Type: application/json");
364
-
365
- ResponseBuffer resp = {0};
366
- curl_easy_setopt(curl, CURLOPT_URL, url);
367
- curl_easy_setopt(curl, CURLOPT_POST, 1L);
368
- curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
369
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_str);
370
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_response);
371
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
372
-
373
- CURLcode res = curl_easy_perform(curl);
374
- free(req_str);
375
- curl_slist_free_all(headers);
376
- curl_easy_cleanup(curl);
377
-
378
- if (res != CURLE_OK) {
379
- free(resp.data);
380
- return NULL;
381
- }
382
- return resp.data; // caller must free
383
- }
384
-
385
- // --------------------------------------------------------------------
386
- // Prompt builder (exactly as in beta10)
387
- // --------------------------------------------------------------------
388
- typedef struct {
389
- char *text;
390
- size_t cap;
391
- size_t len;
392
- } StringBuilder;
393
-
394
- void sb_append(StringBuilder *sb, const char *s) {
395
- size_t add = strlen(s);
396
- if (sb->len + add + 1 > sb->cap) {
397
- sb->cap = sb->cap ? sb->cap * 2 : 1024;
398
- while (sb->len + add + 1 > sb->cap) sb->cap *= 2;
399
- sb->text = realloc(sb->text, sb->cap);
400
- }
401
- memcpy(sb->text + sb->len, s, add);
402
- sb->len += add;
403
- sb->text[sb->len] = '\0';
404
- }
405
-
406
- void collect_blob(const BlobHeader *bh, const char *payload, void *user) {
407
- StringBuilder *sb = (StringBuilder*)user;
408
- static int count = 0;
409
- // keep only system (kind 1) and last 10 user/assistant/tool messages
410
- if (bh->kind == 1) {
411
- sb_append(sb, "[System]\n");
412
- sb_append(sb, payload);
413
- sb_append(sb, "\n\n");
414
- } else if (bh->kind == 2 || bh->kind == 3 || bh->kind == 5) {
415
- if (count < 10) {
416
- const char *role = bh->kind==2 ? "User" : (bh->kind==3 ? "Assistant" : "Tool");
417
- sb_append(sb, "[");
418
- sb_append(sb, role);
419
- sb_append(sb, "]\n");
420
- sb_append(sb, payload);
421
- sb_append(sb, "\n\n");
422
- count++;
423
- }
424
- }
425
- }
426
-
427
- char* build_prompt(ShadowArena *arena) {
428
- StringBuilder sb = {0};
429
- // include system prompt and recent conversation
430
- blob_foreach(arena, collect_blob, &sb);
431
- // add instructions for tool use (exactly as beta10)
432
- sb_append(&sb,
433
- "[Instruction]\n"
434
- "You are ShadowClaw, a tiny AI agent. You can use tools by outputting a JSON block like:\n"
435
- "```tool\n{\"tool\":\"name\",\"args\":\"arguments\"}\n```\n"
436
- "Available tools: shell, read_file, write_file, http_get, math, list_dir.\n"
437
- "After using a tool, you'll see its result. Continue the conversation.\n\n"
438
- "[User]\n");
439
- return sb.text; // caller must free
440
- }
441
-
442
- // --------------------------------------------------------------------
443
- // Parse tool call from assistant text (look for ```tool ... ```)
444
- // Enhanced to handle both string and array args
445
- // --------------------------------------------------------------------
446
- char* parse_tool_call(const char *text, char **tool_name, char **tool_args) {
447
- const char *start = strstr(text, "```tool");
448
- if (!start) return NULL;
449
- start += 7; // skip ```tool
450
- while (*start == ' ' || *start == '\n') start++;
451
- const char *end = strstr(start, "```");
452
- if (!end) return NULL;
453
-
454
- size_t len = end - start;
455
- char *json_str = malloc(len + 1);
456
- memcpy(json_str, start, len);
457
- json_str[len] = '\0';
458
-
459
- cJSON *root = cJSON_Parse(json_str);
460
- free(json_str);
461
- if (!root) return NULL;
462
-
463
- cJSON *name = cJSON_GetObjectItem(root, "tool");
464
- if (!cJSON_IsString(name)) {
465
- cJSON_Delete(root);
466
- return NULL;
467
- }
468
-
469
- cJSON *args = cJSON_GetObjectItem(root, "args");
470
- char *args_str = NULL;
471
- if (cJSON_IsString(args)) {
472
- args_str = strdup(args->valuestring);
473
- } else if (cJSON_IsArray(args)) {
474
- int count = cJSON_GetArraySize(args);
475
- size_t total_len = 0;
476
- for (int i = 0; i < count; i++) {
477
- cJSON *elem = cJSON_GetArrayItem(args, i);
478
- if (cJSON_IsString(elem)) {
479
- if (i > 0) total_len++; // space
480
- total_len += strlen(elem->valuestring);
481
- }
482
- }
483
- args_str = malloc(total_len + 1);
484
- if (args_str) {
485
- args_str[0] = '\0';
486
- for (int i = 0; i < count; i++) {
487
- cJSON *elem = cJSON_GetArrayItem(args, i);
488
- if (cJSON_IsString(elem)) {
489
- if (i > 0) strcat(args_str, " ");
490
- strcat(args_str, elem->valuestring);
491
- }
492
- }
493
- } else {
494
- args_str = strdup("");
495
- }
496
- } else {
497
- args_str = strdup("");
498
- }
499
-
500
- *tool_name = strdup(name->valuestring);
501
- *tool_args = args_str;
502
- cJSON_Delete(root);
503
- return (char*)end + 3; // pointer after the closing ```
504
- }
505
-
506
- // --------------------------------------------------------------------
507
- // Main (with slash commands from v1.2.2)
508
- // --------------------------------------------------------------------
509
- int main(int argc, char **argv) {
510
- const char *state_file = "shadowclaw.bin";
511
- const char *ollama_endpoint = "http://localhost:11434";
512
- const char *ollama_model = "qwen2.5:0.5b"; // change as needed
513
-
514
- ShadowArena arena = shadow_arena_create(128 * 1024); // 128KB start
515
-
516
- // load previous state if exists
517
- if (access(state_file, F_OK) == 0) {
518
- if (arena_load(&arena, state_file)) {
519
- printf("[ShadowClaw] loaded state from %s\n", state_file);
520
- } else {
521
- printf("[ShadowClaw] failed to load %s, starting fresh\n", state_file);
522
- }
523
- } else {
524
- // bootstrap system prompt (includes list_dir)
525
- const char *sys = "You are ShadowClaw – tiny, shadowy, Unix‑punk AI agent. Use tools when helpful. Stay minimal.\n"
526
- "Available tools: shell, read_file, write_file, http_get, math, list_dir.";
527
- blob_append(&arena, 1, 1, sys, strlen(sys)+1);
528
- }
529
-
530
- uint64_t msg_id = time(NULL); // simple id
531
-
532
- printf("ShadowClaw ready. Type your message (Ctrl-D to exit)\n");
533
- char line[4096];
534
- while (fgets(line, sizeof(line), stdin)) {
535
- // remove trailing newline
536
- line[strcspn(line, "\n")] = 0;
537
- if (strlen(line) == 0) continue;
538
-
539
- // ----- Slash commands (always available) -----
540
- if (line[0] == '/') {
541
- if (strcmp(line, "/help") == 0) {
542
- printf("Shadowclaw commands:\n"
543
- " /help Show this help\n"
544
- " /tools List available tools\n"
545
- " /state Show arena memory stats\n"
546
- " /clear Clear conversation history (keeps system prompt)\n"
547
- " /chat Remind you that chat mode is active\n"
548
- " /exit Exit Shadowclaw\n");
549
- } else if (strcmp(line, "/tools") == 0) {
550
- printf("Available tools:\n");
551
- for (Tool *t = tools; t->name; t++) {
552
- printf(" %s\n", t->name);
553
- }
554
- } else if (strcmp(line, "/state") == 0) {
555
- ShadowHeader *h = shadow_header(arena.data);
556
- printf("Arena capacity: %zu bytes\n", h->capacity);
557
- printf("Arena used: %zu bytes\n", h->length);
558
- printf("Reserved total: %zu bytes\n", arena.reserved);
559
- printf("Dirty flag: %d\n", h->flags & 1);
560
- } else if (strcmp(line, "/clear") == 0) {
561
- shadow_arena_clear(&arena);
562
- // Re-add system prompt
563
- const char *sys = "You are ShadowClaw – tiny, shadowy, Unix‑punk AI agent. Use tools when helpful. Stay minimal.\n"
564
- "Available tools: shell, read_file, write_file, http_get, math, list_dir.";
565
- blob_append(&arena, 1, 1, sys, strlen(sys)+1);
566
- printf("Conversation cleared.\n");
567
- } else if (strcmp(line, "/chat") == 0) {
568
- printf("You are already in chat mode. Type your message.\n");
569
- } else if (strcmp(line, "/exit") == 0) {
570
- break;
571
- } else {
572
- printf("Unknown command. Try /help\n");
573
- }
574
- continue;
575
- }
576
-
577
- // ----- Normal LLM mode (exactly like beta10) -----
578
- // store user message
579
- blob_append(&arena, 2, msg_id++, line, strlen(line)+1);
580
-
581
- // build prompt from arena
582
- char *prompt = build_prompt(&arena);
583
- if (!prompt) {
584
- fprintf(stderr, "error building prompt\n");
585
- break;
586
- }
587
-
588
- // call ollama
589
- char *response_json = ollama_generate(prompt, ollama_model, ollama_endpoint);
590
- free(prompt);
591
- if (!response_json) {
592
- fprintf(stderr, "LLM call failed\n");
593
- continue;
594
- }
595
-
596
- // parse response
597
- cJSON *root = cJSON_Parse(response_json);
598
- if (!root) {
599
- fprintf(stderr, "JSON parse error. Raw response: %s\n", response_json);
600
- free(response_json);
601
- continue;
602
- }
603
-
604
- // Check for error field (Ollama error response)
605
- cJSON *err = cJSON_GetObjectItem(root, "error");
606
- if (err && cJSON_IsString(err)) {
607
- fprintf(stderr, "Ollama error: %s\n", err->valuestring);
608
- cJSON_Delete(root);
609
- free(response_json);
610
- continue;
611
- }
612
-
613
- cJSON *resp_text = cJSON_GetObjectItem(root, "response");
614
- if (!cJSON_IsString(resp_text)) {
615
- fprintf(stderr, "no 'response' field in LLM output. Full JSON: %s\n", response_json);
616
- cJSON_Delete(root);
617
- free(response_json);
618
- continue;
619
- }
620
- const char *assistant_msg = resp_text->valuestring;
621
-
622
- // check for tool call
623
- char *tool_name = NULL, *tool_args = NULL;
624
- char *after_tool = parse_tool_call(assistant_msg, &tool_name, &tool_args);
625
- if (tool_name && tool_args) {
626
- // execute tool
627
- char *tool_result = execute_tool(tool_name, tool_args);
628
- // store tool call and result
629
- blob_append(&arena, 4, msg_id++, assistant_msg, after_tool - assistant_msg);
630
- blob_append(&arena, 5, msg_id++, tool_result, strlen(tool_result)+1);
631
- // print result and continue (LLM will see it in next round)
632
- printf("\n[Tool %s] → %s\n", tool_name, tool_result);
633
- free(tool_result);
634
- free(tool_name);
635
- free(tool_args);
636
- } else {
637
- // normal assistant response
638
- printf("\n[ShadowClaw] %s\n", assistant_msg);
639
- blob_append(&arena, 3, msg_id++, assistant_msg, strlen(assistant_msg)+1);
640
- }
641
-
642
- cJSON_Delete(root);
643
- free(response_json);
644
-
645
- // save arena after each interaction
646
- if (!arena_save(&arena, state_file)) {
647
- fprintf(stderr, "warning: could not save state\n");
648
- }
649
- }
650
-
651
- shadow_arena_destroy(&arena);
652
- return 0;
653
- }