@forbocai/core 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -49,6 +49,21 @@ var sdkApi = createApi({
49
49
  transformResponse: (response) => response
50
50
  }),
51
51
  // NPC Endpoints
52
+ /**
53
+ * User Story: As the SDK protocol loop, I need a single process endpoint
54
+ * that returns one atomic instruction per turn while echoing full tape state.
55
+ * ᚹ one hop in, one hop out, like passing a lit shard through vacuum.
56
+ */
57
+ postNpcProcess: builder.mutation({
58
+ query: ({ npcId, request, apiUrl, apiKey }) => ({
59
+ url: `${apiUrl}/npcs/${npcId}/process`,
60
+ method: "POST",
61
+ headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
62
+ body: request
63
+ }),
64
+ invalidatesTags: (result, error, { npcId }) => [{ type: "NPC", id: npcId }],
65
+ transformResponse: (response) => response
66
+ }),
52
67
  postDirective: builder.mutation({
53
68
  query: ({ npcId, request, apiUrl, apiKey }) => ({
54
69
  url: `${apiUrl}/npcs/${npcId}/directive`,
@@ -190,6 +205,16 @@ var sdkApi = createApi({
190
205
  invalidatesTags: ["Soul"],
191
206
  transformResponse: (response) => response
192
207
  }),
208
+ postSoulExportConfirm: builder.mutation({
209
+ query: ({ npcId, request, apiUrl, apiKey }) => ({
210
+ url: `${apiUrl}/npcs/${npcId}/soul/confirm`,
211
+ method: "POST",
212
+ headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
213
+ body: request
214
+ }),
215
+ invalidatesTags: ["Soul"],
216
+ transformResponse: (response) => response
217
+ }),
193
218
  getSoulImport: builder.query({
194
219
  query: ({ txId, apiUrl, apiKey }) => ({
195
220
  url: `${apiUrl}/souls/${txId}`,
@@ -298,6 +323,16 @@ var sdkApi = createApi({
298
323
  invalidatesTags: ["NPC"],
299
324
  transformResponse: (response) => response
300
325
  }),
326
+ postNpcImportConfirm: builder.mutation({
327
+ query: ({ request, apiUrl, apiKey }) => ({
328
+ url: `${apiUrl}/npcs/import/confirm`,
329
+ method: "POST",
330
+ headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
331
+ body: request
332
+ }),
333
+ invalidatesTags: ["NPC"],
334
+ transformResponse: (response) => response
335
+ }),
301
336
  // Cortex Remote Endpoint
302
337
  postCortexComplete: builder.mutation({
303
338
  query: ({ cortexId, prompt, options, apiUrl, apiKey }) => ({
@@ -317,6 +352,25 @@ var sdkApi = createApi({
317
352
  })
318
353
  });
319
354
 
355
+ // src/errors.ts
356
+ var extractThunkErrorMessage = (error, fallback) => {
357
+ if (typeof error === "string") return error;
358
+ if (error && typeof error === "object") {
359
+ const e = error;
360
+ if (typeof e.data === "object" && e.data?.message) return String(e.data.message);
361
+ if (typeof e.data === "string") return e.data;
362
+ if (e.message) return e.message;
363
+ if (e.error) return e.error;
364
+ }
365
+ return fallback;
366
+ };
367
+ var requireApiKeyGuidance = (apiUrl, apiKey) => {
368
+ const normalized = apiUrl.toLowerCase();
369
+ if (normalized.includes("api.forboc.ai") && !apiKey) {
370
+ throw new Error("Missing API key. Set FORBOCAI_API_KEY (or run `forboc config set apiKey <key>`) for production API calls.");
371
+ }
372
+ };
373
+
320
374
  // src/bridgeSlice.ts
321
375
  var initialState = {
322
376
  activePresets: [],
@@ -331,6 +385,7 @@ var validateBridgeThunk = createAsyncThunk(
331
385
  async ({ action, context, npcId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
332
386
  try {
333
387
  const url = apiUrl || "https://api.forboc.ai";
388
+ requireApiKeyGuidance(url, apiKey);
334
389
  const data = await dispatch2(sdkApi.endpoints.postBridgeValidate.initiate({
335
390
  request: { action, context },
336
391
  npcId,
@@ -338,8 +393,8 @@ var validateBridgeThunk = createAsyncThunk(
338
393
  apiKey
339
394
  })).unwrap();
340
395
  return data;
341
- } catch (e) {
342
- return rejectWithValue(e.message || "Bridge validation failed");
396
+ } catch (error) {
397
+ return rejectWithValue(extractThunkErrorMessage(error, "Bridge validation failed"));
343
398
  }
344
399
  }
345
400
  );
@@ -348,9 +403,10 @@ var loadBridgePresetThunk = createAsyncThunk(
348
403
  async ({ presetName, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
349
404
  try {
350
405
  const url = apiUrl || "https://api.forboc.ai";
406
+ requireApiKeyGuidance(url, apiKey);
351
407
  return await dispatch2(sdkApi.endpoints.postBridgePreset.initiate({ presetName, apiUrl: url, apiKey })).unwrap();
352
- } catch (e) {
353
- return rejectWithValue(e.message || "Failed to load preset");
408
+ } catch (error) {
409
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to load preset"));
354
410
  }
355
411
  }
356
412
  );
@@ -359,9 +415,10 @@ var getBridgeRulesThunk = createAsyncThunk(
359
415
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
360
416
  try {
361
417
  const url = apiUrl || "https://api.forboc.ai";
418
+ requireApiKeyGuidance(url, apiKey);
362
419
  return await dispatch2(sdkApi.endpoints.getBridgeRules.initiate({ apiUrl: url, apiKey })).unwrap();
363
- } catch (e) {
364
- return rejectWithValue(e.message || "Failed to list bridge rules");
420
+ } catch (error) {
421
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list bridge rules"));
365
422
  }
366
423
  }
367
424
  );
@@ -370,9 +427,10 @@ var listRulesetsThunk = createAsyncThunk(
370
427
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
371
428
  try {
372
429
  const url = apiUrl || "https://api.forboc.ai";
430
+ requireApiKeyGuidance(url, apiKey);
373
431
  return await dispatch2(sdkApi.endpoints.getRulesets.initiate({ apiUrl: url, apiKey })).unwrap();
374
- } catch (e) {
375
- return rejectWithValue(e.message || "Failed to list rulesets");
432
+ } catch (error) {
433
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list rulesets"));
376
434
  }
377
435
  }
378
436
  );
@@ -381,9 +439,10 @@ var listRulePresetsThunk = createAsyncThunk(
381
439
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
382
440
  try {
383
441
  const url = apiUrl || "https://api.forboc.ai";
442
+ requireApiKeyGuidance(url, apiKey);
384
443
  return await dispatch2(sdkApi.endpoints.getRulePresets.initiate({ apiUrl: url, apiKey })).unwrap();
385
- } catch (e) {
386
- return rejectWithValue(e.message || "Failed to list rule presets");
444
+ } catch (error) {
445
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list rule presets"));
387
446
  }
388
447
  }
389
448
  );
@@ -392,9 +451,10 @@ var registerRulesetThunk = createAsyncThunk(
392
451
  async ({ ruleset, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
393
452
  try {
394
453
  const url = apiUrl || "https://api.forboc.ai";
454
+ requireApiKeyGuidance(url, apiKey);
395
455
  return await dispatch2(sdkApi.endpoints.postRuleRegister.initiate({ request: ruleset, apiUrl: url, apiKey })).unwrap();
396
- } catch (e) {
397
- return rejectWithValue(e.message || "Failed to register ruleset");
456
+ } catch (error) {
457
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to register ruleset"));
398
458
  }
399
459
  }
400
460
  );
@@ -403,9 +463,10 @@ var deleteRulesetThunk = createAsyncThunk(
403
463
  async ({ rulesetId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
404
464
  try {
405
465
  const url = apiUrl || "https://api.forboc.ai";
466
+ requireApiKeyGuidance(url, apiKey);
406
467
  return await dispatch2(sdkApi.endpoints.deleteRule.initiate({ rulesetId, apiUrl: url, apiKey })).unwrap();
407
- } catch (e) {
408
- return rejectWithValue(e.message || "Failed to delete ruleset");
468
+ } catch (error) {
469
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to delete ruleset"));
409
470
  }
410
471
  }
411
472
  );
@@ -454,6 +515,124 @@ var bridgeSlice_default = bridgeSlice.reducer;
454
515
 
455
516
  // src/soulSlice.ts
456
517
  import { createSlice as createSlice2, createAsyncThunk as createAsyncThunk2 } from "@reduxjs/toolkit";
518
+
519
+ // src/handlers/arweave.ts
520
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
521
+ var getLocalWalletToken = () => {
522
+ const maybeEnv = globalThis.process?.env;
523
+ return maybeEnv?.ARWEAVE_WALLET_JWK ?? null;
524
+ };
525
+ var withTimeout = async (promiseFactory, timeoutMs = 6e4) => {
526
+ const controller = new AbortController();
527
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
528
+ try {
529
+ return await promiseFactory(controller.signal);
530
+ } finally {
531
+ clearTimeout(timeout);
532
+ }
533
+ };
534
+ async function handler_ArweaveUpload(instruction, maxRetries = 3) {
535
+ const headers = {
536
+ "Content-Type": instruction.auiContentType || "application/json"
537
+ };
538
+ const authToken = instruction.auiAuthHeader ?? (getLocalWalletToken() ? `Bearer ${getLocalWalletToken()}` : null);
539
+ if (authToken) {
540
+ headers.Authorization = authToken;
541
+ }
542
+ let attempt = 0;
543
+ while (attempt < maxRetries) {
544
+ attempt += 1;
545
+ try {
546
+ const response = await withTimeout(
547
+ (signal) => fetch(instruction.auiEndpoint, {
548
+ method: "POST",
549
+ headers,
550
+ body: JSON.stringify(instruction.auiPayload),
551
+ signal
552
+ })
553
+ );
554
+ let responseBody = null;
555
+ try {
556
+ responseBody = await response.json();
557
+ } catch {
558
+ responseBody = null;
559
+ }
560
+ const txId = responseBody?.id ?? `ar_tx_sdk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
561
+ const success = response.status >= 200 && response.status < 300;
562
+ if (!success && attempt < maxRetries) {
563
+ await sleep(250 * 2 ** (attempt - 1));
564
+ continue;
565
+ }
566
+ return {
567
+ aurTxId: txId,
568
+ aurStatus: response.status,
569
+ aurSuccess: success,
570
+ aurError: success ? null : `upload_failed_status_${response.status}`
571
+ };
572
+ } catch (error) {
573
+ const message = error instanceof Error ? error.message : String(error);
574
+ if (attempt < maxRetries) {
575
+ await sleep(250 * 2 ** (attempt - 1));
576
+ continue;
577
+ }
578
+ return {
579
+ aurTxId: `ar_tx_failed_${Date.now()}`,
580
+ aurStatus: 0,
581
+ aurSuccess: false,
582
+ aurError: `upload_request_failed:${message}`
583
+ };
584
+ }
585
+ }
586
+ return {
587
+ aurTxId: `ar_tx_failed_${Date.now()}`,
588
+ aurStatus: 0,
589
+ aurSuccess: false,
590
+ aurError: "upload_retry_exhausted"
591
+ };
592
+ }
593
+ async function handler_ArweaveDownload(instruction) {
594
+ try {
595
+ const response = await withTimeout(
596
+ (signal) => fetch(instruction.adiGatewayUrl, {
597
+ method: "GET",
598
+ signal
599
+ })
600
+ );
601
+ if (response.status < 200 || response.status >= 300) {
602
+ return {
603
+ adrBody: null,
604
+ adrStatus: response.status,
605
+ adrSuccess: false,
606
+ adrError: `download_failed_status_${response.status}`
607
+ };
608
+ }
609
+ try {
610
+ const body = await response.json();
611
+ return {
612
+ adrBody: body,
613
+ adrStatus: response.status,
614
+ adrSuccess: true,
615
+ adrError: null
616
+ };
617
+ } catch {
618
+ return {
619
+ adrBody: null,
620
+ adrStatus: response.status,
621
+ adrSuccess: false,
622
+ adrError: "download_invalid_json"
623
+ };
624
+ }
625
+ } catch (error) {
626
+ return {
627
+ adrBody: null,
628
+ adrStatus: 0,
629
+ adrSuccess: false,
630
+ adrError: error instanceof Error ? error.message : String(error)
631
+ };
632
+ }
633
+ }
634
+
635
+ // src/soulSlice.ts
457
636
  var initialState2 = {
458
637
  exportStatus: "idle",
459
638
  importStatus: "idle",
@@ -464,26 +643,45 @@ var initialState2 = {
464
643
  };
465
644
  var remoteExportSoulThunk = createAsyncThunk2(
466
645
  "soul/export",
467
- async ({ npcId: argNpcId, apiUrl, apiKey, memories = [] }, { getState, dispatch: dispatch2, rejectWithValue }) => {
646
+ async ({ npcId: argNpcId, apiUrl, apiKey }, { getState, dispatch: dispatch2, rejectWithValue }) => {
468
647
  try {
469
648
  const state = getState().npc;
470
649
  const npcId = argNpcId || state.activeNpcId;
471
650
  const npc = state.entities[npcId];
472
651
  if (!npc) throw new Error(`NPC ${npcId} not found`);
473
652
  const url = apiUrl || "https://api.forboc.ai";
474
- const result = await dispatch2(sdkApi.endpoints.postSoulExport.initiate({
475
- npcId,
476
- request: { npcIdRef: npcId, persona: npc.persona || "NPC", npcState: npc.state },
477
- apiUrl: url,
478
- apiKey
479
- })).unwrap();
653
+ requireApiKeyGuidance(url, apiKey);
654
+ const phase1 = await dispatch2(
655
+ sdkApi.endpoints.postSoulExport.initiate({
656
+ npcId,
657
+ request: { npcIdRef: npcId, persona: npc.persona || "NPC", npcState: npc.state },
658
+ apiUrl: url,
659
+ apiKey
660
+ })
661
+ ).unwrap();
662
+ const uploadResult = await handler_ArweaveUpload({
663
+ ...phase1.se1Instruction,
664
+ auiAuthHeader: phase1.se1Instruction.auiAuthHeader ?? null
665
+ });
666
+ const final = await dispatch2(
667
+ sdkApi.endpoints.postSoulExportConfirm.initiate({
668
+ npcId,
669
+ request: {
670
+ secUploadResult: uploadResult,
671
+ secSignedPayload: phase1.se1SignedPayload,
672
+ secSignature: phase1.se1Signature
673
+ },
674
+ apiUrl: url,
675
+ apiKey
676
+ })
677
+ ).unwrap();
480
678
  return {
481
- txId: result.txId,
482
- url: result.arweaveUrl,
483
- soul: result.soul
679
+ txId: final.txId,
680
+ url: final.arweaveUrl,
681
+ soul: final.soul
484
682
  };
485
- } catch (e) {
486
- return rejectWithValue(e.message || "Soul export failed");
683
+ } catch (error) {
684
+ return rejectWithValue(extractThunkErrorMessage(error, "Soul export failed"));
487
685
  }
488
686
  }
489
687
  );
@@ -492,10 +690,31 @@ var importSoulFromArweaveThunk = createAsyncThunk2(
492
690
  async ({ txId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
493
691
  try {
494
692
  const url = apiUrl || "https://api.forboc.ai";
495
- const data = await dispatch2(sdkApi.endpoints.getSoulImport.initiate({ txId, apiUrl: url, apiKey })).unwrap();
496
- return data;
497
- } catch (e) {
498
- return rejectWithValue(e.message || "Soul import failed");
693
+ requireApiKeyGuidance(url, apiKey);
694
+ const phase1 = await dispatch2(
695
+ sdkApi.endpoints.postNpcImport.initiate({ request: { txIdRef: txId }, apiUrl: url, apiKey })
696
+ ).unwrap();
697
+ const downloadResult = await handler_ArweaveDownload(phase1.si1Instruction);
698
+ const npc = await dispatch2(
699
+ sdkApi.endpoints.postNpcImportConfirm.initiate({
700
+ request: {
701
+ sicTxId: txId,
702
+ sicDownloadResult: downloadResult
703
+ },
704
+ apiUrl: url,
705
+ apiKey
706
+ })
707
+ ).unwrap();
708
+ return {
709
+ id: txId,
710
+ version: "2.0.0",
711
+ name: npc.npcId,
712
+ persona: npc.persona,
713
+ memories: [],
714
+ state: npc.data || {}
715
+ };
716
+ } catch (error) {
717
+ return rejectWithValue(extractThunkErrorMessage(error, "Soul import failed"));
499
718
  }
500
719
  }
501
720
  );
@@ -504,10 +723,11 @@ var getSoulListThunk = createAsyncThunk2(
504
723
  async ({ limit = 50, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
505
724
  try {
506
725
  const url = apiUrl || "https://api.forboc.ai";
726
+ requireApiKeyGuidance(url, apiKey);
507
727
  const data = await dispatch2(sdkApi.endpoints.getSouls.initiate({ limit, apiUrl: url, apiKey })).unwrap();
508
728
  return data.souls || [];
509
- } catch (e) {
510
- return rejectWithValue(e.message || "Failed to list souls");
729
+ } catch (error) {
730
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list souls"));
511
731
  }
512
732
  }
513
733
  );
@@ -516,9 +736,10 @@ var verifySoulThunk = createAsyncThunk2(
516
736
  async ({ txId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
517
737
  try {
518
738
  const url = apiUrl || "https://api.forboc.ai";
739
+ requireApiKeyGuidance(url, apiKey);
519
740
  return await dispatch2(sdkApi.endpoints.postSoulVerify.initiate({ txId, apiUrl: url, apiKey })).unwrap();
520
- } catch (e) {
521
- return rejectWithValue(e.message || "Soul verify failed");
741
+ } catch (error) {
742
+ return rejectWithValue(extractThunkErrorMessage(error, "Soul verify failed"));
522
743
  }
523
744
  }
524
745
  );
@@ -527,13 +748,24 @@ var importNpcFromSoulThunk = createAsyncThunk2(
527
748
  async ({ txId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
528
749
  try {
529
750
  const url = apiUrl || "https://api.forboc.ai";
530
- return await dispatch2(sdkApi.endpoints.postNpcImport.initiate({
531
- request: { txIdRef: txId },
532
- apiUrl: url,
533
- apiKey
534
- })).unwrap();
535
- } catch (e) {
536
- return rejectWithValue(e.message || "NPC import from soul failed");
751
+ requireApiKeyGuidance(url, apiKey);
752
+ const phase1 = await dispatch2(
753
+ sdkApi.endpoints.postNpcImport.initiate({
754
+ request: { txIdRef: txId },
755
+ apiUrl: url,
756
+ apiKey
757
+ })
758
+ ).unwrap();
759
+ const downloadResult = await handler_ArweaveDownload(phase1.si1Instruction);
760
+ return await dispatch2(
761
+ sdkApi.endpoints.postNpcImportConfirm.initiate({
762
+ request: { sicTxId: txId, sicDownloadResult: downloadResult },
763
+ apiUrl: url,
764
+ apiKey
765
+ })
766
+ ).unwrap();
767
+ } catch (error) {
768
+ return rejectWithValue(extractThunkErrorMessage(error, "NPC import from soul failed"));
537
769
  }
538
770
  }
539
771
  );
@@ -592,6 +824,7 @@ var startGhostThunk = createAsyncThunk3(
592
824
  async (config, { dispatch: dispatch2, rejectWithValue }) => {
593
825
  try {
594
826
  const apiUrl = config.apiUrl || "https://api.forboc.ai";
827
+ requireApiKeyGuidance(apiUrl, config.apiKey);
595
828
  const data = await dispatch2(sdkApi.endpoints.postGhostRun.initiate({
596
829
  request: { testSuite: config.testSuite, duration: config.duration ?? 300 },
597
830
  apiUrl,
@@ -601,8 +834,8 @@ var startGhostThunk = createAsyncThunk3(
601
834
  sessionId: data.sessionId,
602
835
  status: data.runStatus
603
836
  };
604
- } catch (e) {
605
- return rejectWithValue(e.message || "Failed to start Ghost");
837
+ } catch (error) {
838
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to start Ghost"));
606
839
  }
607
840
  }
608
841
  );
@@ -614,6 +847,7 @@ var getGhostStatusThunk = createAsyncThunk3(
614
847
  const targetSession = sessionId || state.activeSessionId;
615
848
  if (!targetSession) throw new Error("No active Ghost session");
616
849
  const url = apiUrl || "https://api.forboc.ai";
850
+ requireApiKeyGuidance(url, apiKey);
617
851
  const data = await dispatch2(sdkApi.endpoints.getGhostStatus.initiate({ sessionId: targetSession, apiUrl: url, apiKey })).unwrap();
618
852
  return {
619
853
  sessionId: data.ghostSessionId,
@@ -623,8 +857,8 @@ var getGhostStatusThunk = createAsyncThunk3(
623
857
  duration: data.ghostDuration || 0,
624
858
  errors: data.ghostErrors
625
859
  };
626
- } catch (e) {
627
- return rejectWithValue(e.message || "Failed to get ghost status");
860
+ } catch (error) {
861
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to get ghost status"));
628
862
  }
629
863
  }
630
864
  );
@@ -636,6 +870,7 @@ var getGhostResultsThunk = createAsyncThunk3(
636
870
  const targetSession = sessionId || state.activeSessionId;
637
871
  if (!targetSession) throw new Error("No active Ghost session");
638
872
  const url = apiUrl || "https://api.forboc.ai";
873
+ requireApiKeyGuidance(url, apiKey);
639
874
  const data = await dispatch2(sdkApi.endpoints.getGhostResults.initiate({ sessionId: targetSession, apiUrl: url, apiKey })).unwrap();
640
875
  return {
641
876
  sessionId: data.resultsSessionId,
@@ -654,8 +889,8 @@ var getGhostResultsThunk = createAsyncThunk3(
654
889
  coverage: data.resultsCoverage,
655
890
  metrics: Object.fromEntries(data.resultsMetrics || [])
656
891
  };
657
- } catch (e) {
658
- return rejectWithValue(e.message || "Failed to get ghost results");
892
+ } catch (error) {
893
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to get ghost results"));
659
894
  }
660
895
  }
661
896
  );
@@ -667,14 +902,15 @@ var stopGhostThunk = createAsyncThunk3(
667
902
  const targetSession = sessionId || state.activeSessionId;
668
903
  if (!targetSession) throw new Error("No active Ghost session");
669
904
  const url = apiUrl || "https://api.forboc.ai";
905
+ requireApiKeyGuidance(url, apiKey);
670
906
  const data = await dispatch2(sdkApi.endpoints.postGhostStop.initiate({ sessionId: targetSession, apiUrl: url, apiKey })).unwrap();
671
907
  return {
672
908
  stopped: data.stopped,
673
909
  status: data.stopStatus,
674
910
  sessionId: data.stopSessionId
675
911
  };
676
- } catch (e) {
677
- return rejectWithValue(e.message || "Failed to stop ghost session");
912
+ } catch (error) {
913
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to stop ghost session"));
678
914
  }
679
915
  }
680
916
  );
@@ -683,6 +919,7 @@ var getGhostHistoryThunk = createAsyncThunk3(
683
919
  async ({ limit = 10, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
684
920
  try {
685
921
  const url = apiUrl || "https://api.forboc.ai";
922
+ requireApiKeyGuidance(url, apiKey);
686
923
  const data = await dispatch2(sdkApi.endpoints.getGhostHistory.initiate({ limit, apiUrl: url, apiKey })).unwrap();
687
924
  return (data.sessions || []).map((s) => ({
688
925
  sessionId: s.sessionId,
@@ -692,8 +929,8 @@ var getGhostHistoryThunk = createAsyncThunk3(
692
929
  status: s.status,
693
930
  passRate: s.passRate
694
931
  }));
695
- } catch (e) {
696
- return rejectWithValue(e.message || "Failed to get ghost history");
932
+ } catch (error) {
933
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to get ghost history"));
697
934
  }
698
935
  }
699
936
  );
@@ -728,7 +965,7 @@ var ghostSlice = createSlice3({
728
965
  state.status = "completed";
729
966
  }).addCase(stopGhostThunk.fulfilled, (state, action) => {
730
967
  if (action.payload.stopped) {
731
- state.status = "failed";
968
+ state.status = "completed";
732
969
  } else {
733
970
  state.error = action.payload.status || "Ghost stop request did not stop a session";
734
971
  }
@@ -743,7 +980,7 @@ var { clearGhostSession } = ghostSlice.actions;
743
980
  var ghostSlice_default = ghostSlice.reducer;
744
981
 
745
982
  // src/utils/sdkVersion.ts
746
- var SDK_VERSION = "0.6.0";
983
+ var SDK_VERSION = "0.6.1";
747
984
 
748
985
  // src/utils/generateNPCId.ts
749
986
  var generateNPCId = () => `ag_${Date.now().toString(36)}`;
@@ -824,7 +1061,8 @@ var npcSlice = createSlice4({
824
1061
  persona,
825
1062
  state: initialState5 || {},
826
1063
  history: [],
827
- isBlocked: false
1064
+ isBlocked: false,
1065
+ stateLog: [{ timestamp: Date.now(), delta: initialState5 || {}, state: initialState5 || {} }]
828
1066
  });
829
1067
  state.activeNpcId = id;
830
1068
  },
@@ -839,7 +1077,9 @@ var npcSlice = createSlice4({
839
1077
  const { id, delta } = action.payload;
840
1078
  const npc = state.entities[id];
841
1079
  if (npc) {
842
- npc.state = { ...npc.state, ...delta };
1080
+ const newState = { ...npc.state, ...delta };
1081
+ npc.state = newState;
1082
+ npc.stateLog.push({ timestamp: Date.now(), delta, state: newState });
843
1083
  }
844
1084
  },
845
1085
  addToHistory: (state, action) => {
@@ -909,6 +1149,7 @@ var initRemoteCortexThunk = createAsyncThunk4(
909
1149
  async ({ model = "api-integrated", authKey, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
910
1150
  try {
911
1151
  const url = apiUrl || "https://api.forboc.ai";
1152
+ requireApiKeyGuidance(url, apiKey);
912
1153
  const data = await dispatch2(sdkApi.endpoints.postCortexInit.initiate({
913
1154
  request: { requestedModel: model, authKey },
914
1155
  apiUrl: url,
@@ -920,8 +1161,8 @@ var initRemoteCortexThunk = createAsyncThunk4(
920
1161
  ready: data.state?.toLowerCase() === "ready",
921
1162
  engine: "remote"
922
1163
  };
923
- } catch (e) {
924
- return rejectWithValue(e.message || "Remote cortex init failed");
1164
+ } catch (error) {
1165
+ return rejectWithValue(extractThunkErrorMessage(error, "Remote cortex init failed"));
925
1166
  }
926
1167
  }
927
1168
  );
@@ -930,9 +1171,10 @@ var listCortexModelsThunk = createAsyncThunk4(
930
1171
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
931
1172
  try {
932
1173
  const url = apiUrl || "https://api.forboc.ai";
1174
+ requireApiKeyGuidance(url, apiKey);
933
1175
  return await dispatch2(sdkApi.endpoints.getCortexModels.initiate({ apiUrl: url, apiKey })).unwrap();
934
- } catch (e) {
935
- return rejectWithValue(e.message || "Failed to list cortex models");
1176
+ } catch (error) {
1177
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list cortex models"));
936
1178
  }
937
1179
  }
938
1180
  );
@@ -940,6 +1182,7 @@ var completeRemoteThunk = createAsyncThunk4(
940
1182
  "cortex/completeRemote",
941
1183
  async ({ cortexId, prompt, options, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
942
1184
  try {
1185
+ requireApiKeyGuidance(apiUrl, apiKey);
943
1186
  const data = await dispatch2(sdkApi.endpoints.postCortexComplete.initiate({
944
1187
  cortexId,
945
1188
  prompt,
@@ -948,8 +1191,8 @@ var completeRemoteThunk = createAsyncThunk4(
948
1191
  apiKey
949
1192
  })).unwrap();
950
1193
  return data.text;
951
- } catch (e) {
952
- return rejectWithValue(e.message || "Remote completing failed");
1194
+ } catch (error) {
1195
+ return rejectWithValue(extractThunkErrorMessage(error, "Remote completing failed"));
953
1196
  }
954
1197
  }
955
1198
  );
@@ -1184,6 +1427,13 @@ var dispatch = store.dispatch;
1184
1427
 
1185
1428
  // src/thunks.ts
1186
1429
  import { createAsyncThunk as createAsyncThunk5 } from "@reduxjs/toolkit";
1430
+ var extractThunkErrorMessage2 = (e) => {
1431
+ if (typeof e === "string") return e;
1432
+ if (e?.data?.message) return String(e.data.message);
1433
+ if (e?.error) return String(e.error);
1434
+ if (e?.message) return String(e.message);
1435
+ return "Protocol processing failed";
1436
+ };
1187
1437
  var processNPC = createAsyncThunk5(
1188
1438
  "npc/process",
1189
1439
  async ({ npcId: argNpcId, text, context = {}, apiUrl, apiKey, memory, cortex, persona: argPersona }, { getState, dispatch: dispatch2, rejectWithValue }) => {
@@ -1207,61 +1457,111 @@ var processNPC = createAsyncThunk5(
1207
1457
  const directiveId = `${npcId}:${Date.now()}`;
1208
1458
  dispatch2(directiveRunStarted({ id: directiveId, npcId, observation: text }));
1209
1459
  try {
1210
- const directiveResult = await dispatch2(
1211
- sdkApi.endpoints.postDirective.initiate({ npcId, request: { observation: text, npcState: currentState, context }, apiUrl, apiKey })
1212
- ).unwrap();
1213
- dispatch2(directiveReceived({ id: directiveId, response: directiveResult }));
1214
- if (directiveResult.memoryRecall && !memory) {
1215
- return rejectWithValue("API requested memory recall, but no memory engine is configured");
1216
- }
1217
- const recalledMemories = memory && directiveResult.memoryRecall ? (await memory.recall(
1218
- directiveResult.memoryRecall.query,
1219
- directiveResult.memoryRecall.limit,
1220
- directiveResult.memoryRecall.threshold
1221
- )).map((m) => ({ text: m.text, type: m.type, importance: m.importance })) : [];
1222
- const contextResult = await dispatch2(
1223
- sdkApi.endpoints.postContext.initiate({ npcId, request: { memories: recalledMemories, observation: text, npcState: currentState, persona }, apiUrl, apiKey })
1224
- ).unwrap();
1225
- dispatch2(contextComposed({ id: directiveId, prompt: contextResult.prompt, constraints: contextResult.constraints }));
1226
- const generatedText = await cortex.complete(contextResult.prompt, {
1227
- maxTokens: contextResult.constraints.maxTokens,
1228
- temperature: contextResult.constraints.temperature,
1229
- stop: contextResult.constraints.stop
1230
- });
1231
- const verdictResult = await dispatch2(
1232
- sdkApi.endpoints.postVerdict.initiate({ npcId, request: { generatedOutput: generatedText, observation: text, npcState: currentState }, apiUrl, apiKey })
1233
- ).unwrap();
1234
- dispatch2(verdictValidated({ id: directiveId, verdict: verdictResult }));
1235
- if (!verdictResult.valid) {
1236
- dispatch2(blockAction({ id: npcId, reason: verdictResult.dialogue || "Validation Failed" }));
1237
- return {
1238
- dialogue: verdictResult.dialogue,
1239
- action: verdictResult.action,
1240
- thought: verdictResult.dialogue
1241
- };
1242
- }
1243
- if (verdictResult.memoryStore?.length && !memory) {
1244
- return rejectWithValue("API returned memoryStore instructions, but no memory engine is configured");
1245
- }
1246
- if (memory && verdictResult.memoryStore) {
1247
- for (const inst of verdictResult.memoryStore) {
1248
- await memory.store(inst.text, inst.type, inst.importance);
1460
+ const initialTape = {
1461
+ observation: text,
1462
+ context,
1463
+ npcState: currentState,
1464
+ persona,
1465
+ memories: [],
1466
+ vectorQueried: false
1467
+ };
1468
+ const maxTurns = 12;
1469
+ const persistMemoryInstructionsRecursively = async (instructions, index = 0) => {
1470
+ if (!memory || index >= instructions.length) {
1471
+ return;
1249
1472
  }
1250
- }
1251
- if (verdictResult.stateDelta) {
1252
- dispatch2(updateNPCState({ id: npcId, delta: verdictResult.stateDelta }));
1253
- }
1254
- const action = verdictResult.action;
1255
- dispatch2(setLastAction({ id: npcId, action }));
1256
- dispatch2(addToHistory({ id: npcId, role: "user", content: text }));
1257
- dispatch2(addToHistory({ id: npcId, role: "assistant", content: verdictResult.dialogue }));
1258
- return {
1259
- dialogue: verdictResult.dialogue,
1260
- action,
1261
- thought: verdictResult.dialogue
1473
+ const inst = instructions[index];
1474
+ await memory.store(inst.text, inst.type, inst.importance);
1475
+ await persistMemoryInstructionsRecursively(instructions, index + 1);
1476
+ };
1477
+ const runProtocolRecursively = async (tape, lastResult, turn) => {
1478
+ if (turn >= maxTurns) {
1479
+ return rejectWithValue(`Protocol loop exceeded max turns (${maxTurns})`);
1480
+ }
1481
+ const request = { tape, lastResult };
1482
+ const processResult = await dispatch2(
1483
+ sdkApi.endpoints.postNpcProcess.initiate({ npcId, request, apiUrl, apiKey })
1484
+ ).unwrap();
1485
+ const nextTape = processResult.tape;
1486
+ const instruction = processResult.instruction;
1487
+ if (instruction.type === "IdentifyActor") {
1488
+ return runProtocolRecursively(nextTape, {
1489
+ type: "IdentifyActorResult",
1490
+ actor: {
1491
+ npcId,
1492
+ persona,
1493
+ data: nextTape.npcState
1494
+ }
1495
+ }, turn + 1);
1496
+ }
1497
+ if (instruction.type === "QueryVector") {
1498
+ dispatch2(directiveReceived({
1499
+ id: directiveId,
1500
+ response: { memoryRecall: { query: instruction.query, limit: instruction.limit, threshold: instruction.threshold } }
1501
+ }));
1502
+ if (!memory) {
1503
+ return rejectWithValue("API requested memory recall, but no memory engine is configured");
1504
+ }
1505
+ const recalled = await memory.recall(instruction.query, instruction.limit, instruction.threshold);
1506
+ return runProtocolRecursively(nextTape, {
1507
+ type: "QueryVectorResult",
1508
+ memories: recalled.map((m) => ({ text: m.text, type: m.type, importance: m.importance, similarity: m.similarity }))
1509
+ }, turn + 1);
1510
+ }
1511
+ if (instruction.type === "ExecuteInference") {
1512
+ dispatch2(contextComposed({ id: directiveId, prompt: instruction.prompt, constraints: instruction.constraints }));
1513
+ const generatedText = await cortex.complete(instruction.prompt, {
1514
+ maxTokens: instruction.constraints.maxTokens,
1515
+ temperature: instruction.constraints.temperature,
1516
+ stop: instruction.constraints.stop
1517
+ });
1518
+ return runProtocolRecursively(nextTape, {
1519
+ type: "ExecuteInferenceResult",
1520
+ generatedOutput: generatedText
1521
+ }, turn + 1);
1522
+ }
1523
+ if (instruction.type === "Finalize") {
1524
+ const finalize = instruction;
1525
+ dispatch2(verdictValidated({
1526
+ id: directiveId,
1527
+ verdict: {
1528
+ valid: finalize.valid,
1529
+ signature: finalize.signature,
1530
+ memoryStore: finalize.memoryStore,
1531
+ stateDelta: finalize.stateTransform,
1532
+ action: finalize.action,
1533
+ dialogue: finalize.dialogue
1534
+ }
1535
+ }));
1536
+ if (!finalize.valid) {
1537
+ dispatch2(blockAction({ id: npcId, reason: finalize.dialogue || "Validation Failed" }));
1538
+ return {
1539
+ dialogue: finalize.dialogue,
1540
+ action: finalize.action,
1541
+ thought: finalize.dialogue
1542
+ };
1543
+ }
1544
+ if (finalize.memoryStore?.length && !memory) {
1545
+ return rejectWithValue("API returned memoryStore instructions, but no memory engine is configured");
1546
+ }
1547
+ await persistMemoryInstructionsRecursively(finalize.memoryStore || []);
1548
+ if (finalize.stateTransform) {
1549
+ dispatch2(updateNPCState({ id: npcId, delta: finalize.stateTransform }));
1550
+ }
1551
+ dispatch2(setLastAction({ id: npcId, action: finalize.action }));
1552
+ dispatch2(addToHistory({ id: npcId, role: "user", content: text }));
1553
+ dispatch2(addToHistory({ id: npcId, role: "assistant", content: finalize.dialogue }));
1554
+ return {
1555
+ dialogue: finalize.dialogue,
1556
+ action: finalize.action,
1557
+ thought: finalize.dialogue
1558
+ };
1559
+ }
1560
+ return rejectWithValue("API returned unknown instruction type");
1262
1561
  };
1562
+ return runProtocolRecursively(initialTape, void 0, 0);
1263
1563
  } catch (e) {
1264
- const message = e?.message || e?.data?.message || "Protocol processing failed";
1564
+ const message = extractThunkErrorMessage2(e);
1265
1565
  dispatch2(directiveRunFailed({ id: directiveId, error: String(message) }));
1266
1566
  return rejectWithValue(String(message));
1267
1567
  }
@@ -1381,6 +1681,8 @@ export {
1381
1681
  getGhostStatusThunk,
1382
1682
  getSoulListThunk,
1383
1683
  ghostSlice,
1684
+ handler_ArweaveDownload,
1685
+ handler_ArweaveUpload,
1384
1686
  importNpcFromSoulThunk,
1385
1687
  importSoulFromArweaveThunk,
1386
1688
  initRemoteCortexThunk,