@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.js CHANGED
@@ -54,6 +54,8 @@ __export(index_exports, {
54
54
  getGhostStatusThunk: () => getGhostStatusThunk,
55
55
  getSoulListThunk: () => getSoulListThunk,
56
56
  ghostSlice: () => ghostSlice,
57
+ handler_ArweaveDownload: () => handler_ArweaveDownload,
58
+ handler_ArweaveUpload: () => handler_ArweaveUpload,
57
59
  importNpcFromSoulThunk: () => importNpcFromSoulThunk,
58
60
  importSoulFromArweaveThunk: () => importSoulFromArweaveThunk,
59
61
  initRemoteCortexThunk: () => initRemoteCortexThunk,
@@ -169,6 +171,21 @@ var sdkApi = (0, import_query.createApi)({
169
171
  transformResponse: (response) => response
170
172
  }),
171
173
  // NPC Endpoints
174
+ /**
175
+ * User Story: As the SDK protocol loop, I need a single process endpoint
176
+ * that returns one atomic instruction per turn while echoing full tape state.
177
+ * ᚹ one hop in, one hop out, like passing a lit shard through vacuum.
178
+ */
179
+ postNpcProcess: builder.mutation({
180
+ query: ({ npcId, request, apiUrl, apiKey }) => ({
181
+ url: `${apiUrl}/npcs/${npcId}/process`,
182
+ method: "POST",
183
+ headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
184
+ body: request
185
+ }),
186
+ invalidatesTags: (result, error, { npcId }) => [{ type: "NPC", id: npcId }],
187
+ transformResponse: (response) => response
188
+ }),
172
189
  postDirective: builder.mutation({
173
190
  query: ({ npcId, request, apiUrl, apiKey }) => ({
174
191
  url: `${apiUrl}/npcs/${npcId}/directive`,
@@ -310,6 +327,16 @@ var sdkApi = (0, import_query.createApi)({
310
327
  invalidatesTags: ["Soul"],
311
328
  transformResponse: (response) => response
312
329
  }),
330
+ postSoulExportConfirm: builder.mutation({
331
+ query: ({ npcId, request, apiUrl, apiKey }) => ({
332
+ url: `${apiUrl}/npcs/${npcId}/soul/confirm`,
333
+ method: "POST",
334
+ headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
335
+ body: request
336
+ }),
337
+ invalidatesTags: ["Soul"],
338
+ transformResponse: (response) => response
339
+ }),
313
340
  getSoulImport: builder.query({
314
341
  query: ({ txId, apiUrl, apiKey }) => ({
315
342
  url: `${apiUrl}/souls/${txId}`,
@@ -418,6 +445,16 @@ var sdkApi = (0, import_query.createApi)({
418
445
  invalidatesTags: ["NPC"],
419
446
  transformResponse: (response) => response
420
447
  }),
448
+ postNpcImportConfirm: builder.mutation({
449
+ query: ({ request, apiUrl, apiKey }) => ({
450
+ url: `${apiUrl}/npcs/import/confirm`,
451
+ method: "POST",
452
+ headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
453
+ body: request
454
+ }),
455
+ invalidatesTags: ["NPC"],
456
+ transformResponse: (response) => response
457
+ }),
421
458
  // Cortex Remote Endpoint
422
459
  postCortexComplete: builder.mutation({
423
460
  query: ({ cortexId, prompt, options, apiUrl, apiKey }) => ({
@@ -437,6 +474,25 @@ var sdkApi = (0, import_query.createApi)({
437
474
  })
438
475
  });
439
476
 
477
+ // src/errors.ts
478
+ var extractThunkErrorMessage = (error, fallback) => {
479
+ if (typeof error === "string") return error;
480
+ if (error && typeof error === "object") {
481
+ const e = error;
482
+ if (typeof e.data === "object" && e.data?.message) return String(e.data.message);
483
+ if (typeof e.data === "string") return e.data;
484
+ if (e.message) return e.message;
485
+ if (e.error) return e.error;
486
+ }
487
+ return fallback;
488
+ };
489
+ var requireApiKeyGuidance = (apiUrl, apiKey) => {
490
+ const normalized = apiUrl.toLowerCase();
491
+ if (normalized.includes("api.forboc.ai") && !apiKey) {
492
+ throw new Error("Missing API key. Set FORBOCAI_API_KEY (or run `forboc config set apiKey <key>`) for production API calls.");
493
+ }
494
+ };
495
+
440
496
  // src/bridgeSlice.ts
441
497
  var initialState = {
442
498
  activePresets: [],
@@ -451,6 +507,7 @@ var validateBridgeThunk = (0, import_toolkit.createAsyncThunk)(
451
507
  async ({ action, context, npcId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
452
508
  try {
453
509
  const url = apiUrl || "https://api.forboc.ai";
510
+ requireApiKeyGuidance(url, apiKey);
454
511
  const data = await dispatch2(sdkApi.endpoints.postBridgeValidate.initiate({
455
512
  request: { action, context },
456
513
  npcId,
@@ -458,8 +515,8 @@ var validateBridgeThunk = (0, import_toolkit.createAsyncThunk)(
458
515
  apiKey
459
516
  })).unwrap();
460
517
  return data;
461
- } catch (e) {
462
- return rejectWithValue(e.message || "Bridge validation failed");
518
+ } catch (error) {
519
+ return rejectWithValue(extractThunkErrorMessage(error, "Bridge validation failed"));
463
520
  }
464
521
  }
465
522
  );
@@ -468,9 +525,10 @@ var loadBridgePresetThunk = (0, import_toolkit.createAsyncThunk)(
468
525
  async ({ presetName, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
469
526
  try {
470
527
  const url = apiUrl || "https://api.forboc.ai";
528
+ requireApiKeyGuidance(url, apiKey);
471
529
  return await dispatch2(sdkApi.endpoints.postBridgePreset.initiate({ presetName, apiUrl: url, apiKey })).unwrap();
472
- } catch (e) {
473
- return rejectWithValue(e.message || "Failed to load preset");
530
+ } catch (error) {
531
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to load preset"));
474
532
  }
475
533
  }
476
534
  );
@@ -479,9 +537,10 @@ var getBridgeRulesThunk = (0, import_toolkit.createAsyncThunk)(
479
537
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
480
538
  try {
481
539
  const url = apiUrl || "https://api.forboc.ai";
540
+ requireApiKeyGuidance(url, apiKey);
482
541
  return await dispatch2(sdkApi.endpoints.getBridgeRules.initiate({ apiUrl: url, apiKey })).unwrap();
483
- } catch (e) {
484
- return rejectWithValue(e.message || "Failed to list bridge rules");
542
+ } catch (error) {
543
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list bridge rules"));
485
544
  }
486
545
  }
487
546
  );
@@ -490,9 +549,10 @@ var listRulesetsThunk = (0, import_toolkit.createAsyncThunk)(
490
549
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
491
550
  try {
492
551
  const url = apiUrl || "https://api.forboc.ai";
552
+ requireApiKeyGuidance(url, apiKey);
493
553
  return await dispatch2(sdkApi.endpoints.getRulesets.initiate({ apiUrl: url, apiKey })).unwrap();
494
- } catch (e) {
495
- return rejectWithValue(e.message || "Failed to list rulesets");
554
+ } catch (error) {
555
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list rulesets"));
496
556
  }
497
557
  }
498
558
  );
@@ -501,9 +561,10 @@ var listRulePresetsThunk = (0, import_toolkit.createAsyncThunk)(
501
561
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
502
562
  try {
503
563
  const url = apiUrl || "https://api.forboc.ai";
564
+ requireApiKeyGuidance(url, apiKey);
504
565
  return await dispatch2(sdkApi.endpoints.getRulePresets.initiate({ apiUrl: url, apiKey })).unwrap();
505
- } catch (e) {
506
- return rejectWithValue(e.message || "Failed to list rule presets");
566
+ } catch (error) {
567
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list rule presets"));
507
568
  }
508
569
  }
509
570
  );
@@ -512,9 +573,10 @@ var registerRulesetThunk = (0, import_toolkit.createAsyncThunk)(
512
573
  async ({ ruleset, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
513
574
  try {
514
575
  const url = apiUrl || "https://api.forboc.ai";
576
+ requireApiKeyGuidance(url, apiKey);
515
577
  return await dispatch2(sdkApi.endpoints.postRuleRegister.initiate({ request: ruleset, apiUrl: url, apiKey })).unwrap();
516
- } catch (e) {
517
- return rejectWithValue(e.message || "Failed to register ruleset");
578
+ } catch (error) {
579
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to register ruleset"));
518
580
  }
519
581
  }
520
582
  );
@@ -523,9 +585,10 @@ var deleteRulesetThunk = (0, import_toolkit.createAsyncThunk)(
523
585
  async ({ rulesetId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
524
586
  try {
525
587
  const url = apiUrl || "https://api.forboc.ai";
588
+ requireApiKeyGuidance(url, apiKey);
526
589
  return await dispatch2(sdkApi.endpoints.deleteRule.initiate({ rulesetId, apiUrl: url, apiKey })).unwrap();
527
- } catch (e) {
528
- return rejectWithValue(e.message || "Failed to delete ruleset");
590
+ } catch (error) {
591
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to delete ruleset"));
529
592
  }
530
593
  }
531
594
  );
@@ -574,6 +637,124 @@ var bridgeSlice_default = bridgeSlice.reducer;
574
637
 
575
638
  // src/soulSlice.ts
576
639
  var import_toolkit2 = require("@reduxjs/toolkit");
640
+
641
+ // src/handlers/arweave.ts
642
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
643
+ var getLocalWalletToken = () => {
644
+ const maybeEnv = globalThis.process?.env;
645
+ return maybeEnv?.ARWEAVE_WALLET_JWK ?? null;
646
+ };
647
+ var withTimeout = async (promiseFactory, timeoutMs = 6e4) => {
648
+ const controller = new AbortController();
649
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
650
+ try {
651
+ return await promiseFactory(controller.signal);
652
+ } finally {
653
+ clearTimeout(timeout);
654
+ }
655
+ };
656
+ async function handler_ArweaveUpload(instruction, maxRetries = 3) {
657
+ const headers = {
658
+ "Content-Type": instruction.auiContentType || "application/json"
659
+ };
660
+ const authToken = instruction.auiAuthHeader ?? (getLocalWalletToken() ? `Bearer ${getLocalWalletToken()}` : null);
661
+ if (authToken) {
662
+ headers.Authorization = authToken;
663
+ }
664
+ let attempt = 0;
665
+ while (attempt < maxRetries) {
666
+ attempt += 1;
667
+ try {
668
+ const response = await withTimeout(
669
+ (signal) => fetch(instruction.auiEndpoint, {
670
+ method: "POST",
671
+ headers,
672
+ body: JSON.stringify(instruction.auiPayload),
673
+ signal
674
+ })
675
+ );
676
+ let responseBody = null;
677
+ try {
678
+ responseBody = await response.json();
679
+ } catch {
680
+ responseBody = null;
681
+ }
682
+ const txId = responseBody?.id ?? `ar_tx_sdk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
683
+ const success = response.status >= 200 && response.status < 300;
684
+ if (!success && attempt < maxRetries) {
685
+ await sleep(250 * 2 ** (attempt - 1));
686
+ continue;
687
+ }
688
+ return {
689
+ aurTxId: txId,
690
+ aurStatus: response.status,
691
+ aurSuccess: success,
692
+ aurError: success ? null : `upload_failed_status_${response.status}`
693
+ };
694
+ } catch (error) {
695
+ const message = error instanceof Error ? error.message : String(error);
696
+ if (attempt < maxRetries) {
697
+ await sleep(250 * 2 ** (attempt - 1));
698
+ continue;
699
+ }
700
+ return {
701
+ aurTxId: `ar_tx_failed_${Date.now()}`,
702
+ aurStatus: 0,
703
+ aurSuccess: false,
704
+ aurError: `upload_request_failed:${message}`
705
+ };
706
+ }
707
+ }
708
+ return {
709
+ aurTxId: `ar_tx_failed_${Date.now()}`,
710
+ aurStatus: 0,
711
+ aurSuccess: false,
712
+ aurError: "upload_retry_exhausted"
713
+ };
714
+ }
715
+ async function handler_ArweaveDownload(instruction) {
716
+ try {
717
+ const response = await withTimeout(
718
+ (signal) => fetch(instruction.adiGatewayUrl, {
719
+ method: "GET",
720
+ signal
721
+ })
722
+ );
723
+ if (response.status < 200 || response.status >= 300) {
724
+ return {
725
+ adrBody: null,
726
+ adrStatus: response.status,
727
+ adrSuccess: false,
728
+ adrError: `download_failed_status_${response.status}`
729
+ };
730
+ }
731
+ try {
732
+ const body = await response.json();
733
+ return {
734
+ adrBody: body,
735
+ adrStatus: response.status,
736
+ adrSuccess: true,
737
+ adrError: null
738
+ };
739
+ } catch {
740
+ return {
741
+ adrBody: null,
742
+ adrStatus: response.status,
743
+ adrSuccess: false,
744
+ adrError: "download_invalid_json"
745
+ };
746
+ }
747
+ } catch (error) {
748
+ return {
749
+ adrBody: null,
750
+ adrStatus: 0,
751
+ adrSuccess: false,
752
+ adrError: error instanceof Error ? error.message : String(error)
753
+ };
754
+ }
755
+ }
756
+
757
+ // src/soulSlice.ts
577
758
  var initialState2 = {
578
759
  exportStatus: "idle",
579
760
  importStatus: "idle",
@@ -584,26 +765,45 @@ var initialState2 = {
584
765
  };
585
766
  var remoteExportSoulThunk = (0, import_toolkit2.createAsyncThunk)(
586
767
  "soul/export",
587
- async ({ npcId: argNpcId, apiUrl, apiKey, memories = [] }, { getState, dispatch: dispatch2, rejectWithValue }) => {
768
+ async ({ npcId: argNpcId, apiUrl, apiKey }, { getState, dispatch: dispatch2, rejectWithValue }) => {
588
769
  try {
589
770
  const state = getState().npc;
590
771
  const npcId = argNpcId || state.activeNpcId;
591
772
  const npc = state.entities[npcId];
592
773
  if (!npc) throw new Error(`NPC ${npcId} not found`);
593
774
  const url = apiUrl || "https://api.forboc.ai";
594
- const result = await dispatch2(sdkApi.endpoints.postSoulExport.initiate({
595
- npcId,
596
- request: { npcIdRef: npcId, persona: npc.persona || "NPC", npcState: npc.state },
597
- apiUrl: url,
598
- apiKey
599
- })).unwrap();
775
+ requireApiKeyGuidance(url, apiKey);
776
+ const phase1 = await dispatch2(
777
+ sdkApi.endpoints.postSoulExport.initiate({
778
+ npcId,
779
+ request: { npcIdRef: npcId, persona: npc.persona || "NPC", npcState: npc.state },
780
+ apiUrl: url,
781
+ apiKey
782
+ })
783
+ ).unwrap();
784
+ const uploadResult = await handler_ArweaveUpload({
785
+ ...phase1.se1Instruction,
786
+ auiAuthHeader: phase1.se1Instruction.auiAuthHeader ?? null
787
+ });
788
+ const final = await dispatch2(
789
+ sdkApi.endpoints.postSoulExportConfirm.initiate({
790
+ npcId,
791
+ request: {
792
+ secUploadResult: uploadResult,
793
+ secSignedPayload: phase1.se1SignedPayload,
794
+ secSignature: phase1.se1Signature
795
+ },
796
+ apiUrl: url,
797
+ apiKey
798
+ })
799
+ ).unwrap();
600
800
  return {
601
- txId: result.txId,
602
- url: result.arweaveUrl,
603
- soul: result.soul
801
+ txId: final.txId,
802
+ url: final.arweaveUrl,
803
+ soul: final.soul
604
804
  };
605
- } catch (e) {
606
- return rejectWithValue(e.message || "Soul export failed");
805
+ } catch (error) {
806
+ return rejectWithValue(extractThunkErrorMessage(error, "Soul export failed"));
607
807
  }
608
808
  }
609
809
  );
@@ -612,10 +812,31 @@ var importSoulFromArweaveThunk = (0, import_toolkit2.createAsyncThunk)(
612
812
  async ({ txId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
613
813
  try {
614
814
  const url = apiUrl || "https://api.forboc.ai";
615
- const data = await dispatch2(sdkApi.endpoints.getSoulImport.initiate({ txId, apiUrl: url, apiKey })).unwrap();
616
- return data;
617
- } catch (e) {
618
- return rejectWithValue(e.message || "Soul import failed");
815
+ requireApiKeyGuidance(url, apiKey);
816
+ const phase1 = await dispatch2(
817
+ sdkApi.endpoints.postNpcImport.initiate({ request: { txIdRef: txId }, apiUrl: url, apiKey })
818
+ ).unwrap();
819
+ const downloadResult = await handler_ArweaveDownload(phase1.si1Instruction);
820
+ const npc = await dispatch2(
821
+ sdkApi.endpoints.postNpcImportConfirm.initiate({
822
+ request: {
823
+ sicTxId: txId,
824
+ sicDownloadResult: downloadResult
825
+ },
826
+ apiUrl: url,
827
+ apiKey
828
+ })
829
+ ).unwrap();
830
+ return {
831
+ id: txId,
832
+ version: "2.0.0",
833
+ name: npc.npcId,
834
+ persona: npc.persona,
835
+ memories: [],
836
+ state: npc.data || {}
837
+ };
838
+ } catch (error) {
839
+ return rejectWithValue(extractThunkErrorMessage(error, "Soul import failed"));
619
840
  }
620
841
  }
621
842
  );
@@ -624,10 +845,11 @@ var getSoulListThunk = (0, import_toolkit2.createAsyncThunk)(
624
845
  async ({ limit = 50, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
625
846
  try {
626
847
  const url = apiUrl || "https://api.forboc.ai";
848
+ requireApiKeyGuidance(url, apiKey);
627
849
  const data = await dispatch2(sdkApi.endpoints.getSouls.initiate({ limit, apiUrl: url, apiKey })).unwrap();
628
850
  return data.souls || [];
629
- } catch (e) {
630
- return rejectWithValue(e.message || "Failed to list souls");
851
+ } catch (error) {
852
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list souls"));
631
853
  }
632
854
  }
633
855
  );
@@ -636,9 +858,10 @@ var verifySoulThunk = (0, import_toolkit2.createAsyncThunk)(
636
858
  async ({ txId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
637
859
  try {
638
860
  const url = apiUrl || "https://api.forboc.ai";
861
+ requireApiKeyGuidance(url, apiKey);
639
862
  return await dispatch2(sdkApi.endpoints.postSoulVerify.initiate({ txId, apiUrl: url, apiKey })).unwrap();
640
- } catch (e) {
641
- return rejectWithValue(e.message || "Soul verify failed");
863
+ } catch (error) {
864
+ return rejectWithValue(extractThunkErrorMessage(error, "Soul verify failed"));
642
865
  }
643
866
  }
644
867
  );
@@ -647,13 +870,24 @@ var importNpcFromSoulThunk = (0, import_toolkit2.createAsyncThunk)(
647
870
  async ({ txId, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
648
871
  try {
649
872
  const url = apiUrl || "https://api.forboc.ai";
650
- return await dispatch2(sdkApi.endpoints.postNpcImport.initiate({
651
- request: { txIdRef: txId },
652
- apiUrl: url,
653
- apiKey
654
- })).unwrap();
655
- } catch (e) {
656
- return rejectWithValue(e.message || "NPC import from soul failed");
873
+ requireApiKeyGuidance(url, apiKey);
874
+ const phase1 = await dispatch2(
875
+ sdkApi.endpoints.postNpcImport.initiate({
876
+ request: { txIdRef: txId },
877
+ apiUrl: url,
878
+ apiKey
879
+ })
880
+ ).unwrap();
881
+ const downloadResult = await handler_ArweaveDownload(phase1.si1Instruction);
882
+ return await dispatch2(
883
+ sdkApi.endpoints.postNpcImportConfirm.initiate({
884
+ request: { sicTxId: txId, sicDownloadResult: downloadResult },
885
+ apiUrl: url,
886
+ apiKey
887
+ })
888
+ ).unwrap();
889
+ } catch (error) {
890
+ return rejectWithValue(extractThunkErrorMessage(error, "NPC import from soul failed"));
657
891
  }
658
892
  }
659
893
  );
@@ -712,6 +946,7 @@ var startGhostThunk = (0, import_toolkit3.createAsyncThunk)(
712
946
  async (config, { dispatch: dispatch2, rejectWithValue }) => {
713
947
  try {
714
948
  const apiUrl = config.apiUrl || "https://api.forboc.ai";
949
+ requireApiKeyGuidance(apiUrl, config.apiKey);
715
950
  const data = await dispatch2(sdkApi.endpoints.postGhostRun.initiate({
716
951
  request: { testSuite: config.testSuite, duration: config.duration ?? 300 },
717
952
  apiUrl,
@@ -721,8 +956,8 @@ var startGhostThunk = (0, import_toolkit3.createAsyncThunk)(
721
956
  sessionId: data.sessionId,
722
957
  status: data.runStatus
723
958
  };
724
- } catch (e) {
725
- return rejectWithValue(e.message || "Failed to start Ghost");
959
+ } catch (error) {
960
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to start Ghost"));
726
961
  }
727
962
  }
728
963
  );
@@ -734,6 +969,7 @@ var getGhostStatusThunk = (0, import_toolkit3.createAsyncThunk)(
734
969
  const targetSession = sessionId || state.activeSessionId;
735
970
  if (!targetSession) throw new Error("No active Ghost session");
736
971
  const url = apiUrl || "https://api.forboc.ai";
972
+ requireApiKeyGuidance(url, apiKey);
737
973
  const data = await dispatch2(sdkApi.endpoints.getGhostStatus.initiate({ sessionId: targetSession, apiUrl: url, apiKey })).unwrap();
738
974
  return {
739
975
  sessionId: data.ghostSessionId,
@@ -743,8 +979,8 @@ var getGhostStatusThunk = (0, import_toolkit3.createAsyncThunk)(
743
979
  duration: data.ghostDuration || 0,
744
980
  errors: data.ghostErrors
745
981
  };
746
- } catch (e) {
747
- return rejectWithValue(e.message || "Failed to get ghost status");
982
+ } catch (error) {
983
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to get ghost status"));
748
984
  }
749
985
  }
750
986
  );
@@ -756,6 +992,7 @@ var getGhostResultsThunk = (0, import_toolkit3.createAsyncThunk)(
756
992
  const targetSession = sessionId || state.activeSessionId;
757
993
  if (!targetSession) throw new Error("No active Ghost session");
758
994
  const url = apiUrl || "https://api.forboc.ai";
995
+ requireApiKeyGuidance(url, apiKey);
759
996
  const data = await dispatch2(sdkApi.endpoints.getGhostResults.initiate({ sessionId: targetSession, apiUrl: url, apiKey })).unwrap();
760
997
  return {
761
998
  sessionId: data.resultsSessionId,
@@ -774,8 +1011,8 @@ var getGhostResultsThunk = (0, import_toolkit3.createAsyncThunk)(
774
1011
  coverage: data.resultsCoverage,
775
1012
  metrics: Object.fromEntries(data.resultsMetrics || [])
776
1013
  };
777
- } catch (e) {
778
- return rejectWithValue(e.message || "Failed to get ghost results");
1014
+ } catch (error) {
1015
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to get ghost results"));
779
1016
  }
780
1017
  }
781
1018
  );
@@ -787,14 +1024,15 @@ var stopGhostThunk = (0, import_toolkit3.createAsyncThunk)(
787
1024
  const targetSession = sessionId || state.activeSessionId;
788
1025
  if (!targetSession) throw new Error("No active Ghost session");
789
1026
  const url = apiUrl || "https://api.forboc.ai";
1027
+ requireApiKeyGuidance(url, apiKey);
790
1028
  const data = await dispatch2(sdkApi.endpoints.postGhostStop.initiate({ sessionId: targetSession, apiUrl: url, apiKey })).unwrap();
791
1029
  return {
792
1030
  stopped: data.stopped,
793
1031
  status: data.stopStatus,
794
1032
  sessionId: data.stopSessionId
795
1033
  };
796
- } catch (e) {
797
- return rejectWithValue(e.message || "Failed to stop ghost session");
1034
+ } catch (error) {
1035
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to stop ghost session"));
798
1036
  }
799
1037
  }
800
1038
  );
@@ -803,6 +1041,7 @@ var getGhostHistoryThunk = (0, import_toolkit3.createAsyncThunk)(
803
1041
  async ({ limit = 10, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
804
1042
  try {
805
1043
  const url = apiUrl || "https://api.forboc.ai";
1044
+ requireApiKeyGuidance(url, apiKey);
806
1045
  const data = await dispatch2(sdkApi.endpoints.getGhostHistory.initiate({ limit, apiUrl: url, apiKey })).unwrap();
807
1046
  return (data.sessions || []).map((s) => ({
808
1047
  sessionId: s.sessionId,
@@ -812,8 +1051,8 @@ var getGhostHistoryThunk = (0, import_toolkit3.createAsyncThunk)(
812
1051
  status: s.status,
813
1052
  passRate: s.passRate
814
1053
  }));
815
- } catch (e) {
816
- return rejectWithValue(e.message || "Failed to get ghost history");
1054
+ } catch (error) {
1055
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to get ghost history"));
817
1056
  }
818
1057
  }
819
1058
  );
@@ -848,7 +1087,7 @@ var ghostSlice = (0, import_toolkit3.createSlice)({
848
1087
  state.status = "completed";
849
1088
  }).addCase(stopGhostThunk.fulfilled, (state, action) => {
850
1089
  if (action.payload.stopped) {
851
- state.status = "failed";
1090
+ state.status = "completed";
852
1091
  } else {
853
1092
  state.error = action.payload.status || "Ghost stop request did not stop a session";
854
1093
  }
@@ -863,7 +1102,7 @@ var { clearGhostSession } = ghostSlice.actions;
863
1102
  var ghostSlice_default = ghostSlice.reducer;
864
1103
 
865
1104
  // src/utils/sdkVersion.ts
866
- var SDK_VERSION = "0.6.0";
1105
+ var SDK_VERSION = "0.6.1";
867
1106
 
868
1107
  // src/utils/generateNPCId.ts
869
1108
  var generateNPCId = () => `ag_${Date.now().toString(36)}`;
@@ -944,7 +1183,8 @@ var npcSlice = (0, import_toolkit4.createSlice)({
944
1183
  persona,
945
1184
  state: initialState5 || {},
946
1185
  history: [],
947
- isBlocked: false
1186
+ isBlocked: false,
1187
+ stateLog: [{ timestamp: Date.now(), delta: initialState5 || {}, state: initialState5 || {} }]
948
1188
  });
949
1189
  state.activeNpcId = id;
950
1190
  },
@@ -959,7 +1199,9 @@ var npcSlice = (0, import_toolkit4.createSlice)({
959
1199
  const { id, delta } = action.payload;
960
1200
  const npc = state.entities[id];
961
1201
  if (npc) {
962
- npc.state = { ...npc.state, ...delta };
1202
+ const newState = { ...npc.state, ...delta };
1203
+ npc.state = newState;
1204
+ npc.stateLog.push({ timestamp: Date.now(), delta, state: newState });
963
1205
  }
964
1206
  },
965
1207
  addToHistory: (state, action) => {
@@ -1029,6 +1271,7 @@ var initRemoteCortexThunk = (0, import_toolkit5.createAsyncThunk)(
1029
1271
  async ({ model = "api-integrated", authKey, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
1030
1272
  try {
1031
1273
  const url = apiUrl || "https://api.forboc.ai";
1274
+ requireApiKeyGuidance(url, apiKey);
1032
1275
  const data = await dispatch2(sdkApi.endpoints.postCortexInit.initiate({
1033
1276
  request: { requestedModel: model, authKey },
1034
1277
  apiUrl: url,
@@ -1040,8 +1283,8 @@ var initRemoteCortexThunk = (0, import_toolkit5.createAsyncThunk)(
1040
1283
  ready: data.state?.toLowerCase() === "ready",
1041
1284
  engine: "remote"
1042
1285
  };
1043
- } catch (e) {
1044
- return rejectWithValue(e.message || "Remote cortex init failed");
1286
+ } catch (error) {
1287
+ return rejectWithValue(extractThunkErrorMessage(error, "Remote cortex init failed"));
1045
1288
  }
1046
1289
  }
1047
1290
  );
@@ -1050,9 +1293,10 @@ var listCortexModelsThunk = (0, import_toolkit5.createAsyncThunk)(
1050
1293
  async ({ apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
1051
1294
  try {
1052
1295
  const url = apiUrl || "https://api.forboc.ai";
1296
+ requireApiKeyGuidance(url, apiKey);
1053
1297
  return await dispatch2(sdkApi.endpoints.getCortexModels.initiate({ apiUrl: url, apiKey })).unwrap();
1054
- } catch (e) {
1055
- return rejectWithValue(e.message || "Failed to list cortex models");
1298
+ } catch (error) {
1299
+ return rejectWithValue(extractThunkErrorMessage(error, "Failed to list cortex models"));
1056
1300
  }
1057
1301
  }
1058
1302
  );
@@ -1060,6 +1304,7 @@ var completeRemoteThunk = (0, import_toolkit5.createAsyncThunk)(
1060
1304
  "cortex/completeRemote",
1061
1305
  async ({ cortexId, prompt, options, apiUrl, apiKey }, { dispatch: dispatch2, rejectWithValue }) => {
1062
1306
  try {
1307
+ requireApiKeyGuidance(apiUrl, apiKey);
1063
1308
  const data = await dispatch2(sdkApi.endpoints.postCortexComplete.initiate({
1064
1309
  cortexId,
1065
1310
  prompt,
@@ -1068,8 +1313,8 @@ var completeRemoteThunk = (0, import_toolkit5.createAsyncThunk)(
1068
1313
  apiKey
1069
1314
  })).unwrap();
1070
1315
  return data.text;
1071
- } catch (e) {
1072
- return rejectWithValue(e.message || "Remote completing failed");
1316
+ } catch (error) {
1317
+ return rejectWithValue(extractThunkErrorMessage(error, "Remote completing failed"));
1073
1318
  }
1074
1319
  }
1075
1320
  );
@@ -1304,6 +1549,13 @@ var dispatch = store.dispatch;
1304
1549
 
1305
1550
  // src/thunks.ts
1306
1551
  var import_toolkit10 = require("@reduxjs/toolkit");
1552
+ var extractThunkErrorMessage2 = (e) => {
1553
+ if (typeof e === "string") return e;
1554
+ if (e?.data?.message) return String(e.data.message);
1555
+ if (e?.error) return String(e.error);
1556
+ if (e?.message) return String(e.message);
1557
+ return "Protocol processing failed";
1558
+ };
1307
1559
  var processNPC = (0, import_toolkit10.createAsyncThunk)(
1308
1560
  "npc/process",
1309
1561
  async ({ npcId: argNpcId, text, context = {}, apiUrl, apiKey, memory, cortex, persona: argPersona }, { getState, dispatch: dispatch2, rejectWithValue }) => {
@@ -1327,61 +1579,111 @@ var processNPC = (0, import_toolkit10.createAsyncThunk)(
1327
1579
  const directiveId = `${npcId}:${Date.now()}`;
1328
1580
  dispatch2(directiveRunStarted({ id: directiveId, npcId, observation: text }));
1329
1581
  try {
1330
- const directiveResult = await dispatch2(
1331
- sdkApi.endpoints.postDirective.initiate({ npcId, request: { observation: text, npcState: currentState, context }, apiUrl, apiKey })
1332
- ).unwrap();
1333
- dispatch2(directiveReceived({ id: directiveId, response: directiveResult }));
1334
- if (directiveResult.memoryRecall && !memory) {
1335
- return rejectWithValue("API requested memory recall, but no memory engine is configured");
1336
- }
1337
- const recalledMemories = memory && directiveResult.memoryRecall ? (await memory.recall(
1338
- directiveResult.memoryRecall.query,
1339
- directiveResult.memoryRecall.limit,
1340
- directiveResult.memoryRecall.threshold
1341
- )).map((m) => ({ text: m.text, type: m.type, importance: m.importance })) : [];
1342
- const contextResult = await dispatch2(
1343
- sdkApi.endpoints.postContext.initiate({ npcId, request: { memories: recalledMemories, observation: text, npcState: currentState, persona }, apiUrl, apiKey })
1344
- ).unwrap();
1345
- dispatch2(contextComposed({ id: directiveId, prompt: contextResult.prompt, constraints: contextResult.constraints }));
1346
- const generatedText = await cortex.complete(contextResult.prompt, {
1347
- maxTokens: contextResult.constraints.maxTokens,
1348
- temperature: contextResult.constraints.temperature,
1349
- stop: contextResult.constraints.stop
1350
- });
1351
- const verdictResult = await dispatch2(
1352
- sdkApi.endpoints.postVerdict.initiate({ npcId, request: { generatedOutput: generatedText, observation: text, npcState: currentState }, apiUrl, apiKey })
1353
- ).unwrap();
1354
- dispatch2(verdictValidated({ id: directiveId, verdict: verdictResult }));
1355
- if (!verdictResult.valid) {
1356
- dispatch2(blockAction({ id: npcId, reason: verdictResult.dialogue || "Validation Failed" }));
1357
- return {
1358
- dialogue: verdictResult.dialogue,
1359
- action: verdictResult.action,
1360
- thought: verdictResult.dialogue
1361
- };
1362
- }
1363
- if (verdictResult.memoryStore?.length && !memory) {
1364
- return rejectWithValue("API returned memoryStore instructions, but no memory engine is configured");
1365
- }
1366
- if (memory && verdictResult.memoryStore) {
1367
- for (const inst of verdictResult.memoryStore) {
1368
- await memory.store(inst.text, inst.type, inst.importance);
1582
+ const initialTape = {
1583
+ observation: text,
1584
+ context,
1585
+ npcState: currentState,
1586
+ persona,
1587
+ memories: [],
1588
+ vectorQueried: false
1589
+ };
1590
+ const maxTurns = 12;
1591
+ const persistMemoryInstructionsRecursively = async (instructions, index = 0) => {
1592
+ if (!memory || index >= instructions.length) {
1593
+ return;
1369
1594
  }
1370
- }
1371
- if (verdictResult.stateDelta) {
1372
- dispatch2(updateNPCState({ id: npcId, delta: verdictResult.stateDelta }));
1373
- }
1374
- const action = verdictResult.action;
1375
- dispatch2(setLastAction({ id: npcId, action }));
1376
- dispatch2(addToHistory({ id: npcId, role: "user", content: text }));
1377
- dispatch2(addToHistory({ id: npcId, role: "assistant", content: verdictResult.dialogue }));
1378
- return {
1379
- dialogue: verdictResult.dialogue,
1380
- action,
1381
- thought: verdictResult.dialogue
1595
+ const inst = instructions[index];
1596
+ await memory.store(inst.text, inst.type, inst.importance);
1597
+ await persistMemoryInstructionsRecursively(instructions, index + 1);
1598
+ };
1599
+ const runProtocolRecursively = async (tape, lastResult, turn) => {
1600
+ if (turn >= maxTurns) {
1601
+ return rejectWithValue(`Protocol loop exceeded max turns (${maxTurns})`);
1602
+ }
1603
+ const request = { tape, lastResult };
1604
+ const processResult = await dispatch2(
1605
+ sdkApi.endpoints.postNpcProcess.initiate({ npcId, request, apiUrl, apiKey })
1606
+ ).unwrap();
1607
+ const nextTape = processResult.tape;
1608
+ const instruction = processResult.instruction;
1609
+ if (instruction.type === "IdentifyActor") {
1610
+ return runProtocolRecursively(nextTape, {
1611
+ type: "IdentifyActorResult",
1612
+ actor: {
1613
+ npcId,
1614
+ persona,
1615
+ data: nextTape.npcState
1616
+ }
1617
+ }, turn + 1);
1618
+ }
1619
+ if (instruction.type === "QueryVector") {
1620
+ dispatch2(directiveReceived({
1621
+ id: directiveId,
1622
+ response: { memoryRecall: { query: instruction.query, limit: instruction.limit, threshold: instruction.threshold } }
1623
+ }));
1624
+ if (!memory) {
1625
+ return rejectWithValue("API requested memory recall, but no memory engine is configured");
1626
+ }
1627
+ const recalled = await memory.recall(instruction.query, instruction.limit, instruction.threshold);
1628
+ return runProtocolRecursively(nextTape, {
1629
+ type: "QueryVectorResult",
1630
+ memories: recalled.map((m) => ({ text: m.text, type: m.type, importance: m.importance, similarity: m.similarity }))
1631
+ }, turn + 1);
1632
+ }
1633
+ if (instruction.type === "ExecuteInference") {
1634
+ dispatch2(contextComposed({ id: directiveId, prompt: instruction.prompt, constraints: instruction.constraints }));
1635
+ const generatedText = await cortex.complete(instruction.prompt, {
1636
+ maxTokens: instruction.constraints.maxTokens,
1637
+ temperature: instruction.constraints.temperature,
1638
+ stop: instruction.constraints.stop
1639
+ });
1640
+ return runProtocolRecursively(nextTape, {
1641
+ type: "ExecuteInferenceResult",
1642
+ generatedOutput: generatedText
1643
+ }, turn + 1);
1644
+ }
1645
+ if (instruction.type === "Finalize") {
1646
+ const finalize = instruction;
1647
+ dispatch2(verdictValidated({
1648
+ id: directiveId,
1649
+ verdict: {
1650
+ valid: finalize.valid,
1651
+ signature: finalize.signature,
1652
+ memoryStore: finalize.memoryStore,
1653
+ stateDelta: finalize.stateTransform,
1654
+ action: finalize.action,
1655
+ dialogue: finalize.dialogue
1656
+ }
1657
+ }));
1658
+ if (!finalize.valid) {
1659
+ dispatch2(blockAction({ id: npcId, reason: finalize.dialogue || "Validation Failed" }));
1660
+ return {
1661
+ dialogue: finalize.dialogue,
1662
+ action: finalize.action,
1663
+ thought: finalize.dialogue
1664
+ };
1665
+ }
1666
+ if (finalize.memoryStore?.length && !memory) {
1667
+ return rejectWithValue("API returned memoryStore instructions, but no memory engine is configured");
1668
+ }
1669
+ await persistMemoryInstructionsRecursively(finalize.memoryStore || []);
1670
+ if (finalize.stateTransform) {
1671
+ dispatch2(updateNPCState({ id: npcId, delta: finalize.stateTransform }));
1672
+ }
1673
+ dispatch2(setLastAction({ id: npcId, action: finalize.action }));
1674
+ dispatch2(addToHistory({ id: npcId, role: "user", content: text }));
1675
+ dispatch2(addToHistory({ id: npcId, role: "assistant", content: finalize.dialogue }));
1676
+ return {
1677
+ dialogue: finalize.dialogue,
1678
+ action: finalize.action,
1679
+ thought: finalize.dialogue
1680
+ };
1681
+ }
1682
+ return rejectWithValue("API returned unknown instruction type");
1382
1683
  };
1684
+ return runProtocolRecursively(initialTape, void 0, 0);
1383
1685
  } catch (e) {
1384
- const message = e?.message || e?.data?.message || "Protocol processing failed";
1686
+ const message = extractThunkErrorMessage2(e);
1385
1687
  dispatch2(directiveRunFailed({ id: directiveId, error: String(message) }));
1386
1688
  return rejectWithValue(String(message));
1387
1689
  }
@@ -1502,6 +1804,8 @@ var clearMemoryRemoteThunk = (0, import_toolkit10.createAsyncThunk)(
1502
1804
  getGhostStatusThunk,
1503
1805
  getSoulListThunk,
1504
1806
  ghostSlice,
1807
+ handler_ArweaveDownload,
1808
+ handler_ArweaveUpload,
1505
1809
  importNpcFromSoulThunk,
1506
1810
  importSoulFromArweaveThunk,
1507
1811
  initRemoteCortexThunk,