@athenaintel/react 0.32.0 → 0.36.0
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/README.md +148 -9
- package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -1
- package/dist/agent-runtime/src/composer-markdown.js.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +2 -3
- package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js +3 -4
- package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs +4 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/agent-runtime/src/generated/runconfig-contract.js +4 -0
- package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/agent-runtime/src/messages.cjs +57 -2
- package/dist/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/agent-runtime/src/messages.js +57 -2
- package/dist/agent-runtime/src/messages.js.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +24 -3
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js +24 -3
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/agent-runtime/src/statewire-transport.cjs +4 -13
- package/dist/agent-runtime/src/statewire-transport.cjs.map +1 -1
- package/dist/agent-runtime/src/statewire-transport.js +5 -14
- package/dist/agent-runtime/src/statewire-transport.js.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.cjs +12 -1
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +12 -1
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +3 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +7 -0
- package/dist/assets/AthenaAssetEmbed.js +4 -2
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs +71 -71
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +20 -7
- package/dist/assets/use-asset-embed.js +72 -72
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/catalog/agent-mode-menu.cjs +115 -0
- package/dist/catalog/agent-mode-menu.cjs.map +1 -0
- package/dist/catalog/agent-mode-menu.d.ts +101 -0
- package/dist/catalog/agent-mode-menu.js +115 -0
- package/dist/catalog/agent-mode-menu.js.map +1 -0
- package/dist/catalog/agent-model-options.cjs +122 -0
- package/dist/catalog/agent-model-options.cjs.map +1 -0
- package/dist/catalog/agent-model-options.d.ts +115 -0
- package/dist/catalog/agent-model-options.js +122 -0
- package/dist/catalog/agent-model-options.js.map +1 -0
- package/dist/catalog/chat-pickers.cjs +235 -4
- package/dist/catalog/chat-pickers.cjs.map +1 -1
- package/dist/catalog/chat-pickers.d.ts +55 -3
- package/dist/catalog/chat-pickers.js +238 -7
- package/dist/catalog/chat-pickers.js.map +1 -1
- package/dist/catalog/runtime-catalog.cjs +121 -3
- package/dist/catalog/runtime-catalog.cjs.map +1 -1
- package/dist/catalog/runtime-catalog.d.ts +120 -0
- package/dist/catalog/runtime-catalog.js +124 -6
- package/dist/catalog/runtime-catalog.js.map +1 -1
- package/dist/catalog/use-athena-chat-mode.cjs +139 -0
- package/dist/catalog/use-athena-chat-mode.cjs.map +1 -0
- package/dist/catalog/use-athena-chat-mode.d.ts +43 -0
- package/dist/catalog/use-athena-chat-mode.js +139 -0
- package/dist/catalog/use-athena-chat-mode.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +2 -0
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +8 -7
- package/dist/chat/AthenaChat.js +2 -0
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +30 -3
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.d.ts +12 -9
- package/dist/chat/super-grouping/SuperGroupingCard.js +31 -4
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.d.ts +8 -8
- package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +4 -4
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/super-grouping/index.d.ts +6 -5
- package/dist/chat/super-grouping/toolkit-map.cjs +3 -1
- package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
- package/dist/chat/super-grouping/toolkit-map.js +3 -1
- package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
- package/dist/chat-ui/index.cjs +1 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -1
- package/dist/chat-ui/index.js +2 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +33 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +14 -0
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +33 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/statewire.cjs +3 -0
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.d.ts +4 -2
- package/dist/chat-ui/statewire.js +3 -0
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/use-athena-rest-query.cjs +4 -0
- package/dist/lib/use-athena-rest-query.cjs.map +1 -1
- package/dist/lib/use-athena-rest-query.d.ts +6 -0
- package/dist/lib/use-athena-rest-query.js +4 -0
- package/dist/lib/use-athena-rest-query.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +81 -9
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +25 -5
- package/dist/provider/AthenaProvider.js +83 -11
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/agent-selection-context.cjs +6 -0
- package/dist/provider/agent-selection-context.cjs.map +1 -0
- package/dist/provider/agent-selection-context.d.ts +14 -0
- package/dist/provider/agent-selection-context.js +6 -0
- package/dist/provider/agent-selection-context.js.map +1 -0
- package/dist/provider/chat-mode-context.cjs +6 -0
- package/dist/provider/chat-mode-context.cjs.map +1 -0
- package/dist/provider/chat-mode-context.d.ts +35 -0
- package/dist/provider/chat-mode-context.js +6 -0
- package/dist/provider/chat-mode-context.js.map +1 -0
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/runtime/StatewireThreadConfigLaneSync.cjs +125 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.cjs.map +1 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.d.ts +54 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.js +125 -0
- package/dist/runtime/StatewireThreadConfigLaneSync.js.map +1 -0
- package/dist/runtime/statewire-model-lane.cjs +0 -12
- package/dist/runtime/statewire-model-lane.cjs.map +1 -1
- package/dist/runtime/statewire-model-lane.d.ts +3 -1
- package/dist/runtime/statewire-model-lane.js +0 -12
- package/dist/runtime/statewire-model-lane.js.map +1 -1
- package/dist/runtime/statewire-run-config.cjs +10 -6
- package/dist/runtime/statewire-run-config.cjs.map +1 -1
- package/dist/runtime/statewire-run-config.d.ts +7 -0
- package/dist/runtime/statewire-run-config.js +10 -6
- package/dist/runtime/statewire-run-config.js.map +1 -1
- package/dist/runtime/statewire-server-lane-context.cjs.map +1 -1
- package/dist/runtime/statewire-server-lane-context.d.ts +2 -2
- package/dist/runtime/statewire-server-lane-context.js.map +1 -1
- package/dist/runtime/statewire-thread-config-lane.cjs +84 -0
- package/dist/runtime/statewire-thread-config-lane.cjs.map +1 -0
- package/dist/runtime/statewire-thread-config-lane.d.ts +94 -0
- package/dist/runtime/statewire-thread-config-lane.js +84 -0
- package/dist/runtime/statewire-thread-config-lane.js.map +1 -0
- package/dist/runtime/useAthenaStatewireRuntime.cjs +4 -2
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.d.ts +2 -0
- package/dist/runtime/useAthenaStatewireRuntime.js +4 -2
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/threads/use-athena-thread-model.cjs +11 -3
- package/dist/threads/use-athena-thread-model.cjs.map +1 -1
- package/dist/threads/use-athena-thread-model.d.ts +5 -0
- package/dist/threads/use-athena-thread-model.js +12 -4
- package/dist/threads/use-athena-thread-model.js.map +1 -1
- package/package.json +3 -3
- package/dist/runtime/StatewireModelLaneSync.cjs +0 -96
- package/dist/runtime/StatewireModelLaneSync.cjs.map +0 -1
- package/dist/runtime/StatewireModelLaneSync.d.ts +0 -45
- package/dist/runtime/StatewireModelLaneSync.js +0 -96
- package/dist/runtime/StatewireModelLaneSync.js.map +0 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const reactStatewire = require("@assistant-ui/react-statewire");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
|
|
6
|
+
const statewireThreadConfigLane = require("./statewire-thread-config-lane.cjs");
|
|
7
|
+
const useDeepAgentConfig = require("../agent-runtime/src/use-deep-agent-config.cjs");
|
|
8
|
+
const errors = require("../agent-runtime/src/errors.cjs");
|
|
9
|
+
const MAX_PATCH_ATTEMPTS_PER_CHANGE = 3;
|
|
10
|
+
const RETRY_BACKOFF_MS = [1e3, 4e3];
|
|
11
|
+
function configWriteData({
|
|
12
|
+
threadId,
|
|
13
|
+
patch
|
|
14
|
+
}) {
|
|
15
|
+
const otherKeys = statewireThreadConfigLane.patchedThreadConfigKeys(patch).filter((key) => key !== "model");
|
|
16
|
+
return {
|
|
17
|
+
thread_id: threadId,
|
|
18
|
+
...Object.hasOwn(patch, "model") && { model: patch.model },
|
|
19
|
+
...otherKeys.length > 0 && { config_keys: otherKeys }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function StatewireThreadConfigLaneSync({
|
|
23
|
+
threadId,
|
|
24
|
+
config,
|
|
25
|
+
lane,
|
|
26
|
+
channelLive
|
|
27
|
+
}) {
|
|
28
|
+
const send = reactStatewire.useStatewireSendCommand();
|
|
29
|
+
const sendCommand = (command) => Promise.resolve(send(command));
|
|
30
|
+
const { configVersion, custom, patchConfig } = useDeepAgentConfig.useDeepAgentConfigController({
|
|
31
|
+
serverConfig: lane.config,
|
|
32
|
+
sendCommand
|
|
33
|
+
});
|
|
34
|
+
const seenRef = react.useRef(config);
|
|
35
|
+
const pendingRef = react.useRef(/* @__PURE__ */ new Set());
|
|
36
|
+
const attemptsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
37
|
+
const closedRef = react.useRef(false);
|
|
38
|
+
const patchConfigRef = react.useRef(patchConfig);
|
|
39
|
+
patchConfigRef.current = patchConfig;
|
|
40
|
+
const [retryTick, setRetryTick] = react.useState(0);
|
|
41
|
+
const retryTimerRef = react.useRef(null);
|
|
42
|
+
react.useEffect(
|
|
43
|
+
() => () => {
|
|
44
|
+
if (retryTimerRef.current !== null) clearTimeout(retryTimerRef.current);
|
|
45
|
+
},
|
|
46
|
+
[]
|
|
47
|
+
);
|
|
48
|
+
const landed = lane.landed;
|
|
49
|
+
react.useEffect(() => {
|
|
50
|
+
const pending = pendingRef.current;
|
|
51
|
+
const attempts = attemptsRef.current;
|
|
52
|
+
for (const key of statewireThreadConfigLane.PROVIDER_THREAD_CONFIG_KEYS) {
|
|
53
|
+
if (statewireThreadConfigLane.sameThreadConfigValue(config[key], seenRef.current[key])) continue;
|
|
54
|
+
pending.add(key);
|
|
55
|
+
attempts.set(key, 0);
|
|
56
|
+
}
|
|
57
|
+
seenRef.current = config;
|
|
58
|
+
if (pending.size === 0 || closedRef.current || !channelLive) return;
|
|
59
|
+
const decision = statewireThreadConfigLane.decideThreadConfigLanePatch({
|
|
60
|
+
desired: config,
|
|
61
|
+
keys: pending,
|
|
62
|
+
landed,
|
|
63
|
+
configVersion,
|
|
64
|
+
custom
|
|
65
|
+
});
|
|
66
|
+
if (decision.kind === "wait") return;
|
|
67
|
+
pending.clear();
|
|
68
|
+
if (decision.kind === "done") return;
|
|
69
|
+
const { patch } = decision;
|
|
70
|
+
const patchKeys = statewireThreadConfigLane.patchedThreadConfigKeys(patch);
|
|
71
|
+
for (const key of patchKeys) attempts.set(key, (attempts.get(key) ?? 0) + 1);
|
|
72
|
+
const modelOnly = patchKeys.length === 1 && patchKeys[0] === "model";
|
|
73
|
+
patchConfigRef.current(patch).catch((error) => {
|
|
74
|
+
const data = configWriteData({ threadId, patch });
|
|
75
|
+
if (useDeepAgentConfig.isConfigWriteForbiddenRejection(error) || useDeepAgentConfig.isConfigSyncUnavailableRejection(error)) {
|
|
76
|
+
closedRef.current = true;
|
|
77
|
+
athenaDiagnostics.athenaDiagnostics.emit("statewire.config", {
|
|
78
|
+
level: "warn",
|
|
79
|
+
data: {
|
|
80
|
+
...data,
|
|
81
|
+
reason: useDeepAgentConfig.isConfigWriteForbiddenRejection(error) ? "config_write_forbidden" : "config_sync_unavailable"
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const outcome = errors.statewireTransportOutcome(error);
|
|
87
|
+
if (outcome !== null) {
|
|
88
|
+
athenaDiagnostics.athenaDiagnostics.emit("statewire.config", {
|
|
89
|
+
level: "debug",
|
|
90
|
+
data: { ...data, outcome }
|
|
91
|
+
});
|
|
92
|
+
if (outcome === "unmount") return;
|
|
93
|
+
const retryKeys = patchKeys.filter(
|
|
94
|
+
(key) => statewireThreadConfigLane.sameThreadConfigValue(seenRef.current[key], patch[key] ?? null) && (attempts.get(key) ?? 0) < MAX_PATCH_ATTEMPTS_PER_CHANGE
|
|
95
|
+
);
|
|
96
|
+
if (retryKeys.length === 0) return;
|
|
97
|
+
for (const key of retryKeys) pendingRef.current.add(key);
|
|
98
|
+
const written = Math.max(...retryKeys.map((key) => attempts.get(key) ?? 1));
|
|
99
|
+
const delay = RETRY_BACKOFF_MS[Math.min(written - 1, RETRY_BACKOFF_MS.length - 1)];
|
|
100
|
+
retryTimerRef.current = setTimeout(() => {
|
|
101
|
+
retryTimerRef.current = null;
|
|
102
|
+
setRetryTick((tick) => tick + 1);
|
|
103
|
+
}, delay);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
athenaDiagnostics.athenaDiagnostics.error(
|
|
107
|
+
"statewire.config",
|
|
108
|
+
error,
|
|
109
|
+
modelOnly ? {
|
|
110
|
+
code: "statewire_rejected",
|
|
111
|
+
message: "The statewire host refused the model change",
|
|
112
|
+
hint: "Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the model its server config holds."
|
|
113
|
+
} : {
|
|
114
|
+
code: "statewire_rejected",
|
|
115
|
+
message: "The statewire host refused the thread config change",
|
|
116
|
+
hint: "Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the config its server config holds."
|
|
117
|
+
},
|
|
118
|
+
data
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
}, [config, landed, configVersion, custom, channelLive, retryTick, threadId]);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
exports.StatewireThreadConfigLaneSync = StatewireThreadConfigLaneSync;
|
|
125
|
+
//# sourceMappingURL=StatewireThreadConfigLaneSync.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatewireThreadConfigLaneSync.cjs","sources":["../../src/runtime/StatewireThreadConfigLaneSync.tsx"],"sourcesContent":["import { useStatewireSendCommand } from '@assistant-ui/react-statewire';\nimport {\n type DeepAgentConfigControllerInputs,\n isConfigSyncUnavailableRejection,\n isConfigWriteForbiddenRejection,\n statewireTransportOutcome,\n useDeepAgentConfigController,\n} from '@athenaintel/agent-runtime';\nimport { useEffect, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport type { AthenaStatewireServerLane } from './statewire-server-lane-context';\nimport {\n decideThreadConfigLanePatch,\n PROVIDER_THREAD_CONFIG_KEYS,\n type ProviderThreadConfig,\n type ProviderThreadConfigKey,\n type ProviderThreadConfigPatch,\n patchedThreadConfigKeys,\n sameThreadConfigValue,\n} from './statewire-thread-config-lane';\n\n/** Writes of one change, the first included, before the sync gives up on it. */\nconst MAX_PATCH_ATTEMPTS_PER_CHANGE = 3;\n\n/** Wait before re-writing a change the channel dropped: 1 s, then 4 s. */\nconst RETRY_BACKOFF_MS = [1_000, 4_000] as const;\n\n/**\n * What a `statewire.config` event says about a write: the thread, the model it\n * set (`null`: unset), and the other provider-owned keys it set\n * (`config_keys`), never their values (a system prompt is the host's text).\n */\nfunction configWriteData({\n threadId,\n patch,\n}: {\n threadId: string;\n patch: ProviderThreadConfigPatch;\n}): Record<string, unknown> {\n const otherKeys = patchedThreadConfigKeys(patch).filter((key) => key !== 'model');\n return {\n thread_id: threadId,\n ...(Object.hasOwn(patch, 'model') && { model: patch.model }),\n ...(otherKeys.length > 0 && { config_keys: otherKeys }),\n };\n}\n\n/**\n * Writes the provider-owned thread-config keys to the thread's server config\n * lane when the host changes the props that feed them during an attach:\n * `model`, `systemPrompt` (`system_prompt`), `tools` and `frontendTools`\n * (`enabled_tools`), `workbench` and `knowledgeBase` (`knowledge_base`).\n *\n * Once a thread has a lane (version > 0), agora composes every run from the\n * lane and drops the thread-config keys a send carries, so a model, prompt,\n * tool list or asset list changed after the thread's first turn would never\n * apply. A change of such a prop therefore becomes one `config/patch`\n * carrying exactly the changed keys, each with the value the send would carry\n * (`null` when the send carries none: the key is unset, so the collab agent's\n * own value, or the default, applies). An empty tool or asset list is `null`,\n * as the send omits it.\n *\n * Only a CHANGE is written. A prop's value when the attach starts is never\n * written, even when the lane holds another value: that value is another\n * client's (or an earlier session's) choice for this thread, and an SDK app's\n * global setting must not overwrite it just by opening the thread. Hosts that\n * want the picker to follow the thread read `useAthenaThreadModel`.\n *\n * Decisions read the lane the host itself sent on this attach (`lane`, from\n * the transport), never the rendered thread state: before the attach snapshot\n * lands that state is a placeholder without a lane, or a remembered snapshot\n * with an older one, and comparing a change against either would write over\n * the session's real config. A change made before the snapshot lands waits\n * for it.\n *\n * A version-0 thread needs no write: the next send carries the values and\n * agora's deferral seed persists them. A change stays pending until the lane\n * exists, so a lane an earlier send seeds with the previous values is\n * corrected when it lands. A refusal that can never succeed (a view-only\n * collaborator, a workspace without server config) stops the sync for the\n * attach and is reported on the diagnostics bus. A write the channel dropped\n * (a lost stream, the host's expired verdict) stays pending and is written\n * again after a backoff once the channel is live, at most\n * {@link MAX_PATCH_ATTEMPTS_PER_CHANGE} times per change.\n *\n * The writes go through the `config/*` command family, which agora gates on\n * EDIT on the thread's session asset (`_authorize_config_write`).\n *\n * Mounted under the runtime's `AuiProvider`, keyed by the thread, so each\n * attach starts with nothing pending.\n */\nexport function StatewireThreadConfigLaneSync({\n threadId,\n config,\n lane,\n channelLive,\n}: {\n threadId: string;\n /** The values this thread's sends carry, published with the thread (see the provider). */\n config: ProviderThreadConfig;\n lane: AthenaStatewireServerLane;\n /** Whether the channel can take a command now; writes wait for it. */\n channelLive: boolean;\n}): null {\n const send = useStatewireSendCommand();\n const sendCommand: DeepAgentConfigControllerInputs['sendCommand'] = (command) =>\n Promise.resolve(send(command));\n const { configVersion, custom, patchConfig } = useDeepAgentConfigController({\n serverConfig: lane.config,\n sendCommand,\n });\n\n const seenRef = useRef(config);\n const pendingRef = useRef(new Set<ProviderThreadConfigKey>());\n const attemptsRef = useRef(new Map<ProviderThreadConfigKey, number>());\n const closedRef = useRef(false);\n const patchConfigRef = useRef(patchConfig);\n patchConfigRef.current = patchConfig;\n // Bumped (after a backoff) to re-run the effect for a write the channel dropped.\n const [retryTick, setRetryTick] = useState(0);\n const retryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (retryTimerRef.current !== null) clearTimeout(retryTimerRef.current);\n },\n [],\n );\n const landed = lane.landed;\n\n useEffect(() => {\n const pending = pendingRef.current;\n const attempts = attemptsRef.current;\n for (const key of PROVIDER_THREAD_CONFIG_KEYS) {\n if (sameThreadConfigValue(config[key], seenRef.current[key])) continue;\n pending.add(key);\n attempts.set(key, 0);\n }\n seenRef.current = config;\n if (pending.size === 0 || closedRef.current || !channelLive) return;\n const decision = decideThreadConfigLanePatch({\n desired: config,\n keys: pending,\n landed,\n configVersion,\n custom,\n });\n if (decision.kind === 'wait') return;\n pending.clear();\n if (decision.kind === 'done') return;\n\n const { patch } = decision;\n const patchKeys = patchedThreadConfigKeys(patch);\n for (const key of patchKeys) attempts.set(key, (attempts.get(key) ?? 0) + 1);\n const modelOnly = patchKeys.length === 1 && patchKeys[0] === 'model';\n patchConfigRef.current(patch).catch((error: unknown) => {\n const data = configWriteData({ threadId, patch });\n if (isConfigWriteForbiddenRejection(error) || isConfigSyncUnavailableRejection(error)) {\n closedRef.current = true;\n athenaDiagnostics.emit('statewire.config', {\n level: 'warn',\n data: {\n ...data,\n reason: isConfigWriteForbiddenRejection(error)\n ? 'config_write_forbidden'\n : 'config_sync_unavailable',\n },\n });\n return;\n }\n const outcome = statewireTransportOutcome(error);\n if (outcome !== null) {\n // Channel weather (a dropped stream, the host's expired verdict): keep\n // each change pending and write it again after a backoff, once the\n // channel is live, unless its prop has moved on or the attach is gone.\n athenaDiagnostics.emit('statewire.config', {\n level: 'debug',\n data: { ...data, outcome },\n });\n if (outcome === 'unmount') return;\n const retryKeys = patchKeys.filter(\n (key) =>\n sameThreadConfigValue(seenRef.current[key], patch[key] ?? null) &&\n (attempts.get(key) ?? 0) < MAX_PATCH_ATTEMPTS_PER_CHANGE,\n );\n if (retryKeys.length === 0) return;\n for (const key of retryKeys) pendingRef.current.add(key);\n const written = Math.max(...retryKeys.map((key) => attempts.get(key) ?? 1));\n const delay = RETRY_BACKOFF_MS[Math.min(written - 1, RETRY_BACKOFF_MS.length - 1)];\n retryTimerRef.current = setTimeout(() => {\n retryTimerRef.current = null;\n setRetryTick((tick) => tick + 1);\n }, delay);\n return;\n }\n athenaDiagnostics.error(\n 'statewire.config',\n error,\n modelOnly\n ? {\n code: 'statewire_rejected',\n message: 'The statewire host refused the model change',\n hint: 'Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the model its server config holds.',\n }\n : {\n code: 'statewire_rejected',\n message: 'The statewire host refused the thread config change',\n hint: 'Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the config its server config holds.',\n },\n data,\n );\n });\n }, [config, landed, configVersion, custom, channelLive, retryTick, threadId]);\n\n return null;\n}\n"],"names":["patchedThreadConfigKeys","useStatewireSendCommand","useDeepAgentConfigController","useRef","useState","useEffect","PROVIDER_THREAD_CONFIG_KEYS","sameThreadConfigValue","decideThreadConfigLanePatch","isConfigWriteForbiddenRejection","isConfigSyncUnavailableRejection","athenaDiagnostics","statewireTransportOutcome"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AthenaStatewireServerLane } from './statewire-server-lane-context';
|
|
2
|
+
import { type ProviderThreadConfig } from './statewire-thread-config-lane';
|
|
3
|
+
/**
|
|
4
|
+
* Writes the provider-owned thread-config keys to the thread's server config
|
|
5
|
+
* lane when the host changes the props that feed them during an attach:
|
|
6
|
+
* `model`, `systemPrompt` (`system_prompt`), `tools` and `frontendTools`
|
|
7
|
+
* (`enabled_tools`), `workbench` and `knowledgeBase` (`knowledge_base`).
|
|
8
|
+
*
|
|
9
|
+
* Once a thread has a lane (version > 0), agora composes every run from the
|
|
10
|
+
* lane and drops the thread-config keys a send carries, so a model, prompt,
|
|
11
|
+
* tool list or asset list changed after the thread's first turn would never
|
|
12
|
+
* apply. A change of such a prop therefore becomes one `config/patch`
|
|
13
|
+
* carrying exactly the changed keys, each with the value the send would carry
|
|
14
|
+
* (`null` when the send carries none: the key is unset, so the collab agent's
|
|
15
|
+
* own value, or the default, applies). An empty tool or asset list is `null`,
|
|
16
|
+
* as the send omits it.
|
|
17
|
+
*
|
|
18
|
+
* Only a CHANGE is written. A prop's value when the attach starts is never
|
|
19
|
+
* written, even when the lane holds another value: that value is another
|
|
20
|
+
* client's (or an earlier session's) choice for this thread, and an SDK app's
|
|
21
|
+
* global setting must not overwrite it just by opening the thread. Hosts that
|
|
22
|
+
* want the picker to follow the thread read `useAthenaThreadModel`.
|
|
23
|
+
*
|
|
24
|
+
* Decisions read the lane the host itself sent on this attach (`lane`, from
|
|
25
|
+
* the transport), never the rendered thread state: before the attach snapshot
|
|
26
|
+
* lands that state is a placeholder without a lane, or a remembered snapshot
|
|
27
|
+
* with an older one, and comparing a change against either would write over
|
|
28
|
+
* the session's real config. A change made before the snapshot lands waits
|
|
29
|
+
* for it.
|
|
30
|
+
*
|
|
31
|
+
* A version-0 thread needs no write: the next send carries the values and
|
|
32
|
+
* agora's deferral seed persists them. A change stays pending until the lane
|
|
33
|
+
* exists, so a lane an earlier send seeds with the previous values is
|
|
34
|
+
* corrected when it lands. A refusal that can never succeed (a view-only
|
|
35
|
+
* collaborator, a workspace without server config) stops the sync for the
|
|
36
|
+
* attach and is reported on the diagnostics bus. A write the channel dropped
|
|
37
|
+
* (a lost stream, the host's expired verdict) stays pending and is written
|
|
38
|
+
* again after a backoff once the channel is live, at most
|
|
39
|
+
* {@link MAX_PATCH_ATTEMPTS_PER_CHANGE} times per change.
|
|
40
|
+
*
|
|
41
|
+
* The writes go through the `config/*` command family, which agora gates on
|
|
42
|
+
* EDIT on the thread's session asset (`_authorize_config_write`).
|
|
43
|
+
*
|
|
44
|
+
* Mounted under the runtime's `AuiProvider`, keyed by the thread, so each
|
|
45
|
+
* attach starts with nothing pending.
|
|
46
|
+
*/
|
|
47
|
+
export declare function StatewireThreadConfigLaneSync({ threadId, config, lane, channelLive, }: {
|
|
48
|
+
threadId: string;
|
|
49
|
+
/** The values this thread's sends carry, published with the thread (see the provider). */
|
|
50
|
+
config: ProviderThreadConfig;
|
|
51
|
+
lane: AthenaStatewireServerLane;
|
|
52
|
+
/** Whether the channel can take a command now; writes wait for it. */
|
|
53
|
+
channelLive: boolean;
|
|
54
|
+
}): null;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useStatewireSendCommand } from "@assistant-ui/react-statewire";
|
|
2
|
+
import { useRef, useState, useEffect } from "react";
|
|
3
|
+
import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
|
|
4
|
+
import { PROVIDER_THREAD_CONFIG_KEYS, sameThreadConfigValue, decideThreadConfigLanePatch, patchedThreadConfigKeys } from "./statewire-thread-config-lane.js";
|
|
5
|
+
import { useDeepAgentConfigController, isConfigWriteForbiddenRejection, isConfigSyncUnavailableRejection } from "../agent-runtime/src/use-deep-agent-config.js";
|
|
6
|
+
import { statewireTransportOutcome } from "../agent-runtime/src/errors.js";
|
|
7
|
+
const MAX_PATCH_ATTEMPTS_PER_CHANGE = 3;
|
|
8
|
+
const RETRY_BACKOFF_MS = [1e3, 4e3];
|
|
9
|
+
function configWriteData({
|
|
10
|
+
threadId,
|
|
11
|
+
patch
|
|
12
|
+
}) {
|
|
13
|
+
const otherKeys = patchedThreadConfigKeys(patch).filter((key) => key !== "model");
|
|
14
|
+
return {
|
|
15
|
+
thread_id: threadId,
|
|
16
|
+
...Object.hasOwn(patch, "model") && { model: patch.model },
|
|
17
|
+
...otherKeys.length > 0 && { config_keys: otherKeys }
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function StatewireThreadConfigLaneSync({
|
|
21
|
+
threadId,
|
|
22
|
+
config,
|
|
23
|
+
lane,
|
|
24
|
+
channelLive
|
|
25
|
+
}) {
|
|
26
|
+
const send = useStatewireSendCommand();
|
|
27
|
+
const sendCommand = (command) => Promise.resolve(send(command));
|
|
28
|
+
const { configVersion, custom, patchConfig } = useDeepAgentConfigController({
|
|
29
|
+
serverConfig: lane.config,
|
|
30
|
+
sendCommand
|
|
31
|
+
});
|
|
32
|
+
const seenRef = useRef(config);
|
|
33
|
+
const pendingRef = useRef(/* @__PURE__ */ new Set());
|
|
34
|
+
const attemptsRef = useRef(/* @__PURE__ */ new Map());
|
|
35
|
+
const closedRef = useRef(false);
|
|
36
|
+
const patchConfigRef = useRef(patchConfig);
|
|
37
|
+
patchConfigRef.current = patchConfig;
|
|
38
|
+
const [retryTick, setRetryTick] = useState(0);
|
|
39
|
+
const retryTimerRef = useRef(null);
|
|
40
|
+
useEffect(
|
|
41
|
+
() => () => {
|
|
42
|
+
if (retryTimerRef.current !== null) clearTimeout(retryTimerRef.current);
|
|
43
|
+
},
|
|
44
|
+
[]
|
|
45
|
+
);
|
|
46
|
+
const landed = lane.landed;
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const pending = pendingRef.current;
|
|
49
|
+
const attempts = attemptsRef.current;
|
|
50
|
+
for (const key of PROVIDER_THREAD_CONFIG_KEYS) {
|
|
51
|
+
if (sameThreadConfigValue(config[key], seenRef.current[key])) continue;
|
|
52
|
+
pending.add(key);
|
|
53
|
+
attempts.set(key, 0);
|
|
54
|
+
}
|
|
55
|
+
seenRef.current = config;
|
|
56
|
+
if (pending.size === 0 || closedRef.current || !channelLive) return;
|
|
57
|
+
const decision = decideThreadConfigLanePatch({
|
|
58
|
+
desired: config,
|
|
59
|
+
keys: pending,
|
|
60
|
+
landed,
|
|
61
|
+
configVersion,
|
|
62
|
+
custom
|
|
63
|
+
});
|
|
64
|
+
if (decision.kind === "wait") return;
|
|
65
|
+
pending.clear();
|
|
66
|
+
if (decision.kind === "done") return;
|
|
67
|
+
const { patch } = decision;
|
|
68
|
+
const patchKeys = patchedThreadConfigKeys(patch);
|
|
69
|
+
for (const key of patchKeys) attempts.set(key, (attempts.get(key) ?? 0) + 1);
|
|
70
|
+
const modelOnly = patchKeys.length === 1 && patchKeys[0] === "model";
|
|
71
|
+
patchConfigRef.current(patch).catch((error) => {
|
|
72
|
+
const data = configWriteData({ threadId, patch });
|
|
73
|
+
if (isConfigWriteForbiddenRejection(error) || isConfigSyncUnavailableRejection(error)) {
|
|
74
|
+
closedRef.current = true;
|
|
75
|
+
athenaDiagnostics.emit("statewire.config", {
|
|
76
|
+
level: "warn",
|
|
77
|
+
data: {
|
|
78
|
+
...data,
|
|
79
|
+
reason: isConfigWriteForbiddenRejection(error) ? "config_write_forbidden" : "config_sync_unavailable"
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const outcome = statewireTransportOutcome(error);
|
|
85
|
+
if (outcome !== null) {
|
|
86
|
+
athenaDiagnostics.emit("statewire.config", {
|
|
87
|
+
level: "debug",
|
|
88
|
+
data: { ...data, outcome }
|
|
89
|
+
});
|
|
90
|
+
if (outcome === "unmount") return;
|
|
91
|
+
const retryKeys = patchKeys.filter(
|
|
92
|
+
(key) => sameThreadConfigValue(seenRef.current[key], patch[key] ?? null) && (attempts.get(key) ?? 0) < MAX_PATCH_ATTEMPTS_PER_CHANGE
|
|
93
|
+
);
|
|
94
|
+
if (retryKeys.length === 0) return;
|
|
95
|
+
for (const key of retryKeys) pendingRef.current.add(key);
|
|
96
|
+
const written = Math.max(...retryKeys.map((key) => attempts.get(key) ?? 1));
|
|
97
|
+
const delay = RETRY_BACKOFF_MS[Math.min(written - 1, RETRY_BACKOFF_MS.length - 1)];
|
|
98
|
+
retryTimerRef.current = setTimeout(() => {
|
|
99
|
+
retryTimerRef.current = null;
|
|
100
|
+
setRetryTick((tick) => tick + 1);
|
|
101
|
+
}, delay);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
athenaDiagnostics.error(
|
|
105
|
+
"statewire.config",
|
|
106
|
+
error,
|
|
107
|
+
modelOnly ? {
|
|
108
|
+
code: "statewire_rejected",
|
|
109
|
+
message: "The statewire host refused the model change",
|
|
110
|
+
hint: "Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the model its server config holds."
|
|
111
|
+
} : {
|
|
112
|
+
code: "statewire_rejected",
|
|
113
|
+
message: "The statewire host refused the thread config change",
|
|
114
|
+
hint: "Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the config its server config holds."
|
|
115
|
+
},
|
|
116
|
+
data
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
}, [config, landed, configVersion, custom, channelLive, retryTick, threadId]);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
StatewireThreadConfigLaneSync
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=StatewireThreadConfigLaneSync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatewireThreadConfigLaneSync.js","sources":["../../src/runtime/StatewireThreadConfigLaneSync.tsx"],"sourcesContent":["import { useStatewireSendCommand } from '@assistant-ui/react-statewire';\nimport {\n type DeepAgentConfigControllerInputs,\n isConfigSyncUnavailableRejection,\n isConfigWriteForbiddenRejection,\n statewireTransportOutcome,\n useDeepAgentConfigController,\n} from '@athenaintel/agent-runtime';\nimport { useEffect, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport type { AthenaStatewireServerLane } from './statewire-server-lane-context';\nimport {\n decideThreadConfigLanePatch,\n PROVIDER_THREAD_CONFIG_KEYS,\n type ProviderThreadConfig,\n type ProviderThreadConfigKey,\n type ProviderThreadConfigPatch,\n patchedThreadConfigKeys,\n sameThreadConfigValue,\n} from './statewire-thread-config-lane';\n\n/** Writes of one change, the first included, before the sync gives up on it. */\nconst MAX_PATCH_ATTEMPTS_PER_CHANGE = 3;\n\n/** Wait before re-writing a change the channel dropped: 1 s, then 4 s. */\nconst RETRY_BACKOFF_MS = [1_000, 4_000] as const;\n\n/**\n * What a `statewire.config` event says about a write: the thread, the model it\n * set (`null`: unset), and the other provider-owned keys it set\n * (`config_keys`), never their values (a system prompt is the host's text).\n */\nfunction configWriteData({\n threadId,\n patch,\n}: {\n threadId: string;\n patch: ProviderThreadConfigPatch;\n}): Record<string, unknown> {\n const otherKeys = patchedThreadConfigKeys(patch).filter((key) => key !== 'model');\n return {\n thread_id: threadId,\n ...(Object.hasOwn(patch, 'model') && { model: patch.model }),\n ...(otherKeys.length > 0 && { config_keys: otherKeys }),\n };\n}\n\n/**\n * Writes the provider-owned thread-config keys to the thread's server config\n * lane when the host changes the props that feed them during an attach:\n * `model`, `systemPrompt` (`system_prompt`), `tools` and `frontendTools`\n * (`enabled_tools`), `workbench` and `knowledgeBase` (`knowledge_base`).\n *\n * Once a thread has a lane (version > 0), agora composes every run from the\n * lane and drops the thread-config keys a send carries, so a model, prompt,\n * tool list or asset list changed after the thread's first turn would never\n * apply. A change of such a prop therefore becomes one `config/patch`\n * carrying exactly the changed keys, each with the value the send would carry\n * (`null` when the send carries none: the key is unset, so the collab agent's\n * own value, or the default, applies). An empty tool or asset list is `null`,\n * as the send omits it.\n *\n * Only a CHANGE is written. A prop's value when the attach starts is never\n * written, even when the lane holds another value: that value is another\n * client's (or an earlier session's) choice for this thread, and an SDK app's\n * global setting must not overwrite it just by opening the thread. Hosts that\n * want the picker to follow the thread read `useAthenaThreadModel`.\n *\n * Decisions read the lane the host itself sent on this attach (`lane`, from\n * the transport), never the rendered thread state: before the attach snapshot\n * lands that state is a placeholder without a lane, or a remembered snapshot\n * with an older one, and comparing a change against either would write over\n * the session's real config. A change made before the snapshot lands waits\n * for it.\n *\n * A version-0 thread needs no write: the next send carries the values and\n * agora's deferral seed persists them. A change stays pending until the lane\n * exists, so a lane an earlier send seeds with the previous values is\n * corrected when it lands. A refusal that can never succeed (a view-only\n * collaborator, a workspace without server config) stops the sync for the\n * attach and is reported on the diagnostics bus. A write the channel dropped\n * (a lost stream, the host's expired verdict) stays pending and is written\n * again after a backoff once the channel is live, at most\n * {@link MAX_PATCH_ATTEMPTS_PER_CHANGE} times per change.\n *\n * The writes go through the `config/*` command family, which agora gates on\n * EDIT on the thread's session asset (`_authorize_config_write`).\n *\n * Mounted under the runtime's `AuiProvider`, keyed by the thread, so each\n * attach starts with nothing pending.\n */\nexport function StatewireThreadConfigLaneSync({\n threadId,\n config,\n lane,\n channelLive,\n}: {\n threadId: string;\n /** The values this thread's sends carry, published with the thread (see the provider). */\n config: ProviderThreadConfig;\n lane: AthenaStatewireServerLane;\n /** Whether the channel can take a command now; writes wait for it. */\n channelLive: boolean;\n}): null {\n const send = useStatewireSendCommand();\n const sendCommand: DeepAgentConfigControllerInputs['sendCommand'] = (command) =>\n Promise.resolve(send(command));\n const { configVersion, custom, patchConfig } = useDeepAgentConfigController({\n serverConfig: lane.config,\n sendCommand,\n });\n\n const seenRef = useRef(config);\n const pendingRef = useRef(new Set<ProviderThreadConfigKey>());\n const attemptsRef = useRef(new Map<ProviderThreadConfigKey, number>());\n const closedRef = useRef(false);\n const patchConfigRef = useRef(patchConfig);\n patchConfigRef.current = patchConfig;\n // Bumped (after a backoff) to re-run the effect for a write the channel dropped.\n const [retryTick, setRetryTick] = useState(0);\n const retryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n useEffect(\n () => () => {\n if (retryTimerRef.current !== null) clearTimeout(retryTimerRef.current);\n },\n [],\n );\n const landed = lane.landed;\n\n useEffect(() => {\n const pending = pendingRef.current;\n const attempts = attemptsRef.current;\n for (const key of PROVIDER_THREAD_CONFIG_KEYS) {\n if (sameThreadConfigValue(config[key], seenRef.current[key])) continue;\n pending.add(key);\n attempts.set(key, 0);\n }\n seenRef.current = config;\n if (pending.size === 0 || closedRef.current || !channelLive) return;\n const decision = decideThreadConfigLanePatch({\n desired: config,\n keys: pending,\n landed,\n configVersion,\n custom,\n });\n if (decision.kind === 'wait') return;\n pending.clear();\n if (decision.kind === 'done') return;\n\n const { patch } = decision;\n const patchKeys = patchedThreadConfigKeys(patch);\n for (const key of patchKeys) attempts.set(key, (attempts.get(key) ?? 0) + 1);\n const modelOnly = patchKeys.length === 1 && patchKeys[0] === 'model';\n patchConfigRef.current(patch).catch((error: unknown) => {\n const data = configWriteData({ threadId, patch });\n if (isConfigWriteForbiddenRejection(error) || isConfigSyncUnavailableRejection(error)) {\n closedRef.current = true;\n athenaDiagnostics.emit('statewire.config', {\n level: 'warn',\n data: {\n ...data,\n reason: isConfigWriteForbiddenRejection(error)\n ? 'config_write_forbidden'\n : 'config_sync_unavailable',\n },\n });\n return;\n }\n const outcome = statewireTransportOutcome(error);\n if (outcome !== null) {\n // Channel weather (a dropped stream, the host's expired verdict): keep\n // each change pending and write it again after a backoff, once the\n // channel is live, unless its prop has moved on or the attach is gone.\n athenaDiagnostics.emit('statewire.config', {\n level: 'debug',\n data: { ...data, outcome },\n });\n if (outcome === 'unmount') return;\n const retryKeys = patchKeys.filter(\n (key) =>\n sameThreadConfigValue(seenRef.current[key], patch[key] ?? null) &&\n (attempts.get(key) ?? 0) < MAX_PATCH_ATTEMPTS_PER_CHANGE,\n );\n if (retryKeys.length === 0) return;\n for (const key of retryKeys) pendingRef.current.add(key);\n const written = Math.max(...retryKeys.map((key) => attempts.get(key) ?? 1));\n const delay = RETRY_BACKOFF_MS[Math.min(written - 1, RETRY_BACKOFF_MS.length - 1)];\n retryTimerRef.current = setTimeout(() => {\n retryTimerRef.current = null;\n setRetryTick((tick) => tick + 1);\n }, delay);\n return;\n }\n athenaDiagnostics.error(\n 'statewire.config',\n error,\n modelOnly\n ? {\n code: 'statewire_rejected',\n message: 'The statewire host refused the model change',\n hint: 'Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the model its server config holds.',\n }\n : {\n code: 'statewire_rejected',\n message: 'The statewire host refused the thread config change',\n hint: 'Inspect error.detail: agora rejections carry a structured {code, message}. The thread keeps running with the config its server config holds.',\n },\n data,\n );\n });\n }, [config, landed, configVersion, custom, channelLive, retryTick, threadId]);\n\n return null;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -11,18 +11,6 @@ function threadModelFromLane({
|
|
|
11
11
|
if (!ready || lane === null) return { model: null, configured: false, ready };
|
|
12
12
|
return { model: readLaneModel(lane.custom), configured: true, ready };
|
|
13
13
|
}
|
|
14
|
-
function decideModelLanePatch({
|
|
15
|
-
desiredModel,
|
|
16
|
-
landed,
|
|
17
|
-
configVersion,
|
|
18
|
-
custom
|
|
19
|
-
}) {
|
|
20
|
-
if (!landed || configVersion <= 0) return { kind: "wait" };
|
|
21
|
-
const desired = desiredModel === void 0 || desiredModel === "" ? null : desiredModel;
|
|
22
|
-
if (readLaneModel(custom) === desired) return { kind: "done" };
|
|
23
|
-
return { kind: "patch", patch: { model: desired } };
|
|
24
|
-
}
|
|
25
|
-
exports.decideModelLanePatch = decideModelLanePatch;
|
|
26
14
|
exports.readLaneModel = readLaneModel;
|
|
27
15
|
exports.threadModelFromLane = threadModelFromLane;
|
|
28
16
|
//# sourceMappingURL=statewire-model-lane.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire-model-lane.cjs","sources":["../../src/runtime/statewire-model-lane.ts"],"sourcesContent":["import type { DeepAgentServerConfig, ThreadConfigCustom } from '@athenaintel/agent-runtime';\n\n/**\n * The model a thread's server config lane (`state.config.custom`) pins, as the\n * host app should read it, plus whether there is a lane at all.\n *\n * Once a thread has a lane (version > 0), agora runs every turn with the lane's\n * thread-config keys and drops the ones a send carries, `model` included: the\n * lane, not the provider's `model` prop, decides which model a turn uses.\n */\nexport interface AthenaThreadModel {\n /**\n * The model the lane pins, or `null` when it pins none: the run then uses the\n * selected collab agent's own model, or the deployment default.\n */\n model: string | null;\n /** `true` once the thread has a server config lane (version > 0). */\n configured: boolean;\n /**\n * `true` once the statewire host's snapshot of this thread has arrived (the\n * placeholder or remembered snapshot a thread shows while it loads does not\n * count: it may carry no lane, or an older one). Before that `model` is\n * `null` and `configured` is `false` whatever the thread holds, so wait for\n * `ready` before treating \"no model\" as an answer.\n */\n ready: boolean;\n}\n\n/** The lane's `model` key, or `null` when it carries none (or a non-string). */\nexport function readLaneModel(custom: ThreadConfigCustom): string | null {\n const model = custom.model;\n return typeof model === 'string' && model !== '' ? model : null;\n}\n\n/** {@link AthenaThreadModel} from the lane and the landed flag. Pure. */\nexport function threadModelFromLane({\n lane,\n ready,\n}: {\n lane: DeepAgentServerConfig | null;\n ready: boolean;\n}): AthenaThreadModel {\n if (!ready || lane === null) return { model: null, configured: false, ready };\n return { model: readLaneModel(lane.custom), configured: true, ready };\n}\n\n/**\n * What to do with a `model` prop change the host made during an attach:\n *\n * - `wait`: keep the change pending. The thread's snapshot has not landed, or\n * the thread has no lane yet (version 0). A version-0 thread needs nothing:\n * the next send carries the model and agora's deferral seed persists it. The\n * change stays pending only so that a lane an EARLIER send seeds (with the\n * previous model) is corrected once it lands.\n * - `done`: the lane already holds the model (this client's own unacknowledged\n * writes folded over); nothing to write.\n * - `patch`: issue `config/patch {model}`; `null` unsets the lane's model so\n * the agent's own model (or the deployment default) applies.\n */\nexport type ModelLaneDecision =\n | { kind: 'wait' }\n | { kind: 'done' }\n | { kind: 'patch'; patch: { model: string | null } };\n\nexport function decideModelLanePatch({\n desiredModel,\n landed,\n configVersion,\n custom,\n}: {\n /** The provider's `model` prop after the change (`undefined` = no explicit model). */\n desiredModel: string | undefined;\n landed: boolean;\n configVersion: number;\n /** The effective lane: the server config with this client's pending writes folded over. */\n custom: ThreadConfigCustom;\n}): ModelLaneDecision {\n
|
|
1
|
+
{"version":3,"file":"statewire-model-lane.cjs","sources":["../../src/runtime/statewire-model-lane.ts"],"sourcesContent":["import type { DeepAgentServerConfig, ThreadConfigCustom } from '@athenaintel/agent-runtime';\nimport { decideThreadConfigLanePatch } from './statewire-thread-config-lane';\n\n/**\n * The model a thread's server config lane (`state.config.custom`) pins, as the\n * host app should read it, plus whether there is a lane at all.\n *\n * Once a thread has a lane (version > 0), agora runs every turn with the lane's\n * thread-config keys and drops the ones a send carries, `model` included: the\n * lane, not the provider's `model` prop, decides which model a turn uses.\n */\nexport interface AthenaThreadModel {\n /**\n * The model the lane pins, or `null` when it pins none: the run then uses the\n * selected collab agent's own model, or the deployment default.\n */\n model: string | null;\n /** `true` once the thread has a server config lane (version > 0). */\n configured: boolean;\n /**\n * `true` once the statewire host's snapshot of this thread has arrived (the\n * placeholder or remembered snapshot a thread shows while it loads does not\n * count: it may carry no lane, or an older one). Before that `model` is\n * `null` and `configured` is `false` whatever the thread holds, so wait for\n * `ready` before treating \"no model\" as an answer.\n */\n ready: boolean;\n}\n\n/** The lane's `model` key, or `null` when it carries none (or a non-string). */\nexport function readLaneModel(custom: ThreadConfigCustom): string | null {\n const model = custom.model;\n return typeof model === 'string' && model !== '' ? model : null;\n}\n\n/** {@link AthenaThreadModel} from the lane and the landed flag. Pure. */\nexport function threadModelFromLane({\n lane,\n ready,\n}: {\n lane: DeepAgentServerConfig | null;\n ready: boolean;\n}): AthenaThreadModel {\n if (!ready || lane === null) return { model: null, configured: false, ready };\n return { model: readLaneModel(lane.custom), configured: true, ready };\n}\n\n/**\n * What to do with a `model` prop change the host made during an attach (the\n * `model` key of {@link decideThreadConfigLanePatch}, which decides every\n * provider-owned key the same way):\n *\n * - `wait`: keep the change pending. The thread's snapshot has not landed, or\n * the thread has no lane yet (version 0). A version-0 thread needs nothing:\n * the next send carries the model and agora's deferral seed persists it. The\n * change stays pending only so that a lane an EARLIER send seeds (with the\n * previous model) is corrected once it lands.\n * - `done`: the lane already holds the model (this client's own unacknowledged\n * writes folded over); nothing to write.\n * - `patch`: issue `config/patch {model}`; `null` unsets the lane's model so\n * the agent's own model (or the deployment default) applies.\n */\nexport type ModelLaneDecision =\n | { kind: 'wait' }\n | { kind: 'done' }\n | { kind: 'patch'; patch: { model: string | null } };\n\nexport function decideModelLanePatch({\n desiredModel,\n landed,\n configVersion,\n custom,\n}: {\n /** The provider's `model` prop after the change (`undefined` = no explicit model). */\n desiredModel: string | undefined;\n landed: boolean;\n configVersion: number;\n /** The effective lane: the server config with this client's pending writes folded over. */\n custom: ThreadConfigCustom;\n}): ModelLaneDecision {\n const desired = desiredModel === undefined || desiredModel === '' ? null : desiredModel;\n const decision = decideThreadConfigLanePatch({\n desired: {\n model: desired,\n system_prompt: null,\n enabled_tools: null,\n workbench: null,\n knowledge_base: null,\n },\n keys: ['model'],\n landed,\n configVersion,\n custom,\n });\n if (decision.kind !== 'patch') return decision;\n return { kind: 'patch', patch: { model: desired } };\n}\n"],"names":[],"mappings":";;AA8BO,SAAS,cAAc,QAA2C;AACvE,QAAM,QAAQ,OAAO;AACrB,SAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ;AAC7D;AAGO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGsB;AACpB,MAAI,CAAC,SAAS,SAAS,KAAM,QAAO,EAAE,OAAO,MAAM,YAAY,OAAO,MAAA;AACtE,SAAO,EAAE,OAAO,cAAc,KAAK,MAAM,GAAG,YAAY,MAAM,MAAA;AAChE;;;"}
|
|
@@ -32,7 +32,9 @@ export declare function threadModelFromLane({ lane, ready, }: {
|
|
|
32
32
|
ready: boolean;
|
|
33
33
|
}): AthenaThreadModel;
|
|
34
34
|
/**
|
|
35
|
-
* What to do with a `model` prop change the host made during an attach
|
|
35
|
+
* What to do with a `model` prop change the host made during an attach (the
|
|
36
|
+
* `model` key of {@link decideThreadConfigLanePatch}, which decides every
|
|
37
|
+
* provider-owned key the same way):
|
|
36
38
|
*
|
|
37
39
|
* - `wait`: keep the change pending. The thread's snapshot has not landed, or
|
|
38
40
|
* the thread has no lane yet (version 0). A version-0 thread needs nothing:
|
|
@@ -9,19 +9,7 @@ function threadModelFromLane({
|
|
|
9
9
|
if (!ready || lane === null) return { model: null, configured: false, ready };
|
|
10
10
|
return { model: readLaneModel(lane.custom), configured: true, ready };
|
|
11
11
|
}
|
|
12
|
-
function decideModelLanePatch({
|
|
13
|
-
desiredModel,
|
|
14
|
-
landed,
|
|
15
|
-
configVersion,
|
|
16
|
-
custom
|
|
17
|
-
}) {
|
|
18
|
-
if (!landed || configVersion <= 0) return { kind: "wait" };
|
|
19
|
-
const desired = desiredModel === void 0 || desiredModel === "" ? null : desiredModel;
|
|
20
|
-
if (readLaneModel(custom) === desired) return { kind: "done" };
|
|
21
|
-
return { kind: "patch", patch: { model: desired } };
|
|
22
|
-
}
|
|
23
12
|
export {
|
|
24
|
-
decideModelLanePatch,
|
|
25
13
|
readLaneModel,
|
|
26
14
|
threadModelFromLane
|
|
27
15
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire-model-lane.js","sources":["../../src/runtime/statewire-model-lane.ts"],"sourcesContent":["import type { DeepAgentServerConfig, ThreadConfigCustom } from '@athenaintel/agent-runtime';\n\n/**\n * The model a thread's server config lane (`state.config.custom`) pins, as the\n * host app should read it, plus whether there is a lane at all.\n *\n * Once a thread has a lane (version > 0), agora runs every turn with the lane's\n * thread-config keys and drops the ones a send carries, `model` included: the\n * lane, not the provider's `model` prop, decides which model a turn uses.\n */\nexport interface AthenaThreadModel {\n /**\n * The model the lane pins, or `null` when it pins none: the run then uses the\n * selected collab agent's own model, or the deployment default.\n */\n model: string | null;\n /** `true` once the thread has a server config lane (version > 0). */\n configured: boolean;\n /**\n * `true` once the statewire host's snapshot of this thread has arrived (the\n * placeholder or remembered snapshot a thread shows while it loads does not\n * count: it may carry no lane, or an older one). Before that `model` is\n * `null` and `configured` is `false` whatever the thread holds, so wait for\n * `ready` before treating \"no model\" as an answer.\n */\n ready: boolean;\n}\n\n/** The lane's `model` key, or `null` when it carries none (or a non-string). */\nexport function readLaneModel(custom: ThreadConfigCustom): string | null {\n const model = custom.model;\n return typeof model === 'string' && model !== '' ? model : null;\n}\n\n/** {@link AthenaThreadModel} from the lane and the landed flag. Pure. */\nexport function threadModelFromLane({\n lane,\n ready,\n}: {\n lane: DeepAgentServerConfig | null;\n ready: boolean;\n}): AthenaThreadModel {\n if (!ready || lane === null) return { model: null, configured: false, ready };\n return { model: readLaneModel(lane.custom), configured: true, ready };\n}\n\n/**\n * What to do with a `model` prop change the host made during an attach:\n *\n * - `wait`: keep the change pending. The thread's snapshot has not landed, or\n * the thread has no lane yet (version 0). A version-0 thread needs nothing:\n * the next send carries the model and agora's deferral seed persists it. The\n * change stays pending only so that a lane an EARLIER send seeds (with the\n * previous model) is corrected once it lands.\n * - `done`: the lane already holds the model (this client's own unacknowledged\n * writes folded over); nothing to write.\n * - `patch`: issue `config/patch {model}`; `null` unsets the lane's model so\n * the agent's own model (or the deployment default) applies.\n */\nexport type ModelLaneDecision =\n | { kind: 'wait' }\n | { kind: 'done' }\n | { kind: 'patch'; patch: { model: string | null } };\n\nexport function decideModelLanePatch({\n desiredModel,\n landed,\n configVersion,\n custom,\n}: {\n /** The provider's `model` prop after the change (`undefined` = no explicit model). */\n desiredModel: string | undefined;\n landed: boolean;\n configVersion: number;\n /** The effective lane: the server config with this client's pending writes folded over. */\n custom: ThreadConfigCustom;\n}): ModelLaneDecision {\n
|
|
1
|
+
{"version":3,"file":"statewire-model-lane.js","sources":["../../src/runtime/statewire-model-lane.ts"],"sourcesContent":["import type { DeepAgentServerConfig, ThreadConfigCustom } from '@athenaintel/agent-runtime';\nimport { decideThreadConfigLanePatch } from './statewire-thread-config-lane';\n\n/**\n * The model a thread's server config lane (`state.config.custom`) pins, as the\n * host app should read it, plus whether there is a lane at all.\n *\n * Once a thread has a lane (version > 0), agora runs every turn with the lane's\n * thread-config keys and drops the ones a send carries, `model` included: the\n * lane, not the provider's `model` prop, decides which model a turn uses.\n */\nexport interface AthenaThreadModel {\n /**\n * The model the lane pins, or `null` when it pins none: the run then uses the\n * selected collab agent's own model, or the deployment default.\n */\n model: string | null;\n /** `true` once the thread has a server config lane (version > 0). */\n configured: boolean;\n /**\n * `true` once the statewire host's snapshot of this thread has arrived (the\n * placeholder or remembered snapshot a thread shows while it loads does not\n * count: it may carry no lane, or an older one). Before that `model` is\n * `null` and `configured` is `false` whatever the thread holds, so wait for\n * `ready` before treating \"no model\" as an answer.\n */\n ready: boolean;\n}\n\n/** The lane's `model` key, or `null` when it carries none (or a non-string). */\nexport function readLaneModel(custom: ThreadConfigCustom): string | null {\n const model = custom.model;\n return typeof model === 'string' && model !== '' ? model : null;\n}\n\n/** {@link AthenaThreadModel} from the lane and the landed flag. Pure. */\nexport function threadModelFromLane({\n lane,\n ready,\n}: {\n lane: DeepAgentServerConfig | null;\n ready: boolean;\n}): AthenaThreadModel {\n if (!ready || lane === null) return { model: null, configured: false, ready };\n return { model: readLaneModel(lane.custom), configured: true, ready };\n}\n\n/**\n * What to do with a `model` prop change the host made during an attach (the\n * `model` key of {@link decideThreadConfigLanePatch}, which decides every\n * provider-owned key the same way):\n *\n * - `wait`: keep the change pending. The thread's snapshot has not landed, or\n * the thread has no lane yet (version 0). A version-0 thread needs nothing:\n * the next send carries the model and agora's deferral seed persists it. The\n * change stays pending only so that a lane an EARLIER send seeds (with the\n * previous model) is corrected once it lands.\n * - `done`: the lane already holds the model (this client's own unacknowledged\n * writes folded over); nothing to write.\n * - `patch`: issue `config/patch {model}`; `null` unsets the lane's model so\n * the agent's own model (or the deployment default) applies.\n */\nexport type ModelLaneDecision =\n | { kind: 'wait' }\n | { kind: 'done' }\n | { kind: 'patch'; patch: { model: string | null } };\n\nexport function decideModelLanePatch({\n desiredModel,\n landed,\n configVersion,\n custom,\n}: {\n /** The provider's `model` prop after the change (`undefined` = no explicit model). */\n desiredModel: string | undefined;\n landed: boolean;\n configVersion: number;\n /** The effective lane: the server config with this client's pending writes folded over. */\n custom: ThreadConfigCustom;\n}): ModelLaneDecision {\n const desired = desiredModel === undefined || desiredModel === '' ? null : desiredModel;\n const decision = decideThreadConfigLanePatch({\n desired: {\n model: desired,\n system_prompt: null,\n enabled_tools: null,\n workbench: null,\n knowledge_base: null,\n },\n keys: ['model'],\n landed,\n configVersion,\n custom,\n });\n if (decision.kind !== 'patch') return decision;\n return { kind: 'patch', patch: { model: desired } };\n}\n"],"names":[],"mappings":"AA8BO,SAAS,cAAc,QAA2C;AACvE,QAAM,QAAQ,OAAO;AACrB,SAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ;AAC7D;AAGO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGsB;AACpB,MAAI,CAAC,SAAS,SAAS,KAAM,QAAO,EAAE,OAAO,MAAM,YAAY,OAAO,MAAA;AACtE,SAAO,EAAE,OAAO,cAAc,KAAK,MAAM,GAAG,YAAY,MAAM,MAAA;AAChE;"}
|
|
@@ -30,14 +30,17 @@ function effectiveStatewireModel({
|
|
|
30
30
|
return model === "" ? void 0 : model;
|
|
31
31
|
}
|
|
32
32
|
function buildStatewireRunConfig(options = {}) {
|
|
33
|
-
var _a, _b, _c, _d, _e;
|
|
33
|
+
var _a, _b, _c, _d, _e, _f;
|
|
34
34
|
const collabAgentId = resolveAgentProp({ agent: options.agent });
|
|
35
|
-
const
|
|
35
|
+
const channel = collabAgentId ? ((_a = options.channel) == null ? void 0 : _a.trim()) || void 0 : void 0;
|
|
36
|
+
const mode = collabAgentId ? ((_b = options.mode) == null ? void 0 : _b.trim()) || void 0 : void 0;
|
|
37
|
+
const collabChannelId = mode ?? channel;
|
|
38
|
+
const collabEntryChannelId = mode && channel && mode !== channel ? channel : void 0;
|
|
36
39
|
const model = options.model ?? (collabAgentId ? void 0 : DEFAULT_STATEWIRE_MODEL);
|
|
37
40
|
const enabledTools = Array.from(
|
|
38
41
|
/* @__PURE__ */ new Set([...options.tools ?? [], ...options.frontendToolIds ?? []])
|
|
39
42
|
);
|
|
40
|
-
const systemPrompt = ((
|
|
43
|
+
const systemPrompt = ((_c = options.systemPrompt) == null ? void 0 : _c.trim()) ?? "";
|
|
41
44
|
return {
|
|
42
45
|
custom: {
|
|
43
46
|
runtime: DEEP_AGENT_RUNTIME_MARKER,
|
|
@@ -63,11 +66,12 @@ function buildStatewireRunConfig(options = {}) {
|
|
|
63
66
|
},
|
|
64
67
|
...collabAgentId ? { collab_agent_id: collabAgentId } : {},
|
|
65
68
|
...collabChannelId ? { collab_channel_id: collabChannelId } : {},
|
|
69
|
+
...collabEntryChannelId ? { collab_entry_channel_id: collabEntryChannelId } : {},
|
|
66
70
|
...systemPrompt ? { system_prompt: systemPrompt } : {},
|
|
67
|
-
...((
|
|
68
|
-
...((
|
|
71
|
+
...((_d = options.workbench) == null ? void 0 : _d.length) ? { workbench: options.workbench } : {},
|
|
72
|
+
...((_e = options.knowledgeBase) == null ? void 0 : _e.length) ? { knowledge_base: options.knowledgeBase } : {},
|
|
69
73
|
...options.appId ? { app_id: options.appId } : {},
|
|
70
|
-
...((
|
|
74
|
+
...((_f = options.clientTools) == null ? void 0 : _f.length) ? { client_tools: options.clientTools } : {},
|
|
71
75
|
...options.extraRunConfig ?? {}
|
|
72
76
|
}
|
|
73
77
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire-run-config.cjs","sources":["../../src/runtime/statewire-run-config.ts"],"sourcesContent":["/** Runtime marker the deep-agent session substrate stamps on sessions. */\nconst DEEP_AGENT_RUNTIME_MARKER = 'athena_deep_agent';\n\n/** Session-origin channel. The backend clamps unknown values to 'web', which\n * is how every SDK-started session has always classified. */\nconst STATEWIRE_START_CHANNEL = 'web';\n\n/** Session label persisted in the thread's panel config (mirrors\n * 'Athena Chrome' / 'Athena mobile' in the sibling statewire hosts). */\nconst STATEWIRE_RUN_LABEL = 'Athena SDK';\n\n/** Deep-agent default model — matches the Olympus and mobile statewire hosts. */\nexport const DEFAULT_STATEWIRE_MODEL = 'claude-sonnet-5-5';\n\n/** Agent-id namespace for collab agents (`collab_agent:<asset_id>`). */\nexport const COLLAB_AGENT_REF_PREFIX = 'collab_agent:';\n\n/** The only shape the `agent` prop accepts, for error messages and docs. */\nexport const COLLAB_AGENT_REF_SHAPE = 'collab_agent:<asset_id>';\n\n/** The asset id when `agent` is a `collab_agent:<asset_id>` ref, else null. */\nexport function parseCollabAgentRef(agent: string | undefined | null): string | null {\n if (typeof agent !== 'string' || !agent.startsWith(COLLAB_AGENT_REF_PREFIX)) return null;\n const assetId = agent.slice(COLLAB_AGENT_REF_PREFIX.length).trim();\n return assetId || null;\n}\n\n/**\n * Resolve the provider's `agent` prop to a collab-agent asset id.\n *\n * An omitted or blank `agent` means \"no agent selected\" — the thread runs the\n * Athena deep agent — and resolves to `null`. A `collab_agent:<asset_id>` ref\n * resolves to the asset id. Any other non-blank value throws: the statewire\n * host cannot honour it, and a selector that is silently dropped would let a\n * misconfigured deployment ship answers from the wrong agent.\n */\nexport function resolveAgentProp({ agent }: { agent: string | undefined | null }): string | null {\n if (agent === undefined || agent === null || agent.trim() === '') return null;\n const assetId = parseCollabAgentRef(agent.trim());\n if (assetId) return assetId;\n throw new Error(\n `[AthenaSDK] Unsupported \\`agent\\` prop ${JSON.stringify(agent)}. The only accepted shape is '${COLLAB_AGENT_REF_SHAPE}' (the asset id of a published collab agent). Named agents are not supported — the statewire host always runs the Athena deep agent; omit \\`agent\\` to use it.`,\n );\n}\n\nexport interface StatewireRunConfigOptions {\n /** LLM model identifier. Defaults to the deep-agent default model — unless a\n * collab agent is selected and its author set one (the backend overlay\n * applies the agent's model when the run sends none), in which case no\n * model is sent so the agent's choice wins. */\n model?: string;\n /** Agent selector. Only the `collab_agent:<asset_id>` shape is accepted: the\n * run executes AS that published collab agent (prompt, tools, model\n * default). Any other non-empty value throws (see {@link resolveAgentProp}). */\n agent?: string;\n /** Collab-agent channel id (built-in like `email`/`sms`, or a user-defined\n * custom channel id) selecting that channel's configuration overrides.\n * Sent as `collab_channel_id`; only meaningful with a collab `agent`. */\n channel?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier, passed through as app_id. */\n appId?: string;\n /** Client-executed tool declarations, passed through as client_tools. The\n * deep-agent runtime binds each as an interrupt-backed tool the client\n * answers via the statewire client-tool bridge. */\n clientTools?: Record<string, unknown>[];\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n}\n\n/**\n * The model a send carries for the host's `model` and `extraRunConfig`, with\n * the precedence {@link buildStatewireRunConfig} applies: `extraRunConfig` is\n * merged last, so its `model` key (when it has one) wins over the `model`\n * prop. `undefined` when the send carries no explicit model.\n */\nexport function effectiveStatewireModel({\n model,\n extraRunConfig,\n}: {\n model: string | undefined;\n extraRunConfig: Record<string, unknown> | undefined;\n}): string | undefined {\n if (extraRunConfig !== undefined && Object.hasOwn(extraRunConfig, 'model')) {\n const override = extraRunConfig.model;\n return typeof override === 'string' && override !== '' ? override : undefined;\n }\n return model === '' ? undefined : model;\n}\n\n/**\n * Build the `runConfig` a deep-agent statewire run is driven by. Commands\n * travel bare on the statewire channel, so this payload rides the ambient\n * model context (`config.runConfig`) instead of the request body.\n *\n * The flattened `custom` fields drive the run, while `panel_config` carries a\n * minimal panel state the backend persists per thread and replays on the\n * statewire snapshot — so a session started from the SDK stays configured\n * when reopened elsewhere.\n */\nexport function buildStatewireRunConfig(\n options: StatewireRunConfigOptions = {},\n): Record<string, unknown> {\n const collabAgentId = resolveAgentProp({ agent: options.agent });\n const collabChannelId =\n collabAgentId && options.channel?.trim() ? options.channel.trim() : undefined;\n // With a collab agent selected and no explicit model, send NO model: the\n // backend overlay then applies the model the agent's author configured\n // instead of the SDK default overriding it.\n const model = options.model ?? (collabAgentId ? undefined : DEFAULT_STATEWIRE_MODEL);\n const enabledTools = Array.from(\n new Set([...(options.tools ?? []), ...(options.frontendToolIds ?? [])]),\n );\n const systemPrompt = options.systemPrompt?.trim() ?? '';\n\n return {\n custom: {\n runtime: DEEP_AGENT_RUNTIME_MARKER,\n // With a collab channel selected, omit the SDK's own 'web' stamp: the\n // request's explicit start_channel would win over the backend overlay's\n // kind-mapped provenance (a custom SMS channel must stamp 'sms').\n ...(collabChannelId ? {} : { start_channel: STATEWIRE_START_CHANNEL }),\n ...(model ? { model } : {}),\n // An empty list is behavior-identical to an absent key for the parser\n // (both coerce to []), but the collab-agent overlay merges request keys\n // OVER the agent definition — so an explicit empty list would erase the\n // agent's configured tools. Omit it when empty.\n ...(enabledTools.length > 0 ? { enabled_tools: enabledTools } : {}),\n panel_config: {\n // A collab agent's thread with no explicit model names none here\n // either: agora stores this panel on the thread's first run and\n // replays it to every client, and a stored SDK default would show as\n // the session's model while the agent's own model runs.\n ...(model ? { model } : {}),\n enabled_tools: enabledTools,\n system_prompt: systemPrompt,\n run_label: STATEWIRE_RUN_LABEL,\n },\n ...(collabAgentId ? { collab_agent_id: collabAgentId } : {}),\n ...(collabChannelId ? { collab_channel_id: collabChannelId } : {}),\n ...(systemPrompt ? { system_prompt: systemPrompt } : {}),\n ...(options.workbench?.length ? { workbench: options.workbench } : {}),\n ...(options.knowledgeBase?.length\n ? { knowledge_base: options.knowledgeBase }\n : {}),\n ...(options.appId ? { app_id: options.appId } : {}),\n ...(options.clientTools?.length ? { client_tools: options.clientTools } : {}),\n ...(options.extraRunConfig ?? {}),\n },\n };\n}\n"],"names":[],"mappings":";;AACA,MAAM,4BAA4B;AAIlC,MAAM,0BAA0B;AAIhC,MAAM,sBAAsB;AAGrB,MAAM,0BAA0B;AAGhC,MAAM,0BAA0B;AAGhC,MAAM,yBAAyB;AAG/B,SAAS,oBAAoB,OAAiD;AACnF,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,WAAW,uBAAuB,EAAG,QAAO;AACpF,QAAM,UAAU,MAAM,MAAM,wBAAwB,MAAM,EAAE,KAAA;AAC5D,SAAO,WAAW;AACpB;AAWO,SAAS,iBAAiB,EAAE,SAA8D;AAC/F,MAAI,UAAU,UAAa,UAAU,QAAQ,MAAM,KAAA,MAAW,GAAI,QAAO;AACzE,QAAM,UAAU,oBAAoB,MAAM,KAAA,CAAM;AAChD,MAAI,QAAS,QAAO;AACpB,QAAM,IAAI;AAAA,IACR,0CAA0C,KAAK,UAAU,KAAK,CAAC,iCAAiC,sBAAsB;AAAA,EAAA;AAE1H;AA0CO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAGuB;AACrB,MAAI,mBAAmB,UAAa,OAAO,OAAO,gBAAgB,OAAO,GAAG;AAC1E,UAAM,WAAW,eAAe;AAChC,WAAO,OAAO,aAAa,YAAY,aAAa,KAAK,WAAW;AAAA,EACtE;AACA,SAAO,UAAU,KAAK,SAAY;AACpC;AAYO,SAAS,wBACd,UAAqC,IACZ;;AACzB,QAAM,gBAAgB,iBAAiB,EAAE,OAAO,QAAQ,OAAO;AAC/D,QAAM,kBACJ,mBAAiB,aAAQ,YAAR,mBAAiB,UAAS,QAAQ,QAAQ,KAAA,IAAS;AAItE,QAAM,QAAQ,QAAQ,UAAU,gBAAgB,SAAY;AAC5D,QAAM,eAAe,MAAM;AAAA,IACzB,oBAAI,IAAI,CAAC,GAAI,QAAQ,SAAS,CAAA,GAAK,GAAI,QAAQ,mBAAmB,EAAG,CAAC;AAAA,EAAA;AAExE,QAAM,iBAAe,aAAQ,iBAAR,mBAAsB,WAAU;AAErD,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,SAAS;AAAA;AAAA;AAAA;AAAA,MAIT,GAAI,kBAAkB,CAAA,IAAK,EAAE,eAAe,wBAAA;AAAA,MAC5C,GAAI,QAAQ,EAAE,MAAA,IAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAKxB,GAAI,aAAa,SAAS,IAAI,EAAE,eAAe,aAAA,IAAiB,CAAA;AAAA,MAChE,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA,QAKZ,GAAI,QAAQ,EAAE,MAAA,IAAU,CAAA;AAAA,QACxB,eAAe;AAAA,QACf,eAAe;AAAA,QACf,WAAW;AAAA,MAAA;AAAA,MAEb,GAAI,gBAAgB,EAAE,iBAAiB,cAAA,IAAkB,CAAA;AAAA,MACzD,GAAI,kBAAkB,EAAE,mBAAmB,gBAAA,IAAoB,CAAA;AAAA,MAC/D,GAAI,eAAe,EAAE,eAAe,aAAA,IAAiB,CAAA;AAAA,MACrD,KAAI,aAAQ,cAAR,mBAAmB,UAAS,EAAE,WAAW,QAAQ,UAAA,IAAc,CAAA;AAAA,MACnE,KAAI,aAAQ,kBAAR,mBAAuB,UACvB,EAAE,gBAAgB,QAAQ,cAAA,IAC1B,CAAA;AAAA,MACJ,GAAI,QAAQ,QAAQ,EAAE,QAAQ,QAAQ,MAAA,IAAU,CAAA;AAAA,MAChD,KAAI,aAAQ,gBAAR,mBAAqB,UAAS,EAAE,cAAc,QAAQ,YAAA,IAAgB,CAAA;AAAA,MAC1E,GAAI,QAAQ,kBAAkB,CAAA;AAAA,IAAC;AAAA,EACjC;AAEJ;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"statewire-run-config.cjs","sources":["../../src/runtime/statewire-run-config.ts"],"sourcesContent":["/** Runtime marker the deep-agent session substrate stamps on sessions. */\nconst DEEP_AGENT_RUNTIME_MARKER = 'athena_deep_agent';\n\n/** Session-origin channel. The backend clamps unknown values to 'web', which\n * is how every SDK-started session has always classified. */\nconst STATEWIRE_START_CHANNEL = 'web';\n\n/** Session label persisted in the thread's panel config (mirrors\n * 'Athena Chrome' / 'Athena mobile' in the sibling statewire hosts). */\nconst STATEWIRE_RUN_LABEL = 'Athena SDK';\n\n/** Deep-agent default model — matches the Olympus and mobile statewire hosts. */\nexport const DEFAULT_STATEWIRE_MODEL = 'claude-sonnet-5-5';\n\n/** Agent-id namespace for collab agents (`collab_agent:<asset_id>`). */\nexport const COLLAB_AGENT_REF_PREFIX = 'collab_agent:';\n\n/** The only shape the `agent` prop accepts, for error messages and docs. */\nexport const COLLAB_AGENT_REF_SHAPE = 'collab_agent:<asset_id>';\n\n/** The asset id when `agent` is a `collab_agent:<asset_id>` ref, else null. */\nexport function parseCollabAgentRef(agent: string | undefined | null): string | null {\n if (typeof agent !== 'string' || !agent.startsWith(COLLAB_AGENT_REF_PREFIX)) return null;\n const assetId = agent.slice(COLLAB_AGENT_REF_PREFIX.length).trim();\n return assetId || null;\n}\n\n/**\n * Resolve the provider's `agent` prop to a collab-agent asset id.\n *\n * An omitted or blank `agent` means \"no agent selected\" — the thread runs the\n * Athena deep agent — and resolves to `null`. A `collab_agent:<asset_id>` ref\n * resolves to the asset id. Any other non-blank value throws: the statewire\n * host cannot honour it, and a selector that is silently dropped would let a\n * misconfigured deployment ship answers from the wrong agent.\n */\nexport function resolveAgentProp({ agent }: { agent: string | undefined | null }): string | null {\n if (agent === undefined || agent === null || agent.trim() === '') return null;\n const assetId = parseCollabAgentRef(agent.trim());\n if (assetId) return assetId;\n throw new Error(\n `[AthenaSDK] Unsupported \\`agent\\` prop ${JSON.stringify(agent)}. The only accepted shape is '${COLLAB_AGENT_REF_SHAPE}' (the asset id of a published collab agent). Named agents are not supported — the statewire host always runs the Athena deep agent; omit \\`agent\\` to use it.`,\n );\n}\n\nexport interface StatewireRunConfigOptions {\n /** LLM model identifier. Defaults to the deep-agent default model — unless a\n * collab agent is selected and its author set one (the backend overlay\n * applies the agent's model when the run sends none), in which case no\n * model is sent so the agent's choice wins. */\n model?: string;\n /** Agent selector. Only the `collab_agent:<asset_id>` shape is accepted: the\n * run executes AS that published collab agent (prompt, tools, model\n * default). Any other non-empty value throws (see {@link resolveAgentProp}). */\n agent?: string;\n /** Collab-agent channel id (built-in like `email`/`sms`, or a user-defined\n * custom channel id) selecting that channel's configuration overrides.\n * Sent as `collab_channel_id`; only meaningful with a collab `agent`. */\n channel?: string;\n /** The agent's mode this send runs in, picked from the chat's mode menu\n * (`AthenaProvider` `modes`): `base` or a mode's id. Sent as\n * `collab_channel_id` in place of `channel`, which then rides as\n * `collab_entry_channel_id` so a session the pick starts still enters\n * through the channel (its switch list, lock and thread list). Only\n * meaningful with a collab `agent`. */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier, passed through as app_id. */\n appId?: string;\n /** Client-executed tool declarations, passed through as client_tools. The\n * deep-agent runtime binds each as an interrupt-backed tool the client\n * answers via the statewire client-tool bridge. */\n clientTools?: Record<string, unknown>[];\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n}\n\n/**\n * The model a send carries for the host's `model` and `extraRunConfig`, with\n * the precedence {@link buildStatewireRunConfig} applies: `extraRunConfig` is\n * merged last, so its `model` key (when it has one) wins over the `model`\n * prop. `undefined` when the send carries no explicit model.\n */\nexport function effectiveStatewireModel({\n model,\n extraRunConfig,\n}: {\n model: string | undefined;\n extraRunConfig: Record<string, unknown> | undefined;\n}): string | undefined {\n if (extraRunConfig !== undefined && Object.hasOwn(extraRunConfig, 'model')) {\n const override = extraRunConfig.model;\n return typeof override === 'string' && override !== '' ? override : undefined;\n }\n return model === '' ? undefined : model;\n}\n\n/**\n * Build the `runConfig` a deep-agent statewire run is driven by. Commands\n * travel bare on the statewire channel, so this payload rides the ambient\n * model context (`config.runConfig`) instead of the request body.\n *\n * The flattened `custom` fields drive the run, while `panel_config` carries a\n * minimal panel state the backend persists per thread and replays on the\n * statewire snapshot — so a session started from the SDK stays configured\n * when reopened elsewhere.\n */\nexport function buildStatewireRunConfig(\n options: StatewireRunConfigOptions = {},\n): Record<string, unknown> {\n const collabAgentId = resolveAgentProp({ agent: options.agent });\n const channel = collabAgentId ? options.channel?.trim() || undefined : undefined;\n const mode = collabAgentId ? options.mode?.trim() || undefined : undefined;\n const collabChannelId = mode ?? channel;\n const collabEntryChannelId = mode && channel && mode !== channel ? channel : undefined;\n // With a collab agent selected and no explicit model, send NO model: the\n // backend overlay then applies the model the agent's author configured\n // instead of the SDK default overriding it.\n const model = options.model ?? (collabAgentId ? undefined : DEFAULT_STATEWIRE_MODEL);\n const enabledTools = Array.from(\n new Set([...(options.tools ?? []), ...(options.frontendToolIds ?? [])]),\n );\n const systemPrompt = options.systemPrompt?.trim() ?? '';\n\n return {\n custom: {\n runtime: DEEP_AGENT_RUNTIME_MARKER,\n // With a collab channel selected, omit the SDK's own 'web' stamp: the\n // request's explicit start_channel would win over the backend overlay's\n // kind-mapped provenance (a custom SMS channel must stamp 'sms').\n ...(collabChannelId ? {} : { start_channel: STATEWIRE_START_CHANNEL }),\n ...(model ? { model } : {}),\n // An empty list is behavior-identical to an absent key for the parser\n // (both coerce to []), but the collab-agent overlay merges request keys\n // OVER the agent definition — so an explicit empty list would erase the\n // agent's configured tools. Omit it when empty.\n ...(enabledTools.length > 0 ? { enabled_tools: enabledTools } : {}),\n panel_config: {\n // A collab agent's thread with no explicit model names none here\n // either: agora stores this panel on the thread's first run and\n // replays it to every client, and a stored SDK default would show as\n // the session's model while the agent's own model runs.\n ...(model ? { model } : {}),\n enabled_tools: enabledTools,\n system_prompt: systemPrompt,\n run_label: STATEWIRE_RUN_LABEL,\n },\n ...(collabAgentId ? { collab_agent_id: collabAgentId } : {}),\n ...(collabChannelId ? { collab_channel_id: collabChannelId } : {}),\n ...(collabEntryChannelId ? { collab_entry_channel_id: collabEntryChannelId } : {}),\n ...(systemPrompt ? { system_prompt: systemPrompt } : {}),\n ...(options.workbench?.length ? { workbench: options.workbench } : {}),\n ...(options.knowledgeBase?.length\n ? { knowledge_base: options.knowledgeBase }\n : {}),\n ...(options.appId ? { app_id: options.appId } : {}),\n ...(options.clientTools?.length ? { client_tools: options.clientTools } : {}),\n ...(options.extraRunConfig ?? {}),\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,13 @@ export interface StatewireRunConfigOptions {
|
|
|
32
32
|
* custom channel id) selecting that channel's configuration overrides.
|
|
33
33
|
* Sent as `collab_channel_id`; only meaningful with a collab `agent`. */
|
|
34
34
|
channel?: string;
|
|
35
|
+
/** The agent's mode this send runs in, picked from the chat's mode menu
|
|
36
|
+
* (`AthenaProvider` `modes`): `base` or a mode's id. Sent as
|
|
37
|
+
* `collab_channel_id` in place of `channel`, which then rides as
|
|
38
|
+
* `collab_entry_channel_id` so a session the pick starts still enters
|
|
39
|
+
* through the channel (its switch list, lock and thread list). Only
|
|
40
|
+
* meaningful with a collab `agent`. */
|
|
41
|
+
mode?: string;
|
|
35
42
|
/** Backend toolkit IDs to enable. */
|
|
36
43
|
tools?: string[];
|
|
37
44
|
/** Frontend tool IDs to include in enabled_tools. */
|
|
@@ -28,14 +28,17 @@ function effectiveStatewireModel({
|
|
|
28
28
|
return model === "" ? void 0 : model;
|
|
29
29
|
}
|
|
30
30
|
function buildStatewireRunConfig(options = {}) {
|
|
31
|
-
var _a, _b, _c, _d, _e;
|
|
31
|
+
var _a, _b, _c, _d, _e, _f;
|
|
32
32
|
const collabAgentId = resolveAgentProp({ agent: options.agent });
|
|
33
|
-
const
|
|
33
|
+
const channel = collabAgentId ? ((_a = options.channel) == null ? void 0 : _a.trim()) || void 0 : void 0;
|
|
34
|
+
const mode = collabAgentId ? ((_b = options.mode) == null ? void 0 : _b.trim()) || void 0 : void 0;
|
|
35
|
+
const collabChannelId = mode ?? channel;
|
|
36
|
+
const collabEntryChannelId = mode && channel && mode !== channel ? channel : void 0;
|
|
34
37
|
const model = options.model ?? (collabAgentId ? void 0 : DEFAULT_STATEWIRE_MODEL);
|
|
35
38
|
const enabledTools = Array.from(
|
|
36
39
|
/* @__PURE__ */ new Set([...options.tools ?? [], ...options.frontendToolIds ?? []])
|
|
37
40
|
);
|
|
38
|
-
const systemPrompt = ((
|
|
41
|
+
const systemPrompt = ((_c = options.systemPrompt) == null ? void 0 : _c.trim()) ?? "";
|
|
39
42
|
return {
|
|
40
43
|
custom: {
|
|
41
44
|
runtime: DEEP_AGENT_RUNTIME_MARKER,
|
|
@@ -61,11 +64,12 @@ function buildStatewireRunConfig(options = {}) {
|
|
|
61
64
|
},
|
|
62
65
|
...collabAgentId ? { collab_agent_id: collabAgentId } : {},
|
|
63
66
|
...collabChannelId ? { collab_channel_id: collabChannelId } : {},
|
|
67
|
+
...collabEntryChannelId ? { collab_entry_channel_id: collabEntryChannelId } : {},
|
|
64
68
|
...systemPrompt ? { system_prompt: systemPrompt } : {},
|
|
65
|
-
...((
|
|
66
|
-
...((
|
|
69
|
+
...((_d = options.workbench) == null ? void 0 : _d.length) ? { workbench: options.workbench } : {},
|
|
70
|
+
...((_e = options.knowledgeBase) == null ? void 0 : _e.length) ? { knowledge_base: options.knowledgeBase } : {},
|
|
67
71
|
...options.appId ? { app_id: options.appId } : {},
|
|
68
|
-
...((
|
|
72
|
+
...((_f = options.clientTools) == null ? void 0 : _f.length) ? { client_tools: options.clientTools } : {},
|
|
69
73
|
...options.extraRunConfig ?? {}
|
|
70
74
|
}
|
|
71
75
|
};
|