negotium 0.19.1 → 0.20.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/dist/agent-helpers.js +1175 -207
- package/dist/agent-helpers.js.map +30 -22
- package/dist/background-bash.js.map +2 -2
- package/dist/browser-runtime.js.map +2 -2
- package/dist/{chunk-22mtr8np.js → chunk-7he9nzfa.js} +1 -1
- package/dist/{chunk-y8rtedjb.js → chunk-ad6xqvqy.js} +3 -3
- package/dist/{chunk-y8rtedjb.js.map → chunk-ad6xqvqy.js.map} +3 -3
- package/dist/{chunk-m7qc729a.js → chunk-hh3f0yr7.js} +3 -3
- package/dist/{chunk-m7qc729a.js.map → chunk-hh3f0yr7.js.map} +2 -2
- package/dist/{chunk-q2s5f70x.js → chunk-nm59q3em.js} +1 -1
- package/dist/{chunk-3d1pjgfw.js → chunk-w3c6fc0p.js} +287 -10
- package/dist/{chunk-3d1pjgfw.js.map → chunk-w3c6fc0p.js.map} +11 -8
- package/dist/hosted-agent.js +290 -12
- package/dist/hosted-agent.js.map +10 -7
- package/dist/main.js +4628 -2719
- package/dist/main.js.map +52 -41
- package/dist/mcp-factories.js +1195 -166
- package/dist/mcp-factories.js.map +30 -22
- package/dist/media.js.map +2 -2
- package/dist/prompts.js +23 -3
- package/dist/prompts.js.map +5 -5
- package/dist/query-runtime.js.map +4 -4
- package/dist/registry.js +3 -3
- package/dist/rollout.js +2 -2
- package/dist/runtime/src/agents/explicit-agent-switch.ts +101 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +126 -43
- package/dist/runtime/src/agents/model-catalog.ts +27 -2
- package/dist/runtime/src/agents/public-helpers.ts +5 -0
- package/dist/runtime/src/agents/self-config-core.ts +28 -49
- package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +34 -8
- package/dist/runtime/src/index.ts +75 -0
- package/dist/runtime/src/mcp/peer-bridge.ts +6 -1
- package/dist/runtime/src/mcp/runtime-spec.ts +145 -10
- package/dist/runtime/src/mcp/session-comm/actor-policy.ts +87 -0
- package/dist/runtime/src/mcp/session-comm/context.ts +42 -1
- package/dist/runtime/src/mcp/session-comm/default-host.ts +178 -2
- package/dist/runtime/src/mcp/session-comm/hub-remote-session.ts +553 -0
- package/dist/runtime/src/mcp/session-comm/limits.ts +8 -0
- package/dist/runtime/src/mcp/session-comm/peer-forward.ts +78 -3
- package/dist/runtime/src/mcp/session-comm/server.ts +167 -4
- package/dist/runtime/src/mcp/session-comm/topic-catalog.ts +7 -0
- package/dist/runtime/src/mcp/session-comm/topics.ts +32 -10
- package/dist/runtime/src/mcp-runtime-host.ts +2 -1
- package/dist/runtime/src/node-host.ts +26 -0
- package/dist/runtime/src/platform/config.ts +1 -1
- package/dist/runtime/src/platform/mcp-config.ts +56 -2
- package/dist/runtime/src/query/active-rooms.ts +6 -0
- package/dist/runtime/src/runtime/actor-topic-reach.ts +85 -0
- package/dist/runtime/src/runtime/actor-topic-scope.ts +175 -0
- package/dist/runtime/src/runtime/ask-callbacks.ts +73 -20
- package/dist/runtime/src/runtime/gateway-capabilities.ts +22 -0
- package/dist/runtime/src/runtime/remote-session-grant.ts +221 -0
- package/dist/runtime/src/runtime/remote-session-inbox.ts +476 -0
- package/dist/runtime/src/runtime/remote-session-reply-outbox.ts +156 -0
- package/dist/runtime/src/runtime/turn-runner.ts +157 -33
- package/dist/runtime/src/runtime-gateway.ts +20 -0
- package/dist/runtime/src/storage/api-topics.ts +22 -3
- package/dist/runtime/src/storage/remote-session.ts +526 -0
- package/dist/runtime/src/storage/runtime-turn-requests.ts +75 -2
- package/dist/runtime/src/storage/token-stats.ts +4 -1
- package/dist/runtime/src/topics/derive.ts +3 -0
- package/dist/runtime/src/types/api.ts +6 -0
- package/dist/runtime/src/types.ts +42 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-gateway.js.map +2 -2
- package/dist/runtime-helpers.js.map +2 -2
- package/dist/storage.js +20 -5
- package/dist/storage.js.map +5 -5
- package/dist/types/packages/core/src/agents/explicit-agent-switch.d.ts +26 -0
- package/dist/types/packages/core/src/agents/mcp-tools/spawn-subagent.d.ts +12 -2
- package/dist/types/packages/core/src/agents/public-helpers.d.ts +1 -0
- package/dist/types/packages/core/src/agents/self-config-core.d.ts +15 -1
- package/dist/types/packages/core/src/mcp/peer-bridge.d.ts +6 -1
- package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +69 -2
- package/dist/types/packages/core/src/mcp/session-comm/context.d.ts +17 -1
- package/dist/types/packages/core/src/mcp/session-comm/hub-remote-session.d.ts +149 -0
- package/dist/types/packages/core/src/mcp/session-comm/limits.d.ts +8 -0
- package/dist/types/packages/core/src/mcp/session-comm/peer-forward.d.ts +23 -2
- package/dist/types/packages/core/src/mcp/session-comm/topic-catalog.d.ts +6 -0
- package/dist/types/packages/core/src/platform/config.d.ts +1 -1
- package/dist/types/packages/core/src/platform/mcp-config.d.ts +10 -1
- package/dist/types/packages/core/src/query/active-rooms.d.ts +6 -0
- package/dist/types/packages/core/src/runtime/actor-topic-reach.d.ts +49 -0
- package/dist/types/packages/core/src/runtime/actor-topic-scope.d.ts +91 -0
- package/dist/types/packages/core/src/runtime/ask-callbacks.d.ts +8 -2
- package/dist/types/packages/core/src/runtime/remote-session-grant.d.ts +91 -0
- package/dist/types/packages/core/src/runtime/remote-session-inbox.d.ts +83 -0
- package/dist/types/packages/core/src/runtime/remote-session-reply-outbox.d.ts +37 -0
- package/dist/types/packages/core/src/runtime/turn-runner.d.ts +22 -3
- package/dist/types/packages/core/src/runtime-gateway.d.ts +20 -0
- package/dist/types/packages/core/src/storage/api-topics.d.ts +3 -0
- package/dist/types/packages/core/src/storage/remote-session.d.ts +135 -0
- package/dist/types/packages/core/src/storage/runtime-turn-requests.d.ts +12 -1
- package/dist/types/packages/core/src/topics/derive.d.ts +2 -0
- package/dist/types/packages/core/src/types/api.d.ts +6 -0
- package/dist/types/packages/core/src/types.d.ts +40 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js.map +2 -2
- package/package.json +3 -3
- /package/dist/{chunk-22mtr8np.js.map → chunk-7he9nzfa.js.map} +0 -0
- /package/dist/{chunk-q2s5f70x.js.map → chunk-nm59q3em.js.map} +0 -0
package/dist/agent-helpers.js
CHANGED
|
@@ -480,7 +480,7 @@ function resolveWhisperBackend() {
|
|
|
480
480
|
function codexAuthFilePath() {
|
|
481
481
|
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
|
482
482
|
}
|
|
483
|
-
var HOME, PROJECT_ROOT, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BINARY_EXTENSION, BROWSER_RS_VERSION = "v0.6.0", BROWSER_RS_MIN_SECURE_VERSION = "0.2.1", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.10", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER_PATH, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, NODE_ID, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, RUNTIME_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, ABSOLUTE_PATH_PREFIX, FILE_EXTENSIONS_REGEX, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_HAIKU = "claude-haiku-4-5-20251001", MODEL_FABLE = "claude-fable-5-1", MODEL_DEEPSEEK_V4_PRO = "deepseek-v4-pro", MODEL_DEEPSEEK_V4_FLASH = "deepseek-v4-flash", FALLBACK_AGENT, FALLBACK_MODEL, DEFAULT_TOPIC_EFFORT, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, WHISPER_BACKEND, MLX_WHISPER_BIN, WHISPER_MODEL_MLX, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
483
|
+
var HOME, PROJECT_ROOT, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BINARY_EXTENSION, BROWSER_RS_VERSION = "v0.6.0", BROWSER_RS_MIN_SECURE_VERSION = "0.2.1", BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000, SNIPPETS_API_URL, BROWSER_RS_BIN, BASH_RS_VERSION = "v0.1.10", BASH_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER_PATH, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, DECISION_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, NODE_ID, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, RUNTIME_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, BASHRS_SPILL_ROOT, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, ABSOLUTE_PATH_PREFIX, FILE_EXTENSIONS_REGEX, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5-5", MODEL_HAIKU = "claude-haiku-4-5-20251001", MODEL_FABLE = "claude-fable-5-1", MODEL_DEEPSEEK_V4_PRO = "deepseek-v4-pro", MODEL_DEEPSEEK_V4_FLASH = "deepseek-v4-flash", FALLBACK_AGENT, FALLBACK_MODEL, DEFAULT_TOPIC_EFFORT, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, WHISPER_BACKEND, MLX_WHISPER_BIN, WHISPER_MODEL_MLX, _envMaxTellDepth, MAX_TELL_DEPTH;
|
|
484
484
|
var init_config = __esm(() => {
|
|
485
485
|
init_config_helpers();
|
|
486
486
|
init_logger();
|
|
@@ -593,6 +593,72 @@ var init_config = __esm(() => {
|
|
|
593
593
|
MAX_TELL_DEPTH = Number.isInteger(_envMaxTellDepth) && _envMaxTellDepth > 0 ? _envMaxTellDepth : 20;
|
|
594
594
|
});
|
|
595
595
|
|
|
596
|
+
// ../../packages/core/src/agents/explicit-agent-switch.ts
|
|
597
|
+
function escapeRegExp(value) {
|
|
598
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
599
|
+
}
|
|
600
|
+
function agentAliases(agent) {
|
|
601
|
+
switch (agent) {
|
|
602
|
+
case "codex":
|
|
603
|
+
return [
|
|
604
|
+
"codex",
|
|
605
|
+
"\uCF54\uB371\uC2A4",
|
|
606
|
+
"gpt-6-luna",
|
|
607
|
+
"gpt-6-sol",
|
|
608
|
+
"gpt-5.6-luna",
|
|
609
|
+
"gpt-5.6-terra",
|
|
610
|
+
"gpt-5.6-sol",
|
|
611
|
+
"gpt-6-astra"
|
|
612
|
+
];
|
|
613
|
+
case "claude":
|
|
614
|
+
return ["claude", "\uD074\uB85C\uB4DC", "sonnet", "opus", "fable"];
|
|
615
|
+
case "maestro":
|
|
616
|
+
return [
|
|
617
|
+
"maestro",
|
|
618
|
+
"\uB9C8\uC5D0\uC2A4\uD2B8\uB85C",
|
|
619
|
+
"\uBA54\uC2A4\uD2B8\uB85C",
|
|
620
|
+
"deepseek",
|
|
621
|
+
"deepseek-pro",
|
|
622
|
+
"kimi",
|
|
623
|
+
"kimi-pro",
|
|
624
|
+
"kimi-k3",
|
|
625
|
+
"kimi-code",
|
|
626
|
+
"kimi-k2.7-code",
|
|
627
|
+
"\uB525\uC2DC\uD06C",
|
|
628
|
+
"\uD0A4\uBBF8"
|
|
629
|
+
];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function hasExplicitAgentSwitchRequest(prompt, agent) {
|
|
633
|
+
if (!prompt?.trim())
|
|
634
|
+
return false;
|
|
635
|
+
const text = prompt.toLowerCase().replace(/\s+/g, " ").trim();
|
|
636
|
+
const target = `(?:${agentAliases(agent).map(escapeRegExp).join("|")})`;
|
|
637
|
+
const switchVerb = "(?:\uBC14\uAFD4|\uBC14\uAFD4\uC918|\uBCC0\uACBD|\uBCC0\uACBD\uD574|\uC804\uD658|\uC804\uD658\uD574|\uC124\uC815|\uC124\uC815\uD574|\uC368\uC918|\uC0AC\uC6A9|\uAC00|switch|change|set|use)";
|
|
638
|
+
const switchSubject = "(?:agent|runtime|model|\uC5D0\uC774\uC804\uD2B8|\uB7F0\uD0C0\uC784|\uBAA8\uB378)";
|
|
639
|
+
return [
|
|
640
|
+
new RegExp(`^/(?:agent|runtime)\\s+${target}(?:\\s|$)`, "iu"),
|
|
641
|
+
new RegExp(`${target}\\s*(?:\uB85C|\uC73C\uB85C)\\s*.{0,16}${switchVerb}`, "iu"),
|
|
642
|
+
new RegExp(`${switchSubject}.{0,24}${target}.{0,24}${switchVerb}`, "iu"),
|
|
643
|
+
new RegExp(`${switchVerb}.{0,24}${switchSubject}.{0,24}${target}`, "iu"),
|
|
644
|
+
new RegExp(`(?:switch|change|set|use).{0,24}${target}`, "iu")
|
|
645
|
+
].some((pattern) => pattern.test(text));
|
|
646
|
+
}
|
|
647
|
+
function explicitAgentSwitchTargets(prompt) {
|
|
648
|
+
if (!prompt?.trim())
|
|
649
|
+
return [];
|
|
650
|
+
return SUPPORTED_AGENTS.filter((agent) => hasExplicitAgentSwitchRequest(prompt, agent));
|
|
651
|
+
}
|
|
652
|
+
function isExplicitAgentSwitchTargets(value) {
|
|
653
|
+
return Array.isArray(value) && value.length <= SUPPORTED_AGENTS.length && value.every((item, index) => isAgentKindUnique(value, item, index));
|
|
654
|
+
}
|
|
655
|
+
function isAgentKindUnique(list, item, index) {
|
|
656
|
+
return typeof item === "string" && SUPPORTED_AGENTS.includes(item) && list.indexOf(item) === index;
|
|
657
|
+
}
|
|
658
|
+
var init_explicit_agent_switch = __esm(() => {
|
|
659
|
+
init_types();
|
|
660
|
+
});
|
|
661
|
+
|
|
596
662
|
// ../../packages/core/src/mcp/canonical-bridge-config.ts
|
|
597
663
|
function turnKey(scope) {
|
|
598
664
|
return JSON.stringify([
|
|
@@ -620,6 +686,232 @@ var init_canonical_bridge_config = __esm(() => {
|
|
|
620
686
|
turnLeases = new Map;
|
|
621
687
|
});
|
|
622
688
|
|
|
689
|
+
// ../../packages/core/src/runtime/actor-topic-scope.ts
|
|
690
|
+
function idList(value, name) {
|
|
691
|
+
if (!Array.isArray(value))
|
|
692
|
+
return `${name} must be an array of topic ids`;
|
|
693
|
+
if (value.length > ACTOR_TOPIC_SCOPE_LIMITS.maxIdsPerList) {
|
|
694
|
+
return `${name} has ${value.length} ids; at most ${ACTOR_TOPIC_SCOPE_LIMITS.maxIdsPerList} are allowed`;
|
|
695
|
+
}
|
|
696
|
+
const ids = new Set;
|
|
697
|
+
for (const item of value) {
|
|
698
|
+
if (typeof item !== "string")
|
|
699
|
+
return `${name} must contain only strings`;
|
|
700
|
+
const id = item.trim();
|
|
701
|
+
if (!id)
|
|
702
|
+
return `${name} must not contain empty ids`;
|
|
703
|
+
if (id.length > ACTOR_TOPIC_SCOPE_LIMITS.maxIdLength) {
|
|
704
|
+
return `${name} contains an id longer than ${ACTOR_TOPIC_SCOPE_LIMITS.maxIdLength} characters`;
|
|
705
|
+
}
|
|
706
|
+
ids.add(id);
|
|
707
|
+
}
|
|
708
|
+
return [...ids];
|
|
709
|
+
}
|
|
710
|
+
function validateActorTopicScope(value) {
|
|
711
|
+
if (value === undefined)
|
|
712
|
+
return { ok: true, scope: undefined };
|
|
713
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
714
|
+
return {
|
|
715
|
+
ok: false,
|
|
716
|
+
error: "actorTopicScope must be { visibleNodeTopicIds: string[], ownedNodeTopicIds: string[] }"
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
const proto = Object.getPrototypeOf(value);
|
|
720
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
721
|
+
return { ok: false, error: "actorTopicScope must be a plain object" };
|
|
722
|
+
}
|
|
723
|
+
const keys = Object.keys(value);
|
|
724
|
+
const unknown = keys.filter((key) => !SCOPE_KEYS.includes(key));
|
|
725
|
+
if (unknown.length || keys.length !== SCOPE_KEYS.length) {
|
|
726
|
+
return {
|
|
727
|
+
ok: false,
|
|
728
|
+
error: unknown.length ? `actorTopicScope has unexpected key(s): ${unknown.join(", ")}` : "actorTopicScope must have both visibleNodeTopicIds and ownedNodeTopicIds"
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const record = value;
|
|
732
|
+
const visible = idList(record.visibleNodeTopicIds, "actorTopicScope.visibleNodeTopicIds");
|
|
733
|
+
if (typeof visible === "string")
|
|
734
|
+
return { ok: false, error: visible };
|
|
735
|
+
const owned = idList(record.ownedNodeTopicIds, "actorTopicScope.ownedNodeTopicIds");
|
|
736
|
+
if (typeof owned === "string")
|
|
737
|
+
return { ok: false, error: owned };
|
|
738
|
+
const scope = { visibleNodeTopicIds: visible, ownedNodeTopicIds: owned };
|
|
739
|
+
const bytes = Buffer.byteLength(JSON.stringify(scope), "utf-8");
|
|
740
|
+
if (bytes > ACTOR_TOPIC_SCOPE_LIMITS.maxSerializedBytes) {
|
|
741
|
+
return {
|
|
742
|
+
ok: false,
|
|
743
|
+
error: `actorTopicScope is ${bytes} bytes serialized; at most ${ACTOR_TOPIC_SCOPE_LIMITS.maxSerializedBytes} are allowed`
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return { ok: true, scope };
|
|
747
|
+
}
|
|
748
|
+
function parseActorTopicScope(value) {
|
|
749
|
+
const validated = validateActorTopicScope(value);
|
|
750
|
+
return validated.ok ? validated.scope : null;
|
|
751
|
+
}
|
|
752
|
+
function actorTopicScopeFrom(value) {
|
|
753
|
+
return parseActorTopicScope(value) ?? undefined;
|
|
754
|
+
}
|
|
755
|
+
function intersectActorTopicScopes(scopes) {
|
|
756
|
+
if (scopes.length === 0 || scopes.some((scope) => scope === undefined))
|
|
757
|
+
return;
|
|
758
|
+
const [first, ...rest] = scopes;
|
|
759
|
+
const keep = (key) => first[key].filter((id) => rest.every((scope) => scope[key].includes(id)));
|
|
760
|
+
return {
|
|
761
|
+
visibleNodeTopicIds: keep("visibleNodeTopicIds"),
|
|
762
|
+
ownedNodeTopicIds: keep("ownedNodeTopicIds")
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function encodeActorTopicScopeArg(scope) {
|
|
766
|
+
const validated = validateActorTopicScope(scope);
|
|
767
|
+
if (!validated.ok)
|
|
768
|
+
throw new Error(validated.error);
|
|
769
|
+
if (!validated.scope)
|
|
770
|
+
throw new Error("actorTopicScope is required");
|
|
771
|
+
return Buffer.from(JSON.stringify(validated.scope), "utf-8").toString("base64url");
|
|
772
|
+
}
|
|
773
|
+
var ACTOR_TOPIC_SCOPE_LIMITS, SCOPE_KEYS;
|
|
774
|
+
var init_actor_topic_scope = __esm(() => {
|
|
775
|
+
ACTOR_TOPIC_SCOPE_LIMITS = Object.freeze({
|
|
776
|
+
maxIdsPerList: 200,
|
|
777
|
+
maxIdLength: 128,
|
|
778
|
+
maxSerializedBytes: 8 * 1024
|
|
779
|
+
});
|
|
780
|
+
SCOPE_KEYS = ["visibleNodeTopicIds", "ownedNodeTopicIds"];
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// ../../packages/core/src/runtime/remote-session-grant.ts
|
|
784
|
+
function normalizeHubUrl(raw, name = "remoteSession.hubUrl") {
|
|
785
|
+
if (raw.length > REMOTE_SESSION_GRANT_LIMITS.maxHubUrlLength) {
|
|
786
|
+
return {
|
|
787
|
+
error: `${name} is longer than ${REMOTE_SESSION_GRANT_LIMITS.maxHubUrlLength} characters`
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
let url;
|
|
791
|
+
try {
|
|
792
|
+
url = new URL(raw);
|
|
793
|
+
} catch {
|
|
794
|
+
return { error: `${name} must be an absolute URL` };
|
|
795
|
+
}
|
|
796
|
+
if (url.username || url.password) {
|
|
797
|
+
return { error: `${name} must not carry credentials` };
|
|
798
|
+
}
|
|
799
|
+
if (url.search || url.hash || raw.includes("?") || raw.includes("#")) {
|
|
800
|
+
return { error: `${name} must not carry a query or fragment` };
|
|
801
|
+
}
|
|
802
|
+
if (url.protocol === "http:") {
|
|
803
|
+
if (!LOOPBACK_HOSTS.has(url.hostname)) {
|
|
804
|
+
return { error: `${name} must be https:// or a loopback http:// origin` };
|
|
805
|
+
}
|
|
806
|
+
} else if (url.protocol !== "https:") {
|
|
807
|
+
return { error: `${name} must be https:// or a loopback http:// origin` };
|
|
808
|
+
}
|
|
809
|
+
return { url: url.toString().replace(/\/+$/, "") };
|
|
810
|
+
}
|
|
811
|
+
function validateRemoteSessionGrant(value) {
|
|
812
|
+
if (value === undefined)
|
|
813
|
+
return { ok: true, grant: undefined };
|
|
814
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
815
|
+
return { ok: false, error: "remoteSession must be { hubUrl: string, capability: string }" };
|
|
816
|
+
}
|
|
817
|
+
const proto = Object.getPrototypeOf(value);
|
|
818
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
819
|
+
return { ok: false, error: "remoteSession must be a plain object" };
|
|
820
|
+
}
|
|
821
|
+
const keys = Object.keys(value);
|
|
822
|
+
const unknown = keys.filter((key) => !GRANT_KEYS.includes(key));
|
|
823
|
+
if (unknown.length || keys.length !== GRANT_KEYS.length) {
|
|
824
|
+
return {
|
|
825
|
+
ok: false,
|
|
826
|
+
error: unknown.length ? `remoteSession has unexpected key(s): ${unknown.join(", ")}` : "remoteSession must have both hubUrl and capability"
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
const record = value;
|
|
830
|
+
if (typeof record.hubUrl !== "string" || !record.hubUrl.trim()) {
|
|
831
|
+
return { ok: false, error: "remoteSession.hubUrl must be a non-empty string" };
|
|
832
|
+
}
|
|
833
|
+
const hubUrl = normalizeHubUrl(record.hubUrl.trim());
|
|
834
|
+
if ("error" in hubUrl)
|
|
835
|
+
return { ok: false, error: hubUrl.error };
|
|
836
|
+
if (typeof record.capability !== "string" || !record.capability.trim()) {
|
|
837
|
+
return { ok: false, error: "remoteSession.capability must be a non-empty string" };
|
|
838
|
+
}
|
|
839
|
+
const capability = record.capability.trim();
|
|
840
|
+
if (capability.length > REMOTE_SESSION_GRANT_LIMITS.maxCapabilityLength) {
|
|
841
|
+
return {
|
|
842
|
+
ok: false,
|
|
843
|
+
error: `remoteSession.capability is ${capability.length} characters; at most ${REMOTE_SESSION_GRANT_LIMITS.maxCapabilityLength} are allowed`
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
if (!capability.startsWith(REMOTE_SESSION_GRANT_LIMITS.capabilityPrefix)) {
|
|
847
|
+
return {
|
|
848
|
+
ok: false,
|
|
849
|
+
error: `remoteSession.capability must start with "${REMOTE_SESSION_GRANT_LIMITS.capabilityPrefix}"`
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (!/^rsc1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(capability)) {
|
|
853
|
+
return { ok: false, error: "remoteSession.capability is malformed" };
|
|
854
|
+
}
|
|
855
|
+
return { ok: true, grant: { hubUrl: hubUrl.url, capability } };
|
|
856
|
+
}
|
|
857
|
+
function parseRemoteSessionGrant(value) {
|
|
858
|
+
const validated = validateRemoteSessionGrant(value);
|
|
859
|
+
return validated.ok ? validated.grant : null;
|
|
860
|
+
}
|
|
861
|
+
function remoteSessionGrantFrom(value) {
|
|
862
|
+
return parseRemoteSessionGrant(value) ?? undefined;
|
|
863
|
+
}
|
|
864
|
+
function remoteSessionGrantExpiry(grant) {
|
|
865
|
+
const payload = grant.capability.split(".")[1];
|
|
866
|
+
if (!payload)
|
|
867
|
+
return null;
|
|
868
|
+
try {
|
|
869
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
870
|
+
const exp = decoded?.e;
|
|
871
|
+
return typeof exp === "number" && Number.isFinite(exp) ? exp : null;
|
|
872
|
+
} catch {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function mergeRemoteSessionGrants(grants) {
|
|
877
|
+
if (grants.length === 0 || grants.some((grant) => grant === undefined))
|
|
878
|
+
return;
|
|
879
|
+
const present = grants;
|
|
880
|
+
const hubUrl = present[0].hubUrl;
|
|
881
|
+
if (present.some((grant) => grant.hubUrl !== hubUrl))
|
|
882
|
+
return;
|
|
883
|
+
let chosen = present[present.length - 1];
|
|
884
|
+
let chosenExpiry = remoteSessionGrantExpiry(chosen);
|
|
885
|
+
for (const grant of present) {
|
|
886
|
+
const expiry = remoteSessionGrantExpiry(grant);
|
|
887
|
+
if (expiry !== null && (chosenExpiry === null || expiry > chosenExpiry)) {
|
|
888
|
+
chosen = grant;
|
|
889
|
+
chosenExpiry = expiry;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return chosen;
|
|
893
|
+
}
|
|
894
|
+
function encodeRemoteSessionGrantArg(grant) {
|
|
895
|
+
const validated = validateRemoteSessionGrant(grant);
|
|
896
|
+
if (!validated.ok)
|
|
897
|
+
throw new Error(validated.error);
|
|
898
|
+
if (!validated.grant)
|
|
899
|
+
throw new Error("remoteSession is required");
|
|
900
|
+
return Buffer.from(JSON.stringify(validated.grant), "utf-8").toString("base64url");
|
|
901
|
+
}
|
|
902
|
+
var REMOTE_SESSION_GRANT_LIMITS, GRANT_KEYS, LOOPBACK_HOSTS, REMOTE_SESSION_REPLY_TOKEN_PATTERN, MAX_REMOTE_SESSION_REPLY_TOKEN_LENGTH;
|
|
903
|
+
var init_remote_session_grant = __esm(() => {
|
|
904
|
+
REMOTE_SESSION_GRANT_LIMITS = Object.freeze({
|
|
905
|
+
maxHubUrlLength: 512,
|
|
906
|
+
maxCapabilityLength: 2 * 1024,
|
|
907
|
+
capabilityPrefix: "rsc1."
|
|
908
|
+
});
|
|
909
|
+
GRANT_KEYS = ["hubUrl", "capability"];
|
|
910
|
+
LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "[::1]", "localhost"]);
|
|
911
|
+
REMOTE_SESSION_REPLY_TOKEN_PATTERN = /^rsr1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
912
|
+
MAX_REMOTE_SESSION_REPLY_TOKEN_LENGTH = REMOTE_SESSION_GRANT_LIMITS.maxCapabilityLength;
|
|
913
|
+
});
|
|
914
|
+
|
|
623
915
|
// ../../packages/core/src/mcp/runtime-spec.ts
|
|
624
916
|
import { createHash, createHmac, timingSafeEqual } from "crypto";
|
|
625
917
|
function encodeTokenPart(value) {
|
|
@@ -646,18 +938,26 @@ function issueHostedMcpToken(surface, ctx) {
|
|
|
646
938
|
return `${payloadPart}.${signTokenPayload(payloadPart)}`;
|
|
647
939
|
}
|
|
648
940
|
function buildRuntimeMcpSpec(agent, ctx) {
|
|
649
|
-
const token = issueRuntimeMcpToken(ctx);
|
|
650
941
|
const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}`;
|
|
651
|
-
const query = `token=${encodeURIComponent(
|
|
942
|
+
const query = `token=${encodeURIComponent(issueRuntimeMcpToken(ctx))}`;
|
|
943
|
+
const url = agent === "codex" ? `${base}/mcp?${query}` : `${base}/sse?${query}`;
|
|
944
|
+
assertMcpUrlWithinBudget(RUNTIME_MCP_KEY, url, ctx.topicId);
|
|
652
945
|
if (agent === "codex")
|
|
653
|
-
return { url
|
|
946
|
+
return { url };
|
|
654
947
|
return {
|
|
655
948
|
type: "sse",
|
|
656
|
-
url
|
|
949
|
+
url,
|
|
657
950
|
timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS,
|
|
658
951
|
...agent === "maestro" ? { lifecycle: "turn" } : {}
|
|
659
952
|
};
|
|
660
953
|
}
|
|
954
|
+
function assertMcpUrlWithinBudget(surface, url, topicId) {
|
|
955
|
+
if (url.length <= MCP_URL_BUDGET_CHARS)
|
|
956
|
+
return;
|
|
957
|
+
const error = new McpUrlBudgetExceededError(surface, url.length);
|
|
958
|
+
logger.error({ topicId, surface, urlLength: url.length, budget: MCP_URL_BUDGET_CHARS }, "built-in MCP URL exceeds the transport budget; refusing to mint the token");
|
|
959
|
+
throw error;
|
|
960
|
+
}
|
|
661
961
|
function hostedMcpCacheIdentity(surface, ctx) {
|
|
662
962
|
let semanticContext;
|
|
663
963
|
switch (surface) {
|
|
@@ -688,6 +988,9 @@ function hostedMcpCacheIdentity(surface, ctx) {
|
|
|
688
988
|
case "session-comm":
|
|
689
989
|
semanticContext = {
|
|
690
990
|
userId: ctx.userId,
|
|
991
|
+
actorUserId: ctx.actorUserId ?? null,
|
|
992
|
+
actorTopicScope: ctx.actorTopicScope ?? null,
|
|
993
|
+
remoteSession: ctx.remoteSession ?? null,
|
|
691
994
|
topicTitle: ctx.topicTitle,
|
|
692
995
|
topicId: ctx.topicId ?? null,
|
|
693
996
|
subagentParentTopicId: ctx.subagentParentTopicId ?? null,
|
|
@@ -706,13 +1009,15 @@ function buildHostedMcpSpec(agent, surface, ctx) {
|
|
|
706
1009
|
const token = issueHostedMcpToken(surface, ctx);
|
|
707
1010
|
const base = `http://127.0.0.1:${runtimePort}${RUNTIME_MCP_BASE_PATH}/${surface}`;
|
|
708
1011
|
const query = `token=${encodeURIComponent(token)}`;
|
|
1012
|
+
const url = agent === "codex" ? `${base}/mcp?${query}` : `${base}/sse?${query}`;
|
|
1013
|
+
assertMcpUrlWithinBudget(surface, url, ctx.topicId);
|
|
709
1014
|
if (agent === "codex")
|
|
710
|
-
return { url
|
|
711
|
-
const queryBound = surface === "session-comm" && (ctx.silent === true || ctx.peerBridge !== undefined);
|
|
1015
|
+
return { url };
|
|
1016
|
+
const queryBound = surface === "session-comm" && (ctx.silent === true || ctx.peerBridge !== undefined || ctx.remoteSession !== undefined);
|
|
712
1017
|
const lifecycle = queryBound ? "turn" : surface === "session-comm" ? "session" : "process";
|
|
713
1018
|
return {
|
|
714
1019
|
type: "sse",
|
|
715
|
-
url
|
|
1020
|
+
url,
|
|
716
1021
|
timeout: CLAUDE_MCP_TOOL_TIMEOUT_MS,
|
|
717
1022
|
...agent === "maestro" ? {
|
|
718
1023
|
lifecycle,
|
|
@@ -720,11 +1025,26 @@ function buildHostedMcpSpec(agent, surface, ctx) {
|
|
|
720
1025
|
} : {}
|
|
721
1026
|
};
|
|
722
1027
|
}
|
|
723
|
-
var RUNTIME_MCP_KEY = "runtime", RUNTIME_MCP_BASE_PATH = "/mcp/runtime", TOKEN_TTL_MS, CLAUDE_MCP_TOOL_TIMEOUT_MS = 600000, runtimePort;
|
|
1028
|
+
var RUNTIME_MCP_KEY = "runtime", RUNTIME_MCP_BASE_PATH = "/mcp/runtime", TOKEN_TTL_MS, CLAUDE_MCP_TOOL_TIMEOUT_MS = 600000, MCP_URL_BUDGET_CHARS, McpUrlBudgetExceededError, runtimePort;
|
|
724
1029
|
var init_runtime_spec = __esm(() => {
|
|
1030
|
+
init_explicit_agent_switch();
|
|
725
1031
|
init_config();
|
|
1032
|
+
init_logger();
|
|
1033
|
+
init_actor_topic_scope();
|
|
1034
|
+
init_remote_session_grant();
|
|
726
1035
|
init_types();
|
|
727
1036
|
TOKEN_TTL_MS = 4 * 60 * 60 * 1000;
|
|
1037
|
+
MCP_URL_BUDGET_CHARS = 14 * 1024;
|
|
1038
|
+
McpUrlBudgetExceededError = class McpUrlBudgetExceededError extends Error {
|
|
1039
|
+
surface;
|
|
1040
|
+
urlLength;
|
|
1041
|
+
constructor(surface, urlLength) {
|
|
1042
|
+
super(`built-in MCP URL for "${surface}" is ${urlLength} characters; the transport allows at most ${MCP_URL_BUDGET_CHARS}`);
|
|
1043
|
+
this.surface = surface;
|
|
1044
|
+
this.urlLength = urlLength;
|
|
1045
|
+
this.name = "McpUrlBudgetExceededError";
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
728
1048
|
runtimePort = NEGOTIUM_PORT;
|
|
729
1049
|
});
|
|
730
1050
|
|
|
@@ -1545,6 +1865,9 @@ function initializeApiTopicsSchema() {
|
|
|
1545
1865
|
if (!tableColumns("api_topics").has("subagent_report_mode")) {
|
|
1546
1866
|
db.exec("ALTER TABLE api_topics ADD COLUMN subagent_report_mode TEXT NOT NULL DEFAULT 'auto'");
|
|
1547
1867
|
}
|
|
1868
|
+
if (!tableColumns("api_topics").has("derived_by_user_id")) {
|
|
1869
|
+
db.exec("ALTER TABLE api_topics ADD COLUMN derived_by_user_id TEXT");
|
|
1870
|
+
}
|
|
1548
1871
|
if (!tableColumns("api_topics").has("memory_topic_id")) {
|
|
1549
1872
|
db.exec("ALTER TABLE api_topics ADD COLUMN memory_topic_id TEXT");
|
|
1550
1873
|
}
|
|
@@ -1657,6 +1980,7 @@ function rowToDto(r, participants = getTopicParticipants(r.id), tellTargets) {
|
|
|
1657
1980
|
memoryTopicId: r.memory_topic_id ?? undefined,
|
|
1658
1981
|
memoryKey: r.memory_key ?? undefined,
|
|
1659
1982
|
isFork: r.is_fork !== 0,
|
|
1983
|
+
...r.derived_by_user_id ? { derivedByUserId: r.derived_by_user_id } : {},
|
|
1660
1984
|
...r.is_subagent !== 0 ? { isSubagent: true } : {},
|
|
1661
1985
|
...r.is_subagent !== 0 ? {
|
|
1662
1986
|
subagentTellTargetIds: tellTargets ? tellTargets.get(r.id) ?? [] : listSubagentTellTargetIds(r.id)
|
|
@@ -1731,8 +2055,8 @@ function upsertTopic(t) {
|
|
|
1731
2055
|
db.query(`INSERT INTO api_topics
|
|
1732
2056
|
(id,title,kind,description,agent,base_model,base_effort,response_policy,
|
|
1733
2057
|
created_at,last_message_at,parent_topic_id,memory_topic_id,memory_key,is_fork,is_subagent,visibility,surface,
|
|
1734
|
-
surface_scope,subagent_report_mode)
|
|
1735
|
-
VALUES (
|
|
2058
|
+
surface_scope,subagent_report_mode,derived_by_user_id)
|
|
2059
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
1736
2060
|
ON CONFLICT(id) DO UPDATE SET
|
|
1737
2061
|
title = excluded.title,
|
|
1738
2062
|
kind = excluded.kind,
|
|
@@ -1754,7 +2078,10 @@ function upsertTopic(t) {
|
|
|
1754
2078
|
-- assignment: an update may fill in a scope that was unknown when the
|
|
1755
2079
|
-- room was created, but may never move a room to another workspace.
|
|
1756
2080
|
surface_scope = COALESCE(api_topics.surface_scope, excluded.surface_scope),
|
|
1757
|
-
subagent_report_mode = excluded.subagent_report_mode
|
|
2081
|
+
subagent_report_mode = excluded.subagent_report_mode,
|
|
2082
|
+
-- Who asked for the derive is a fact about creation; a later update
|
|
2083
|
+
-- that omits it must not erase it.
|
|
2084
|
+
derived_by_user_id = COALESCE(excluded.derived_by_user_id, api_topics.derived_by_user_id)`).run(t.id, t.title, normalized.kind, t.description ?? null, normalized.agent ?? null, t.defaultModel ?? null, t.defaultEffort ?? null, normalized.aiMode, t.createdAt, t.lastMessageAt ?? null, t.parentTopicId ?? null, t.memoryTopicId ?? null, t.memoryKey ?? null, t.isFork ? 1 : 0, t.isSubagent ? 1 : 0, normalizeTopicVisibility(t.visibility), normalizeTopicSurface(t.surface ?? defaultTopicSurface()), surfaceScopeForWrite(t), t.subagentReportMode ?? "auto", t.derivedByUserId ?? null);
|
|
1758
2085
|
db.query("DELETE FROM topic_members WHERE topic_id = ?").run(t.id);
|
|
1759
2086
|
for (const participant of t.participants) {
|
|
1760
2087
|
db.query("INSERT INTO topic_members (topic_id,user_id,role) VALUES (?,?,?)").run(t.id, participant.userId, participant.role);
|
|
@@ -1903,6 +2230,9 @@ function grantSubagentTellTarget(subagentTopicId, targetTopicId, grantedByTopicI
|
|
|
1903
2230
|
function revokeSubagentTellTarget(subagentTopicId, targetTopicId) {
|
|
1904
2231
|
return db.query("DELETE FROM subagent_tell_grants WHERE subagent_topic_id = ? AND target_topic_id = ?").run(subagentTopicId, targetTopicId).changes > 0;
|
|
1905
2232
|
}
|
|
2233
|
+
function listSubagentChildTopicIds(parentTopicId) {
|
|
2234
|
+
return db.query("SELECT id FROM api_topics WHERE parent_topic_id = ? AND is_subagent = 1 ORDER BY created_at").all(parentTopicId).map((row) => row.id);
|
|
2235
|
+
}
|
|
1906
2236
|
function reparentTopicChildren(deletedTopicId, replacementParentTopicId) {
|
|
1907
2237
|
const rows = db.query("SELECT id FROM api_topics WHERE parent_topic_id = ?").all(deletedTopicId);
|
|
1908
2238
|
if (rows.length === 0)
|
|
@@ -1949,6 +2279,9 @@ function buildBuiltinMcpServer(surface, ctx, stdio) {
|
|
|
1949
2279
|
const agent = ctx.agent ?? FALLBACK_AGENT;
|
|
1950
2280
|
return buildHostedMcpSpec(agent, surface, {
|
|
1951
2281
|
userId: ctx.userId,
|
|
2282
|
+
...ctx.actorUserId ? { actorUserId: ctx.actorUserId } : {},
|
|
2283
|
+
...ctx.actorTopicScope ? { actorTopicScope: ctx.actorTopicScope } : {},
|
|
2284
|
+
...surface === "session-comm" && ctx.remoteSession ? { remoteSession: ctx.remoteSession } : {},
|
|
1952
2285
|
topicTitle: ctx.session,
|
|
1953
2286
|
...ctx.topicId ? { topicId: ctx.topicId } : {},
|
|
1954
2287
|
...ctx.queryId ? { queryId: ctx.queryId } : {},
|
|
@@ -2140,6 +2473,9 @@ function getManagerMcpServers(opts) {
|
|
|
2140
2473
|
const topicId = opts.topicId;
|
|
2141
2474
|
return buildScope("manager", {
|
|
2142
2475
|
userId: opts.userId,
|
|
2476
|
+
actorUserId: opts.actorUserId,
|
|
2477
|
+
actorTopicScope: opts.actorTopicScope,
|
|
2478
|
+
remoteSession: opts.remoteSession,
|
|
2143
2479
|
session: opts.session ?? "General",
|
|
2144
2480
|
topicId,
|
|
2145
2481
|
queryId: opts.queryId,
|
|
@@ -2159,6 +2495,8 @@ function getForumMcpServers(opts) {
|
|
|
2159
2495
|
const {
|
|
2160
2496
|
userId,
|
|
2161
2497
|
actorUserId,
|
|
2498
|
+
actorTopicScope,
|
|
2499
|
+
remoteSession,
|
|
2162
2500
|
vaultUserId,
|
|
2163
2501
|
session,
|
|
2164
2502
|
topicId,
|
|
@@ -2192,6 +2530,8 @@ function getForumMcpServers(opts) {
|
|
|
2192
2530
|
const base = buildScope("forum", {
|
|
2193
2531
|
userId,
|
|
2194
2532
|
actorUserId,
|
|
2533
|
+
actorTopicScope,
|
|
2534
|
+
remoteSession,
|
|
2195
2535
|
vaultUserId,
|
|
2196
2536
|
session,
|
|
2197
2537
|
topicId,
|
|
@@ -2272,6 +2612,9 @@ function getMcpServersForQuery(opts) {
|
|
|
2272
2612
|
if (opts.sessionType === "manager") {
|
|
2273
2613
|
return hideProviderIntrospectionForOtium(mergeHostMcpServers(getManagerMcpServers({
|
|
2274
2614
|
userId: opts.userId || "local",
|
|
2615
|
+
actorUserId: opts.actorUserId,
|
|
2616
|
+
actorTopicScope: opts.actorTopicScope,
|
|
2617
|
+
remoteSession: opts.remoteSession,
|
|
2275
2618
|
session: opts.session,
|
|
2276
2619
|
topicId: opts.topicId,
|
|
2277
2620
|
queryId: opts.queryId,
|
|
@@ -2290,6 +2633,8 @@ function getMcpServersForQuery(opts) {
|
|
|
2290
2633
|
return hideProviderIntrospectionForOtium(getForumMcpServers({
|
|
2291
2634
|
userId: opts.userId || "local",
|
|
2292
2635
|
actorUserId: opts.actorUserId,
|
|
2636
|
+
actorTopicScope: opts.actorTopicScope,
|
|
2637
|
+
remoteSession: opts.remoteSession,
|
|
2293
2638
|
vaultUserId: opts.vaultUserId,
|
|
2294
2639
|
session: opts.session || "default",
|
|
2295
2640
|
topicId: opts.topicId,
|
|
@@ -2324,6 +2669,7 @@ function hideProviderIntrospectionForOtium(servers, topicId) {
|
|
|
2324
2669
|
}
|
|
2325
2670
|
var _playwrightUnavailableNotifier, _playwrightUnavailableLastNotifiedAt, _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS, _playwrightUnavailableThisTurn, cuaRsMcpPort, cuaRsMcpToken, MCP_CATALOG, nodeMcpEntries, resolvedNodeMcpPorts, allForumMcpServerNames, requiredForumMcpServers, REQUIRED_FORUM_MCP_SERVERS, optionalForumMcpServers;
|
|
2326
2671
|
var init_mcp_config = __esm(async () => {
|
|
2672
|
+
init_explicit_agent_switch();
|
|
2327
2673
|
init_canonical_bridge_config();
|
|
2328
2674
|
init_runtime_spec();
|
|
2329
2675
|
init_bridge_ipc_config();
|
|
@@ -2333,7 +2679,9 @@ var init_mcp_config = __esm(async () => {
|
|
|
2333
2679
|
init_mcp_catalog_policy();
|
|
2334
2680
|
init_capability();
|
|
2335
2681
|
init_mcp_transport();
|
|
2682
|
+
init_actor_topic_scope();
|
|
2336
2683
|
init_cron_sessions();
|
|
2684
|
+
init_remote_session_grant();
|
|
2337
2685
|
await init_api_topics();
|
|
2338
2686
|
_playwrightUnavailableLastNotifiedAt = new Map;
|
|
2339
2687
|
_PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS = 5 * 60000;
|
|
@@ -2361,6 +2709,7 @@ var init_mcp_config = __esm(async () => {
|
|
|
2361
2709
|
build({
|
|
2362
2710
|
userId,
|
|
2363
2711
|
actorUserId,
|
|
2712
|
+
actorTopicScope,
|
|
2364
2713
|
session,
|
|
2365
2714
|
topicId,
|
|
2366
2715
|
queryId,
|
|
@@ -2376,16 +2725,18 @@ var init_mcp_config = __esm(async () => {
|
|
|
2376
2725
|
}) {
|
|
2377
2726
|
if (!topicId || !agent)
|
|
2378
2727
|
return null;
|
|
2728
|
+
const switchTargets = explicitAgentSwitchTargets(currentUserPrompt);
|
|
2379
2729
|
return buildRuntimeMcpSpec(agent, {
|
|
2380
2730
|
userId,
|
|
2381
2731
|
...actorUserId ? { actorUserId } : {},
|
|
2732
|
+
...actorTopicScope ? { actorTopicScope } : {},
|
|
2382
2733
|
topicId,
|
|
2383
2734
|
topicTitle: session,
|
|
2384
2735
|
queryId,
|
|
2385
2736
|
cwd: cwd ?? resolveTopicWorkspaceDir(topicId),
|
|
2386
2737
|
agent,
|
|
2387
2738
|
model,
|
|
2388
|
-
|
|
2739
|
+
...switchTargets.length ? { explicitAgentSwitchTargets: switchTargets } : {},
|
|
2389
2740
|
autoContinue,
|
|
2390
2741
|
visualTools,
|
|
2391
2742
|
fileDeliveryTools,
|
|
@@ -2455,6 +2806,9 @@ var init_mcp_config = __esm(async () => {
|
|
|
2455
2806
|
build(ctx) {
|
|
2456
2807
|
const {
|
|
2457
2808
|
userId,
|
|
2809
|
+
actorUserId,
|
|
2810
|
+
actorTopicScope,
|
|
2811
|
+
remoteSession,
|
|
2458
2812
|
session,
|
|
2459
2813
|
topicId,
|
|
2460
2814
|
subagentParentTopicId,
|
|
@@ -2468,6 +2822,9 @@ var init_mcp_config = __esm(async () => {
|
|
|
2468
2822
|
const cronSessionId = topicId ? getRegisteredCronSession(topicId, effectiveAgent)?.sessionId : undefined;
|
|
2469
2823
|
const args = [
|
|
2470
2824
|
`--user-id=${userId}`,
|
|
2825
|
+
...actorUserId ? [`--actor-user-id=${actorUserId}`] : [],
|
|
2826
|
+
...actorTopicScope ? [`--actor-topic-scope=${encodeActorTopicScopeArg(actorTopicScope)}`] : [],
|
|
2827
|
+
...remoteSession ? [`--remote-session-grant=${encodeRemoteSessionGrantArg(remoteSession)}`] : [],
|
|
2471
2828
|
`--topic=${session}`,
|
|
2472
2829
|
...topicId ? [`--topic-id=${topicId}`] : [],
|
|
2473
2830
|
...subagentParentTopicId ? [`--subagent-parent-topic-id=${subagentParentTopicId}`] : [],
|
|
@@ -3969,7 +4326,7 @@ var init_codex = __esm(async () => {
|
|
|
3969
4326
|
});
|
|
3970
4327
|
|
|
3971
4328
|
// ../../packages/core/src/version.ts
|
|
3972
|
-
var NEGOTIUM_VERSION = "0.
|
|
4329
|
+
var NEGOTIUM_VERSION = "0.20.0";
|
|
3973
4330
|
|
|
3974
4331
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3975
4332
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -7653,6 +8010,8 @@ var init_model_catalog = __esm(() => {
|
|
|
7653
8010
|
haiku: "claude",
|
|
7654
8011
|
fable: "claude",
|
|
7655
8012
|
"gpt-6-astra": "codex",
|
|
8013
|
+
"gpt-6-sol": "codex",
|
|
8014
|
+
"gpt-6-luna": "codex",
|
|
7656
8015
|
"gpt-5.6-luna": "codex",
|
|
7657
8016
|
"gpt-5.6-terra": "codex",
|
|
7658
8017
|
"gpt-5.6-sol": "codex",
|
|
@@ -7682,6 +8041,26 @@ var init_model_catalog = __esm(() => {
|
|
|
7682
8041
|
marginalTokenCost: "Codex credits (OpenAI API rate): $10/M uncached input, $1/M cached input, $12.50/M cache write, $50/M output",
|
|
7683
8042
|
estimatedUsage: `Released 2026-09-03; became Codex CLI's bundled default in v0.153.4 (2026-09-05). Local message-count/quota-weight ranges not yet published as of the last catalog check \u2014 expect a quota weight above gpt-5.6-sol given the higher per-token cost. ${CODEX_COMMUNITY_WEEKLY}`
|
|
7684
8043
|
},
|
|
8044
|
+
{
|
|
8045
|
+
model: "gpt-6-sol",
|
|
8046
|
+
agent: "codex",
|
|
8047
|
+
description: "GPT-6 generation Sol route (2026-09-22) for demanding coding and agentic work.",
|
|
8048
|
+
intelligenceTier: "opus",
|
|
8049
|
+
routingSummary: "demanding coding work; about half as many mistakes as gpt-5.6-sol at half the price",
|
|
8050
|
+
accessCost: CODEX_PRO_20X_COST,
|
|
8051
|
+
marginalTokenCost: "Codex credits (OpenAI API rate): $2/M uncached input, $0.20/M cached input (assumed), $10/M output",
|
|
8052
|
+
estimatedUsage: `Released 2026-09-22; local message-count/quota-weight ranges not yet published. ${CODEX_COMMUNITY_WEEKLY}`
|
|
8053
|
+
},
|
|
8054
|
+
{
|
|
8055
|
+
model: "gpt-6-luna",
|
|
8056
|
+
agent: "codex",
|
|
8057
|
+
description: "GPT-6 generation Luna route (2026-09-22): fast, high-volume, lowest cost.",
|
|
8058
|
+
intelligenceTier: "sonnet",
|
|
8059
|
+
routingSummary: "fast high-volume work; cheapest Codex route",
|
|
8060
|
+
accessCost: CODEX_PRO_20X_COST,
|
|
8061
|
+
marginalTokenCost: "Codex credits (OpenAI API rate): $0.10/M uncached input, $0.01/M cached input (assumed), $0.50/M output",
|
|
8062
|
+
estimatedUsage: `Released 2026-09-22; local message-count/quota-weight ranges not yet published. ${CODEX_COMMUNITY_WEEKLY}`
|
|
8063
|
+
},
|
|
7685
8064
|
{
|
|
7686
8065
|
model: "gpt-5.6-sol",
|
|
7687
8066
|
agent: "codex",
|
|
@@ -7727,9 +8106,9 @@ var init_model_catalog = __esm(() => {
|
|
|
7727
8106
|
agent: "claude",
|
|
7728
8107
|
description: "High-capability Claude route for complex reasoning and tool-heavy work.",
|
|
7729
8108
|
intelligenceTier: "opus",
|
|
7730
|
-
routingSummary: "complex reasoning and tool-heavy work; about
|
|
8109
|
+
routingSummary: "complex reasoning and tool-heavy work; about 2x Sonnet marginal cost",
|
|
7731
8110
|
accessCost: CLAUDE_MAX_20X_COST,
|
|
7732
|
-
marginalTokenCost: "Claude API/extra usage: $
|
|
8111
|
+
marginalTokenCost: "Claude API/extra usage (Opus 5.5, 2026-09-22): $4/M input, $5/M cache write (assumed), $0.40/M cache read (assumed), $20/M output",
|
|
7733
8112
|
estimatedUsage: `${CLAUDE_COMMUNITY_SESSION}; Opus uses the shared all-model weekly pool more quickly than Sonnet. No stable per-model token cap is published.`
|
|
7734
8113
|
},
|
|
7735
8114
|
{
|
|
@@ -8775,7 +9154,7 @@ function isTopicSummaryFile(filename, topicId, topicTitle) {
|
|
|
8775
9154
|
return false;
|
|
8776
9155
|
if (topicTitle) {
|
|
8777
9156
|
const titleSlug = wikiSummarySlug(topicTitle);
|
|
8778
|
-
if (new RegExp(`^\\d{4}-\\d{2}-\\d{2}-${
|
|
9157
|
+
if (new RegExp(`^\\d{4}-\\d{2}-\\d{2}-${escapeRegExp2(titleSlug)}(?:~\\d+)?\\.md$`).test(filename)) {
|
|
8779
9158
|
return true;
|
|
8780
9159
|
}
|
|
8781
9160
|
}
|
|
@@ -8788,7 +9167,7 @@ function isTopicBriefFile(filename, topicId, topicTitle) {
|
|
|
8788
9167
|
const idSlug = wikiSummarySlug(topicId);
|
|
8789
9168
|
return filename === `${idSlug}.md` || filename.endsWith(`--${idSlug}.md`);
|
|
8790
9169
|
}
|
|
8791
|
-
function
|
|
9170
|
+
function escapeRegExp2(value) {
|
|
8792
9171
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8793
9172
|
}
|
|
8794
9173
|
var WIKI_SUMMARY_DATE_PREFIX;
|
|
@@ -13114,11 +13493,14 @@ var init_token_stats = __esm(async () => {
|
|
|
13114
13493
|
]);
|
|
13115
13494
|
TOKEN_PRICES = {
|
|
13116
13495
|
"codex:gpt-6-astra": { input: 10, cacheWrite: 12.5, cacheRead: 1, output: 50 },
|
|
13496
|
+
"codex:gpt-6-sol": { input: 2, cacheRead: 0.2, output: 10 },
|
|
13497
|
+
"codex:gpt-6-luna": { input: 0.1, cacheRead: 0.01, output: 0.5 },
|
|
13117
13498
|
"codex:gpt-5.6-sol": { input: 5, cacheRead: 0.5, output: 30 },
|
|
13118
13499
|
"codex:gpt-5.6-terra": { input: 2.5, cacheRead: 0.25, output: 15 },
|
|
13119
13500
|
"codex:gpt-5.6-luna": { input: 1, cacheRead: 0.1, output: 6 },
|
|
13120
13501
|
"claude:fable": { input: 10, cacheWrite: 12.5, cacheRead: 0.25, output: 50 },
|
|
13121
|
-
"claude:opus": { input:
|
|
13502
|
+
"claude:opus": { input: 4, cacheWrite: 5, cacheRead: 0.4, output: 20 },
|
|
13503
|
+
"claude:claude-opus-5-5": { input: 4, cacheWrite: 5, cacheRead: 0.4, output: 20 },
|
|
13122
13504
|
"claude:sonnet": { input: 2, cacheWrite: 2.5, cacheRead: 0.2, output: 10 },
|
|
13123
13505
|
"maestro:kimi-k3": { input: 3, cacheRead: 0.3, output: 15 },
|
|
13124
13506
|
"maestro:kimi-k2.7-code": { input: 0.95, cacheRead: 0.19, output: 4 },
|
|
@@ -13631,6 +14013,9 @@ function rowToRequest(row) {
|
|
|
13631
14013
|
runningQueryId: row.running_query_id ?? undefined
|
|
13632
14014
|
};
|
|
13633
14015
|
}
|
|
14016
|
+
function requestActor(actorUserId) {
|
|
14017
|
+
return actorUserId ?? null;
|
|
14018
|
+
}
|
|
13634
14019
|
function loggedMessageCount(request) {
|
|
13635
14020
|
const explicit = request.execution?.loggedUserMessageCount;
|
|
13636
14021
|
if (typeof explicit === "number" && Number.isInteger(explicit)) {
|
|
@@ -13647,7 +14032,13 @@ function mergeRuntimeUserTurnRequest(input) {
|
|
|
13647
14032
|
return db.transaction(() => {
|
|
13648
14033
|
const rows = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? ORDER BY created_at ASC, rowid ASC").all(input.topicId);
|
|
13649
14034
|
const thread = input.execution.threadRootId;
|
|
13650
|
-
const
|
|
14035
|
+
const sameThread = rows.map(rowToRequest).filter((request) => request.execution?.threadRootId === thread);
|
|
14036
|
+
const actor = requestActor(input.execution.actorUserId);
|
|
14037
|
+
let firstMergeable = sameThread.length;
|
|
14038
|
+
while (firstMergeable > 0 && requestActor(sameThread[firstMergeable - 1].execution?.actorUserId) === actor) {
|
|
14039
|
+
firstMergeable -= 1;
|
|
14040
|
+
}
|
|
14041
|
+
const previous = sameThread.slice(firstMergeable);
|
|
13651
14042
|
const omittedRequestIds = new Set([
|
|
13652
14043
|
...input.omitRequestIds ?? [],
|
|
13653
14044
|
...previous.filter((request) => Boolean(request.execution?.providerSessionId)).map((request) => request.requestId)
|
|
@@ -13666,8 +14057,18 @@ function mergeRuntimeUserTurnRequest(input) {
|
|
|
13666
14057
|
];
|
|
13667
14058
|
const incomingLoggedCount = Math.min(Math.max(0, input.execution.loggedUserMessageCount ?? 0), input.userMessages.length);
|
|
13668
14059
|
const loggedUserMessageCount = carried.reduce((count, request) => count + loggedMessageCount(request), 0) + Math.max(0, incomingLoggedCount - alreadyIncludedMessageCount);
|
|
14060
|
+
const actorTopicScope = intersectActorTopicScopes([
|
|
14061
|
+
...previous.map((request) => request.execution?.actorTopicScope),
|
|
14062
|
+
input.execution.actorTopicScope
|
|
14063
|
+
]);
|
|
14064
|
+
const remoteSession = mergeRemoteSessionGrants([
|
|
14065
|
+
...previous.map((request) => request.execution?.remoteSession),
|
|
14066
|
+
input.execution.remoteSession
|
|
14067
|
+
]);
|
|
13669
14068
|
const execution = {
|
|
13670
14069
|
...input.execution,
|
|
14070
|
+
actorTopicScope,
|
|
14071
|
+
remoteSession,
|
|
13671
14072
|
loggedUserMessageCount,
|
|
13672
14073
|
supersededRequestIds: [
|
|
13673
14074
|
...new Set(previous.flatMap((request) => [
|
|
@@ -13803,12 +14204,21 @@ function cancelRuntimeUserTurnRequests(topicId) {
|
|
|
13803
14204
|
db.query("DELETE FROM runtime_user_turn_requests WHERE topic_id = ?").run(topicId);
|
|
13804
14205
|
return rows.map((row) => row.request_id);
|
|
13805
14206
|
}
|
|
14207
|
+
function getActiveRuntimeUserTurnRequest(topicId) {
|
|
14208
|
+
const row = db.query(`SELECT * FROM runtime_user_turn_requests
|
|
14209
|
+
WHERE topic_id = ?
|
|
14210
|
+
AND (status = 'running' OR (claimed_by IS NOT NULL AND claimed_at >= ?))
|
|
14211
|
+
ORDER BY created_at ASC, rowid ASC LIMIT 1`).get(topicId, Date.now() - REQUEST_CLAIM_STALE_MS);
|
|
14212
|
+
return row ? rowToRequest(row) : null;
|
|
14213
|
+
}
|
|
13806
14214
|
function getRuntimeUserTurnRequest(topicId) {
|
|
13807
14215
|
const row = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 1").get(topicId);
|
|
13808
14216
|
return row ? rowToRequest(row) : null;
|
|
13809
14217
|
}
|
|
13810
14218
|
var REQUEST_CLAIM_STALE_MS;
|
|
13811
14219
|
var init_runtime_turn_requests = __esm(async () => {
|
|
14220
|
+
init_actor_topic_scope();
|
|
14221
|
+
init_remote_session_grant();
|
|
13812
14222
|
init_user_turn_envelope();
|
|
13813
14223
|
await __promiseAll([
|
|
13814
14224
|
init_forum_db(),
|
|
@@ -14721,6 +15131,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
|
|
|
14721
15131
|
parentTopicId: sourceTopicId,
|
|
14722
15132
|
...subagent?.memoryTopicId ? { memoryTopicId: subagent.memoryTopicId } : {},
|
|
14723
15133
|
isFork: copyHistory,
|
|
15134
|
+
...opts?.derivedByUserId ? { derivedByUserId: opts.derivedByUserId } : {},
|
|
14724
15135
|
...subagent ? { isSubagent: true } : {},
|
|
14725
15136
|
visibility: topic.visibility,
|
|
14726
15137
|
surface,
|
|
@@ -15028,6 +15439,50 @@ var init_state = __esm(() => {
|
|
|
15028
15439
|
});
|
|
15029
15440
|
});
|
|
15030
15441
|
|
|
15442
|
+
// ../../packages/core/src/runtime/actor-topic-reach.ts
|
|
15443
|
+
function subagentLineageTopicIds(currentTopicId) {
|
|
15444
|
+
const reachable = new Set;
|
|
15445
|
+
const children = new Set;
|
|
15446
|
+
if (!currentTopicId)
|
|
15447
|
+
return { reachable, children };
|
|
15448
|
+
const current2 = getTopic(currentTopicId);
|
|
15449
|
+
if (current2?.isSubagent && current2.parentTopicId) {
|
|
15450
|
+
reachable.add(current2.parentTopicId);
|
|
15451
|
+
for (const id of listSubagentTellTargetIds(currentTopicId))
|
|
15452
|
+
reachable.add(id);
|
|
15453
|
+
}
|
|
15454
|
+
for (const id of listSubagentChildTopicIds(currentTopicId)) {
|
|
15455
|
+
reachable.add(id);
|
|
15456
|
+
children.add(id);
|
|
15457
|
+
}
|
|
15458
|
+
return { reachable, children };
|
|
15459
|
+
}
|
|
15460
|
+
function actorReachableTopicIds(input) {
|
|
15461
|
+
if (input.surface !== "otium")
|
|
15462
|
+
return null;
|
|
15463
|
+
const ids = new Set;
|
|
15464
|
+
if (input.currentTopicId)
|
|
15465
|
+
ids.add(input.currentTopicId);
|
|
15466
|
+
if (input.actorTopicScope) {
|
|
15467
|
+
for (const id of input.actorTopicScope.visibleNodeTopicIds)
|
|
15468
|
+
ids.add(id);
|
|
15469
|
+
return ids;
|
|
15470
|
+
}
|
|
15471
|
+
for (const id of subagentLineageTopicIds(input.currentTopicId).reachable)
|
|
15472
|
+
ids.add(id);
|
|
15473
|
+
return ids;
|
|
15474
|
+
}
|
|
15475
|
+
function actorOwnedTopicIds(input) {
|
|
15476
|
+
if (input.surface !== "otium")
|
|
15477
|
+
return null;
|
|
15478
|
+
if (input.actorTopicScope)
|
|
15479
|
+
return new Set(input.actorTopicScope.ownedNodeTopicIds);
|
|
15480
|
+
return subagentLineageTopicIds(input.currentTopicId).children;
|
|
15481
|
+
}
|
|
15482
|
+
var init_actor_topic_reach = __esm(async () => {
|
|
15483
|
+
await init_api_topics();
|
|
15484
|
+
});
|
|
15485
|
+
|
|
15031
15486
|
// ../../packages/core/src/query/session-inbox-path.ts
|
|
15032
15487
|
import { join as join25 } from "path";
|
|
15033
15488
|
function sessionInboxPath(userId, topicId) {
|
|
@@ -15118,96 +15573,210 @@ var init_session_inbox_cleanup = __esm(async () => {
|
|
|
15118
15573
|
await init_session_inbox();
|
|
15119
15574
|
});
|
|
15120
15575
|
|
|
15121
|
-
// ../../packages/core/src/mcp/session-comm/
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
|
|
15127
|
-
deliverPeerReply: () => deliverPeerReply
|
|
15128
|
-
});
|
|
15129
|
-
function loopbackBridgeConfig() {
|
|
15130
|
-
const rawUrl = process.env[PEER_BRIDGE_URL_ENV]?.trim();
|
|
15131
|
-
const token = process.env[PEER_BRIDGE_TOKEN_ENV]?.trim();
|
|
15132
|
-
if (!rawUrl || !token)
|
|
15576
|
+
// ../../packages/core/src/mcp/session-comm/hub-remote-session.ts
|
|
15577
|
+
function isPlainRecord(value) {
|
|
15578
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
15579
|
+
}
|
|
15580
|
+
function parseListedRoom(value, mode) {
|
|
15581
|
+
if (!isPlainRecord(value))
|
|
15133
15582
|
return null;
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15583
|
+
const { name, agent, status, description, role } = value;
|
|
15584
|
+
if (typeof name !== "string" || !name.trim())
|
|
15585
|
+
return null;
|
|
15586
|
+
if (agent !== undefined && agent !== null && typeof agent !== "string")
|
|
15587
|
+
return null;
|
|
15588
|
+
if (status === undefined) {
|
|
15589
|
+
if (mode === "peek")
|
|
15137
15590
|
return null;
|
|
15138
|
-
|
|
15139
|
-
return
|
|
15140
|
-
}
|
|
15591
|
+
} else if (status !== "active" && status !== "ready") {
|
|
15592
|
+
return null;
|
|
15593
|
+
}
|
|
15594
|
+
if (description !== undefined && description !== null && typeof description !== "string") {
|
|
15141
15595
|
return null;
|
|
15142
15596
|
}
|
|
15597
|
+
if (role !== undefined && role !== "owner" && role !== "member")
|
|
15598
|
+
return null;
|
|
15599
|
+
return {
|
|
15600
|
+
name,
|
|
15601
|
+
...agent !== undefined ? { agent } : {},
|
|
15602
|
+
...status !== undefined ? { status } : {},
|
|
15603
|
+
...description !== undefined ? { description } : {},
|
|
15604
|
+
...role !== undefined ? { role } : {}
|
|
15605
|
+
};
|
|
15143
15606
|
}
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15607
|
+
function parseNodeListing(value, mode) {
|
|
15608
|
+
if (!isPlainRecord(value))
|
|
15609
|
+
return null;
|
|
15610
|
+
const { node, sessions, error } = value;
|
|
15611
|
+
if (typeof node !== "string" || !node.trim() || node.includes("/"))
|
|
15612
|
+
return null;
|
|
15613
|
+
if (error !== undefined && (typeof error !== "string" || !error.trim()))
|
|
15614
|
+
return null;
|
|
15615
|
+
if (sessions === undefined === (error === undefined))
|
|
15616
|
+
return null;
|
|
15617
|
+
if (sessions === undefined)
|
|
15618
|
+
return { node, error };
|
|
15619
|
+
if (!Array.isArray(sessions))
|
|
15620
|
+
return null;
|
|
15621
|
+
const parsed = [];
|
|
15622
|
+
for (const entry of sessions) {
|
|
15623
|
+
const room = parseListedRoom(entry, mode);
|
|
15624
|
+
if (!room)
|
|
15625
|
+
return null;
|
|
15626
|
+
parsed.push(room);
|
|
15163
15627
|
}
|
|
15628
|
+
return { node, sessions: parsed };
|
|
15164
15629
|
}
|
|
15165
|
-
function
|
|
15166
|
-
|
|
15167
|
-
|
|
15168
|
-
|
|
15169
|
-
|
|
15170
|
-
|
|
15630
|
+
function parseNodeListings(value, mode) {
|
|
15631
|
+
if (!Array.isArray(value))
|
|
15632
|
+
return null;
|
|
15633
|
+
const listings = [];
|
|
15634
|
+
for (const entry of value) {
|
|
15635
|
+
const listing = parseNodeListing(entry, mode);
|
|
15636
|
+
if (!listing)
|
|
15637
|
+
return null;
|
|
15638
|
+
listings.push(listing);
|
|
15639
|
+
}
|
|
15640
|
+
return listings;
|
|
15171
15641
|
}
|
|
15172
|
-
|
|
15173
|
-
if (
|
|
15174
|
-
return
|
|
15175
|
-
const
|
|
15176
|
-
|
|
15177
|
-
|
|
15642
|
+
function parsePendingAsks(value) {
|
|
15643
|
+
if (!Array.isArray(value))
|
|
15644
|
+
return null;
|
|
15645
|
+
const asks = [];
|
|
15646
|
+
for (const entry of value) {
|
|
15647
|
+
if (!isPlainRecord(entry))
|
|
15648
|
+
return null;
|
|
15649
|
+
const { to, requestId, status } = entry;
|
|
15650
|
+
if (typeof to !== "string" || !to.trim())
|
|
15651
|
+
return null;
|
|
15652
|
+
if (typeof requestId !== "string" || !requestId.trim())
|
|
15653
|
+
return null;
|
|
15654
|
+
if (status !== "forwarded" && status !== "reply_pending")
|
|
15655
|
+
return null;
|
|
15656
|
+
asks.push({ to, requestId, status });
|
|
15657
|
+
}
|
|
15658
|
+
return asks;
|
|
15659
|
+
}
|
|
15660
|
+
function validateHubSuccessEnvelope(body, shape) {
|
|
15661
|
+
if (!isPlainRecord(body))
|
|
15662
|
+
return null;
|
|
15663
|
+
if (body.ok !== true || body.v !== REMOTE_SESSION_PROTOCOL_VERSION)
|
|
15664
|
+
return null;
|
|
15665
|
+
switch (shape) {
|
|
15666
|
+
case "sessions": {
|
|
15667
|
+
const nodes = parseNodeListings(body.nodes, "sessions");
|
|
15668
|
+
return nodes ? { shape, nodes } : null;
|
|
15669
|
+
}
|
|
15670
|
+
case "peek": {
|
|
15671
|
+
const nodes = parseNodeListings(body.nodes, "peek");
|
|
15672
|
+
const pendingAsks = parsePendingAsks(body.pendingAsks);
|
|
15673
|
+
return nodes && pendingAsks ? { shape, nodes, pendingAsks } : null;
|
|
15674
|
+
}
|
|
15675
|
+
case "delivery":
|
|
15676
|
+
case "reply":
|
|
15677
|
+
return typeof body.replayed === "boolean" ? { shape, replayed: body.replayed } : null;
|
|
15678
|
+
}
|
|
15679
|
+
}
|
|
15680
|
+
function hubHeaders(bearer) {
|
|
15178
15681
|
return {
|
|
15179
|
-
|
|
15180
|
-
|
|
15682
|
+
authorization: `Bearer ${bearer}`,
|
|
15683
|
+
"content-type": "application/json",
|
|
15684
|
+
accept: "application/json"
|
|
15181
15685
|
};
|
|
15182
15686
|
}
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
}
|
|
15687
|
+
function errorText(status, body, fallback) {
|
|
15688
|
+
const text2 = body?.error;
|
|
15689
|
+
if (typeof text2 === "string" && text2.trim())
|
|
15690
|
+
return text2.trim();
|
|
15691
|
+
return `${fallback} (hub answered ${status})`;
|
|
15692
|
+
}
|
|
15693
|
+
function describeFailure(status, body) {
|
|
15694
|
+
switch (status) {
|
|
15695
|
+
case 401:
|
|
15696
|
+
return "remote session authorization for this turn is no longer valid (expired or revoked); send a new message to start a turn with a fresh grant";
|
|
15697
|
+
case 404:
|
|
15698
|
+
return errorText(status, body, "remote session not found");
|
|
15699
|
+
case 409:
|
|
15700
|
+
return errorText(status, body, "remote session refused the request");
|
|
15701
|
+
case 413:
|
|
15702
|
+
return errorText(status, body, "message too long for the hub");
|
|
15703
|
+
case 429:
|
|
15704
|
+
return errorText(status, body, "remote session rate limit reached; try again shortly");
|
|
15705
|
+
case 502:
|
|
15706
|
+
return errorText(status, body, "the target node could not be reached by the hub");
|
|
15707
|
+
case 503:
|
|
15708
|
+
return errorText(status, body, "remote session-comm is disabled on the hub");
|
|
15709
|
+
default:
|
|
15710
|
+
return errorText(status, body, "hub refused the remote session call");
|
|
15711
|
+
}
|
|
15712
|
+
}
|
|
15713
|
+
async function hubRemoteReply(args) {
|
|
15714
|
+
const url = `${args.hubUrl}${REMOTE_SESSION_HUB_BASE_PATH}/reply`;
|
|
15715
|
+
let response;
|
|
15716
|
+
try {
|
|
15717
|
+
response = await hubFetch(url, {
|
|
15718
|
+
method: "POST",
|
|
15719
|
+
headers: hubHeaders(args.token),
|
|
15720
|
+
body: JSON.stringify({
|
|
15721
|
+
v: REMOTE_SESSION_PROTOCOL_VERSION,
|
|
15722
|
+
requestId: args.requestId,
|
|
15723
|
+
kind: args.kind,
|
|
15724
|
+
replyText: args.replyText,
|
|
15725
|
+
fromLabel: args.fromLabel
|
|
15726
|
+
}),
|
|
15727
|
+
signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
|
|
15728
|
+
redirect: "error"
|
|
15729
|
+
});
|
|
15730
|
+
} catch (error) {
|
|
15731
|
+
return {
|
|
15732
|
+
settled: false,
|
|
15733
|
+
error: `hub unreachable: ${error instanceof Error ? error.message : String(error)}`
|
|
15734
|
+
};
|
|
15735
|
+
}
|
|
15736
|
+
let parsed = null;
|
|
15737
|
+
try {
|
|
15738
|
+
parsed = await response.json();
|
|
15739
|
+
} catch {
|
|
15740
|
+
parsed = null;
|
|
15741
|
+
}
|
|
15742
|
+
if (response.ok) {
|
|
15743
|
+
const envelope = validateHubSuccessEnvelope(parsed, "reply");
|
|
15744
|
+
if (!envelope) {
|
|
15745
|
+
return {
|
|
15746
|
+
settled: false,
|
|
15747
|
+
error: `hub answered ${response.status} with an unexpected body`
|
|
15748
|
+
};
|
|
15749
|
+
}
|
|
15750
|
+
return { settled: true, replayed: envelope.shape === "reply" && envelope.replayed };
|
|
15751
|
+
}
|
|
15752
|
+
const code = parsed?.code;
|
|
15753
|
+
if (response.status >= 500 || response.status === 429 || response.status === 409 && code === HUB_IN_PROGRESS_CODE) {
|
|
15754
|
+
return { settled: false, error: describeFailure(response.status, parsed) };
|
|
15755
|
+
}
|
|
15756
|
+
logger.warn({
|
|
15757
|
+
requestId: args.requestId,
|
|
15758
|
+
status: response.status,
|
|
15759
|
+
error: describeFailure(response.status, parsed)
|
|
15760
|
+
}, "session-comm: hub rejected an ask reply; dropping it");
|
|
15761
|
+
return { settled: true, replayed: false };
|
|
15208
15762
|
}
|
|
15209
|
-
var
|
|
15210
|
-
|
|
15763
|
+
var REMOTE_SESSION_HUB_BASE_PATH = "/api/v1/session-comm/remote", REMOTE_SESSION_PROTOCOL_VERSION = 1, HUB_TIMEOUT_MS = 15000, HUB_IN_PROGRESS_CODE = "in_progress", hubFetch = (input, init) => fetch(input, init), NEVER_REACHED_HUB_CAUSE_CODES;
|
|
15764
|
+
var init_hub_remote_session = __esm(() => {
|
|
15765
|
+
init_logger();
|
|
15766
|
+
NEVER_REACHED_HUB_CAUSE_CODES = new Set([
|
|
15767
|
+
"ECONNREFUSED",
|
|
15768
|
+
"ENOTFOUND",
|
|
15769
|
+
"EAI_AGAIN",
|
|
15770
|
+
"EHOSTUNREACH",
|
|
15771
|
+
"ENETUNREACH",
|
|
15772
|
+
"ERR_INVALID_URL",
|
|
15773
|
+
"CERT_HAS_EXPIRED",
|
|
15774
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
15775
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
15776
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
15777
|
+
"SELF_SIGNED_CERT_IN_CHAIN"
|
|
15778
|
+
]);
|
|
15779
|
+
});
|
|
15211
15780
|
// ../../packages/core/src/storage/session-asks.ts
|
|
15212
15781
|
var exports_session_asks = {};
|
|
15213
15782
|
__export(exports_session_asks, {
|
|
@@ -15573,6 +16142,326 @@ var init_session_asks = __esm(async () => {
|
|
|
15573
16142
|
PENDING_ASK_TTL_MS = 15 * 60 * 1000;
|
|
15574
16143
|
});
|
|
15575
16144
|
|
|
16145
|
+
// ../../packages/core/src/storage/remote-session.ts
|
|
16146
|
+
function remoteSessionRetryDelayMs(attempts, random = Math.random) {
|
|
16147
|
+
const step = Math.max(0, Math.floor(attempts) - 1);
|
|
16148
|
+
const base = Math.min(REMOTE_SESSION_REPLY_BACKOFF_BASE_MS * 2 ** step, REMOTE_SESSION_REPLY_BACKOFF_MAX_MS);
|
|
16149
|
+
const jitter = 0.8 + 0.4 * Math.min(1, Math.max(0, random()));
|
|
16150
|
+
return Math.round(base * jitter);
|
|
16151
|
+
}
|
|
16152
|
+
function toOutboxEntry(row) {
|
|
16153
|
+
return {
|
|
16154
|
+
requestId: row.request_id,
|
|
16155
|
+
hubUrl: row.hub_url,
|
|
16156
|
+
token: row.token,
|
|
16157
|
+
kind: row.kind,
|
|
16158
|
+
replyText: row.reply_text,
|
|
16159
|
+
fromLabel: row.from_label,
|
|
16160
|
+
createdAt: Number(row.created_at),
|
|
16161
|
+
attempts: Number(row.attempts),
|
|
16162
|
+
nextAttemptAt: Number(row.next_attempt_at),
|
|
16163
|
+
...row.last_error ? { lastError: row.last_error } : {}
|
|
16164
|
+
};
|
|
16165
|
+
}
|
|
16166
|
+
function upsertRemoteSessionReplyOutbox(args) {
|
|
16167
|
+
db.query(`INSERT INTO remote_session_reply_outbox
|
|
16168
|
+
(request_id, hub_url, token, kind, reply_text, from_label, created_at, attempts, next_attempt_at, last_error)
|
|
16169
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, NULL)
|
|
16170
|
+
ON CONFLICT(request_id) DO UPDATE SET
|
|
16171
|
+
hub_url = excluded.hub_url,
|
|
16172
|
+
token = excluded.token,
|
|
16173
|
+
kind = excluded.kind,
|
|
16174
|
+
reply_text = excluded.reply_text,
|
|
16175
|
+
from_label = excluded.from_label,
|
|
16176
|
+
attempts = 0,
|
|
16177
|
+
next_attempt_at = 0,
|
|
16178
|
+
last_error = NULL`).run(args.requestId, args.hubUrl, args.token, args.kind, args.replyText, args.fromLabel, args.createdAt ?? Date.now());
|
|
16179
|
+
}
|
|
16180
|
+
function listRemoteSessionReplyOutbox(args = {}) {
|
|
16181
|
+
const now = args.now ?? Date.now();
|
|
16182
|
+
const rows = args.due === false ? db.query("SELECT * FROM remote_session_reply_outbox ORDER BY created_at LIMIT ?").all(args.limit ?? 100) : db.query(`SELECT * FROM remote_session_reply_outbox
|
|
16183
|
+
WHERE next_attempt_at <= ?
|
|
16184
|
+
ORDER BY created_at LIMIT ?`).all(now, args.limit ?? 100);
|
|
16185
|
+
return rows.map(toOutboxEntry);
|
|
16186
|
+
}
|
|
16187
|
+
function deleteRemoteSessionReplyOutbox(requestId) {
|
|
16188
|
+
const result = db.query("DELETE FROM remote_session_reply_outbox WHERE request_id = ?").run(requestId);
|
|
16189
|
+
return Number(result.changes ?? 0) === 1;
|
|
16190
|
+
}
|
|
16191
|
+
function deferRemoteSessionReplyOutbox(args) {
|
|
16192
|
+
const now = args.now ?? Date.now();
|
|
16193
|
+
db.transaction(() => {
|
|
16194
|
+
const row = db.query("SELECT attempts FROM remote_session_reply_outbox WHERE request_id = ?").get(args.requestId);
|
|
16195
|
+
if (!row)
|
|
16196
|
+
return;
|
|
16197
|
+
const attempts = Number(row.attempts) + 1;
|
|
16198
|
+
const delay2 = args.retryMs ?? remoteSessionRetryDelayMs(attempts, args.random);
|
|
16199
|
+
db.query(`UPDATE remote_session_reply_outbox
|
|
16200
|
+
SET attempts = ?, next_attempt_at = ?, last_error = ?
|
|
16201
|
+
WHERE request_id = ?`).run(attempts, now + delay2, args.error.slice(0, 500), args.requestId);
|
|
16202
|
+
})();
|
|
16203
|
+
}
|
|
16204
|
+
function purgeExpiredRemoteSessionReplyOutbox(now = Date.now()) {
|
|
16205
|
+
const cutoff = now - REMOTE_SESSION_REPLY_OUTBOX_TTL_MS;
|
|
16206
|
+
const expired = db.query("SELECT * FROM remote_session_reply_outbox WHERE created_at < ?").all(cutoff).map(toOutboxEntry);
|
|
16207
|
+
if (expired.length) {
|
|
16208
|
+
db.query("DELETE FROM remote_session_reply_outbox WHERE created_at < ?").run(cutoff);
|
|
16209
|
+
}
|
|
16210
|
+
return expired;
|
|
16211
|
+
}
|
|
16212
|
+
var REMOTE_SESSION_INBOX_CLAIM_TTL_MS, REMOTE_SESSION_REPLY_OUTBOX_TTL_MS, REMOTE_SESSION_REPLY_BACKOFF_BASE_MS = 5000, REMOTE_SESSION_REPLY_BACKOFF_MAX_MS = 30000;
|
|
16213
|
+
var init_remote_session = __esm(async () => {
|
|
16214
|
+
await __promiseAll([
|
|
16215
|
+
init_forum_db(),
|
|
16216
|
+
init_session_asks(),
|
|
16217
|
+
init_storage_host()
|
|
16218
|
+
]);
|
|
16219
|
+
registerStorageSchemaInitializer((database) => {
|
|
16220
|
+
database.exec(`
|
|
16221
|
+
CREATE TABLE IF NOT EXISTS remote_session_inbox_claims (
|
|
16222
|
+
request_id TEXT PRIMARY KEY,
|
|
16223
|
+
kind TEXT NOT NULL,
|
|
16224
|
+
topic_id TEXT NOT NULL,
|
|
16225
|
+
payload_hash TEXT NOT NULL,
|
|
16226
|
+
created_at INTEGER NOT NULL
|
|
16227
|
+
);
|
|
16228
|
+
CREATE INDEX IF NOT EXISTS idx_remote_session_inbox_claims_created
|
|
16229
|
+
ON remote_session_inbox_claims(created_at);
|
|
16230
|
+
CREATE TABLE IF NOT EXISTS remote_session_asks (
|
|
16231
|
+
request_id TEXT PRIMARY KEY,
|
|
16232
|
+
caller_topic_id TEXT NOT NULL,
|
|
16233
|
+
user_id TEXT NOT NULL,
|
|
16234
|
+
from_key TEXT NOT NULL,
|
|
16235
|
+
to_key TEXT NOT NULL,
|
|
16236
|
+
caller_thread_root_id TEXT,
|
|
16237
|
+
created_at INTEGER NOT NULL
|
|
16238
|
+
);
|
|
16239
|
+
CREATE INDEX IF NOT EXISTS idx_remote_session_asks_caller
|
|
16240
|
+
ON remote_session_asks(caller_topic_id);
|
|
16241
|
+
CREATE TABLE IF NOT EXISTS remote_session_reply_outbox (
|
|
16242
|
+
request_id TEXT PRIMARY KEY,
|
|
16243
|
+
hub_url TEXT NOT NULL,
|
|
16244
|
+
token TEXT NOT NULL,
|
|
16245
|
+
kind TEXT NOT NULL CHECK (kind IN ('reply', 'error')),
|
|
16246
|
+
reply_text TEXT NOT NULL,
|
|
16247
|
+
from_label TEXT NOT NULL,
|
|
16248
|
+
created_at INTEGER NOT NULL,
|
|
16249
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
16250
|
+
next_attempt_at INTEGER NOT NULL DEFAULT 0,
|
|
16251
|
+
last_error TEXT
|
|
16252
|
+
);
|
|
16253
|
+
`);
|
|
16254
|
+
for (const ddl of [
|
|
16255
|
+
"ALTER TABLE remote_session_inbox_claims ADD COLUMN state TEXT NOT NULL DEFAULT 'completed'",
|
|
16256
|
+
"ALTER TABLE remote_session_inbox_claims ADD COLUMN lease_until INTEGER NOT NULL DEFAULT 0",
|
|
16257
|
+
"ALTER TABLE remote_session_inbox_claims ADD COLUMN payload_json TEXT"
|
|
16258
|
+
]) {
|
|
16259
|
+
try {
|
|
16260
|
+
database.exec(ddl);
|
|
16261
|
+
} catch {}
|
|
16262
|
+
}
|
|
16263
|
+
}, 36);
|
|
16264
|
+
REMOTE_SESSION_INBOX_CLAIM_TTL_MS = 24 * 60 * 60 * 1000;
|
|
16265
|
+
REMOTE_SESSION_REPLY_OUTBOX_TTL_MS = PENDING_ASK_TTL_MS;
|
|
16266
|
+
});
|
|
16267
|
+
|
|
16268
|
+
// ../../packages/core/src/runtime/remote-session-inbox.ts
|
|
16269
|
+
var inboxLocks;
|
|
16270
|
+
var init_remote_session_inbox = __esm(async () => {
|
|
16271
|
+
init_config();
|
|
16272
|
+
init_logger();
|
|
16273
|
+
await __promiseAll([
|
|
16274
|
+
init_peer_forward(),
|
|
16275
|
+
init_forum_db(),
|
|
16276
|
+
init_remote_session(),
|
|
16277
|
+
init_session_inbox()
|
|
16278
|
+
]);
|
|
16279
|
+
inboxLocks = new Map;
|
|
16280
|
+
});
|
|
16281
|
+
|
|
16282
|
+
// ../../packages/core/src/runtime/remote-session-reply-outbox.ts
|
|
16283
|
+
async function deliverHubRemoteReply(route, fromLabel, replyText, kind) {
|
|
16284
|
+
db.transaction(() => {
|
|
16285
|
+
upsertRemoteSessionReplyOutbox({
|
|
16286
|
+
requestId: route.requestId,
|
|
16287
|
+
hubUrl: route.hubUrl,
|
|
16288
|
+
token: route.token,
|
|
16289
|
+
kind,
|
|
16290
|
+
replyText,
|
|
16291
|
+
fromLabel
|
|
16292
|
+
});
|
|
16293
|
+
deleteHubRemoteAskCallbackRow(route.requestId);
|
|
16294
|
+
})();
|
|
16295
|
+
await flushRemoteSessionReplyOutbox();
|
|
16296
|
+
return true;
|
|
16297
|
+
}
|
|
16298
|
+
function flushRemoteSessionReplyOutbox(now = Date.now()) {
|
|
16299
|
+
if (inFlight)
|
|
16300
|
+
return inFlight;
|
|
16301
|
+
inFlight = (async () => {
|
|
16302
|
+
for (const expired of purgeExpiredRemoteSessionReplyOutbox(now)) {
|
|
16303
|
+
logger.warn({ requestId: expired.requestId, attempts: expired.attempts, lastError: expired.lastError }, "session-comm: remote ask reply expired before the hub accepted it");
|
|
16304
|
+
}
|
|
16305
|
+
for (const entry of listRemoteSessionReplyOutbox({ now })) {
|
|
16306
|
+
const result = await hubRemoteReply({
|
|
16307
|
+
hubUrl: entry.hubUrl,
|
|
16308
|
+
token: entry.token,
|
|
16309
|
+
requestId: entry.requestId,
|
|
16310
|
+
kind: entry.kind,
|
|
16311
|
+
replyText: entry.replyText,
|
|
16312
|
+
fromLabel: entry.fromLabel
|
|
16313
|
+
});
|
|
16314
|
+
if (result.settled) {
|
|
16315
|
+
deleteRemoteSessionReplyOutbox(entry.requestId);
|
|
16316
|
+
continue;
|
|
16317
|
+
}
|
|
16318
|
+
deferRemoteSessionReplyOutbox({
|
|
16319
|
+
requestId: entry.requestId,
|
|
16320
|
+
error: result.error,
|
|
16321
|
+
now,
|
|
16322
|
+
random: retryRandom
|
|
16323
|
+
});
|
|
16324
|
+
logger.info({ requestId: entry.requestId, attempts: entry.attempts + 1, error: result.error }, "session-comm: remote ask reply deferred");
|
|
16325
|
+
}
|
|
16326
|
+
})().finally(() => {
|
|
16327
|
+
inFlight = null;
|
|
16328
|
+
});
|
|
16329
|
+
return inFlight;
|
|
16330
|
+
}
|
|
16331
|
+
var retryRandom, inFlight = null;
|
|
16332
|
+
var init_remote_session_reply_outbox = __esm(async () => {
|
|
16333
|
+
init_hub_remote_session();
|
|
16334
|
+
init_logger();
|
|
16335
|
+
await __promiseAll([
|
|
16336
|
+
init_ask_callbacks(),
|
|
16337
|
+
init_remote_session_inbox(),
|
|
16338
|
+
init_forum_db(),
|
|
16339
|
+
init_remote_session()
|
|
16340
|
+
]);
|
|
16341
|
+
retryRandom = Math.random;
|
|
16342
|
+
});
|
|
16343
|
+
|
|
16344
|
+
// ../../packages/core/src/mcp/session-comm/peer-forward.ts
|
|
16345
|
+
var exports_peer_forward = {};
|
|
16346
|
+
__export(exports_peer_forward, {
|
|
16347
|
+
registerPeerSessionBridge: () => registerPeerSessionBridge,
|
|
16348
|
+
peerSessionsForUser: () => peerSessionsForUser,
|
|
16349
|
+
parseHubRemoteReplyRoute: () => parseHubRemoteReplyRoute,
|
|
16350
|
+
isHubRemoteReplyRoute: () => isHubRemoteReplyRoute,
|
|
16351
|
+
forwardToPeer: () => forwardToPeer,
|
|
16352
|
+
deliverPeerReply: () => deliverPeerReply
|
|
16353
|
+
});
|
|
16354
|
+
function isHubRemoteReplyRoute(route) {
|
|
16355
|
+
return route.via === "hub";
|
|
16356
|
+
}
|
|
16357
|
+
function parseHubRemoteReplyRoute(value) {
|
|
16358
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16359
|
+
return null;
|
|
16360
|
+
const route = value;
|
|
16361
|
+
if (route.via !== "hub" || typeof route.hubUrl !== "string" || typeof route.token !== "string" || typeof route.nodeName !== "string" || typeof route.topicId !== "string" || typeof route.requestId !== "string") {
|
|
16362
|
+
return null;
|
|
16363
|
+
}
|
|
16364
|
+
const requestId = route.requestId.trim();
|
|
16365
|
+
const nodeName = route.nodeName.trim();
|
|
16366
|
+
const topicId = route.topicId.trim();
|
|
16367
|
+
const token = route.token.trim();
|
|
16368
|
+
if (!requestId || requestId.length > MAX_ROUTE_REQUEST_ID_LENGTH || !nodeName || nodeName.length > MAX_ROUTE_LABEL_LENGTH || !topicId || topicId.length > MAX_ROUTE_LABEL_LENGTH || token.length > MAX_REMOTE_SESSION_REPLY_TOKEN_LENGTH || !REMOTE_SESSION_REPLY_TOKEN_PATTERN.test(token)) {
|
|
16369
|
+
return null;
|
|
16370
|
+
}
|
|
16371
|
+
const hubUrl = normalizeHubUrl(route.hubUrl.trim(), "remoteReply.hubUrl");
|
|
16372
|
+
if ("error" in hubUrl)
|
|
16373
|
+
return null;
|
|
16374
|
+
return { via: "hub", hubUrl: hubUrl.url, token, nodeName, topicId, requestId };
|
|
16375
|
+
}
|
|
16376
|
+
function loopbackBridgeConfig() {
|
|
16377
|
+
const rawUrl = process.env[PEER_BRIDGE_URL_ENV]?.trim();
|
|
16378
|
+
const token = process.env[PEER_BRIDGE_TOKEN_ENV]?.trim();
|
|
16379
|
+
if (!rawUrl || !token)
|
|
16380
|
+
return null;
|
|
16381
|
+
try {
|
|
16382
|
+
const url = new URL(rawUrl);
|
|
16383
|
+
if (url.protocol !== "http:" || !["127.0.0.1", "::1", "localhost"].includes(url.hostname)) {
|
|
16384
|
+
return null;
|
|
16385
|
+
}
|
|
16386
|
+
return { url: url.toString(), token };
|
|
16387
|
+
} catch {
|
|
16388
|
+
return null;
|
|
16389
|
+
}
|
|
16390
|
+
}
|
|
16391
|
+
async function callLoopbackBridge(request) {
|
|
16392
|
+
const config = loopbackBridgeConfig();
|
|
16393
|
+
if (!config)
|
|
16394
|
+
return { configured: false, result: null };
|
|
16395
|
+
try {
|
|
16396
|
+
const response = await fetch(config.url, {
|
|
16397
|
+
method: "POST",
|
|
16398
|
+
headers: {
|
|
16399
|
+
authorization: `Bearer ${config.token}`,
|
|
16400
|
+
"content-type": "application/json"
|
|
16401
|
+
},
|
|
16402
|
+
body: JSON.stringify(request),
|
|
16403
|
+
signal: AbortSignal.timeout(PEER_BRIDGE_TIMEOUT_MS)
|
|
16404
|
+
});
|
|
16405
|
+
if (!response.ok)
|
|
16406
|
+
return { configured: true, result: null };
|
|
16407
|
+
return { configured: true, result: await response.json() };
|
|
16408
|
+
} catch {
|
|
16409
|
+
return { configured: true, result: null };
|
|
16410
|
+
}
|
|
16411
|
+
}
|
|
16412
|
+
function registerPeerSessionBridge(bridge) {
|
|
16413
|
+
activeBridge = bridge;
|
|
16414
|
+
return () => {
|
|
16415
|
+
if (activeBridge === bridge)
|
|
16416
|
+
activeBridge = null;
|
|
16417
|
+
};
|
|
16418
|
+
}
|
|
16419
|
+
async function forwardToPeer(args) {
|
|
16420
|
+
if (activeBridge)
|
|
16421
|
+
return activeBridge.forward(args);
|
|
16422
|
+
const forwarded = await callLoopbackBridge({ action: "forward", args });
|
|
16423
|
+
if (forwarded.result)
|
|
16424
|
+
return forwarded.result;
|
|
16425
|
+
return {
|
|
16426
|
+
ok: false,
|
|
16427
|
+
error: forwarded.configured ? "remote session bridge is configured but unavailable" : "remote nodes are not connected on this negotium node (standalone mode)"
|
|
16428
|
+
};
|
|
16429
|
+
}
|
|
16430
|
+
async function peerSessionsForUser(userId, sourceQueryId, fromTopicId) {
|
|
16431
|
+
if (activeBridge)
|
|
16432
|
+
return activeBridge.sessions(userId, sourceQueryId, fromTopicId);
|
|
16433
|
+
const sessions = await callLoopbackBridge({
|
|
16434
|
+
action: "sessions",
|
|
16435
|
+
userId,
|
|
16436
|
+
sourceQueryId,
|
|
16437
|
+
fromTopicId
|
|
16438
|
+
});
|
|
16439
|
+
if (sessions.result)
|
|
16440
|
+
return sessions.result;
|
|
16441
|
+
if (sessions.configured)
|
|
16442
|
+
return { ok: false, nodes: [] };
|
|
16443
|
+
return { ok: true, nodes: [] };
|
|
16444
|
+
}
|
|
16445
|
+
async function deliverPeerReply(route, sourceTitle, replyText, kind) {
|
|
16446
|
+
if (isHubRemoteReplyRoute(route)) {
|
|
16447
|
+
return deliverHubRemoteReply(route, sourceTitle, replyText, kind);
|
|
16448
|
+
}
|
|
16449
|
+
if (activeBridge)
|
|
16450
|
+
return activeBridge.reply(route, sourceTitle, replyText, kind);
|
|
16451
|
+
return (await callLoopbackBridge({
|
|
16452
|
+
action: "reply",
|
|
16453
|
+
route,
|
|
16454
|
+
sourceTitle,
|
|
16455
|
+
replyText,
|
|
16456
|
+
kind
|
|
16457
|
+
})).result ?? false;
|
|
16458
|
+
}
|
|
16459
|
+
var MAX_ROUTE_REQUEST_ID_LENGTH = 200, MAX_ROUTE_LABEL_LENGTH = 300, activeBridge = null, PEER_BRIDGE_URL_ENV = "NEGOTIUM_PEER_SESSION_BRIDGE_URL", PEER_BRIDGE_TOKEN_ENV = "NEGOTIUM_PEER_SESSION_BRIDGE_TOKEN", PEER_BRIDGE_TIMEOUT_MS = 5000;
|
|
16460
|
+
var init_peer_forward = __esm(async () => {
|
|
16461
|
+
init_remote_session_grant();
|
|
16462
|
+
await init_remote_session_reply_outbox();
|
|
16463
|
+
});
|
|
16464
|
+
|
|
15576
16465
|
// ../../packages/core/src/runtime/ask-callbacks.ts
|
|
15577
16466
|
var exports_ask_callbacks = {};
|
|
15578
16467
|
__export(exports_ask_callbacks, {
|
|
@@ -15580,21 +16469,25 @@ __export(exports_ask_callbacks, {
|
|
|
15580
16469
|
registerAskCallback: () => registerAskCallback,
|
|
15581
16470
|
purgeStaleAsks: () => purgeStaleAsks,
|
|
15582
16471
|
failInterruptedRemoteAskCallbacks: () => failInterruptedRemoteAskCallbacks,
|
|
16472
|
+
deleteHubRemoteAskCallbackRow: () => deleteHubRemoteAskCallbackRow,
|
|
15583
16473
|
cancelAskCallbacksForTopic: () => cancelAskCallbacksForTopic
|
|
15584
16474
|
});
|
|
15585
16475
|
function registerAskCallback(entry) {
|
|
15586
16476
|
pendingAsks.set(entry.targetQueryId, entry);
|
|
15587
16477
|
if (entry.remoteReply) {
|
|
16478
|
+
const route = entry.remoteReply;
|
|
16479
|
+
const hub = isHubRemoteReplyRoute(route);
|
|
15588
16480
|
db.query(`INSERT INTO remote_ask_callbacks
|
|
15589
|
-
(target_query_id, request_id, node_name, node_cell_id, topic_id, user_id, created_at)
|
|
15590
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
16481
|
+
(target_query_id, request_id, node_name, node_cell_id, topic_id, user_id, created_at, route_json)
|
|
16482
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
15591
16483
|
ON CONFLICT(target_query_id) DO UPDATE SET
|
|
15592
16484
|
request_id = excluded.request_id,
|
|
15593
16485
|
node_name = excluded.node_name,
|
|
15594
16486
|
node_cell_id = excluded.node_cell_id,
|
|
15595
16487
|
topic_id = excluded.topic_id,
|
|
15596
16488
|
user_id = excluded.user_id,
|
|
15597
|
-
created_at = excluded.created_at
|
|
16489
|
+
created_at = excluded.created_at,
|
|
16490
|
+
route_json = excluded.route_json`).run(entry.targetQueryId, route.requestId, route.nodeName, hub ? "" : route.nodeCellId, route.topicId, hub ? "" : route.userId, entry.createdAt, hub ? JSON.stringify(route) : null);
|
|
15598
16491
|
}
|
|
15599
16492
|
}
|
|
15600
16493
|
function resolveAskCallback(queryId) {
|
|
@@ -15605,9 +16498,14 @@ function resolveAskCallback(queryId) {
|
|
|
15605
16498
|
entry.timedOut = true;
|
|
15606
16499
|
}
|
|
15607
16500
|
pendingAsks.delete(queryId);
|
|
15608
|
-
|
|
16501
|
+
if (!entry.remoteReply || !isHubRemoteReplyRoute(entry.remoteReply)) {
|
|
16502
|
+
db.query("DELETE FROM remote_ask_callbacks WHERE target_query_id = ?").run(queryId);
|
|
16503
|
+
}
|
|
15609
16504
|
return entry;
|
|
15610
16505
|
}
|
|
16506
|
+
function deleteHubRemoteAskCallbackRow(requestId) {
|
|
16507
|
+
db.query("DELETE FROM remote_ask_callbacks WHERE request_id = ? AND route_json IS NOT NULL").run(requestId);
|
|
16508
|
+
}
|
|
15611
16509
|
function purgeStaleAsks(now = Date.now()) {
|
|
15612
16510
|
for (const [id, entry] of pendingAsks) {
|
|
15613
16511
|
if (now - entry.createdAt > MAX_ASK_AGE_MS)
|
|
@@ -15626,19 +16524,36 @@ function cancelAskCallbacksForTopic(topicId) {
|
|
|
15626
16524
|
}
|
|
15627
16525
|
return cancelled;
|
|
15628
16526
|
}
|
|
15629
|
-
|
|
15630
|
-
|
|
16527
|
+
function durableRoute(row) {
|
|
16528
|
+
if (row.route_json) {
|
|
16529
|
+
try {
|
|
16530
|
+
return parseHubRemoteReplyRoute(JSON.parse(row.route_json));
|
|
16531
|
+
} catch {
|
|
16532
|
+
return null;
|
|
16533
|
+
}
|
|
16534
|
+
}
|
|
16535
|
+
return {
|
|
16536
|
+
nodeName: row.node_name,
|
|
16537
|
+
nodeCellId: row.node_cell_id,
|
|
16538
|
+
topicId: row.topic_id,
|
|
16539
|
+
userId: row.user_id,
|
|
16540
|
+
requestId: row.request_id
|
|
16541
|
+
};
|
|
16542
|
+
}
|
|
16543
|
+
async function failInterruptedRemoteAskCallbacks(options = {}) {
|
|
16544
|
+
const rows = db.query(`SELECT target_query_id, request_id, node_name, node_cell_id, topic_id, user_id, route_json
|
|
15631
16545
|
FROM remote_ask_callbacks
|
|
15632
16546
|
ORDER BY created_at ASC`).all();
|
|
15633
16547
|
let failed = 0;
|
|
15634
16548
|
for (const row of rows) {
|
|
15635
|
-
const
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
16549
|
+
const route = durableRoute(row);
|
|
16550
|
+
if (!route) {
|
|
16551
|
+
db.query("DELETE FROM remote_ask_callbacks WHERE target_query_id = ?").run(row.target_query_id);
|
|
16552
|
+
continue;
|
|
16553
|
+
}
|
|
16554
|
+
if (options.routes === "hub" && !isHubRemoteReplyRoute(route))
|
|
16555
|
+
continue;
|
|
16556
|
+
const delivered = await deliverPeerReply(route, "peer", "The remote worker restarted before this ask completed. Please retry the request.", "error");
|
|
15642
16557
|
if (!delivered)
|
|
15643
16558
|
continue;
|
|
15644
16559
|
db.query("DELETE FROM remote_ask_callbacks WHERE target_query_id = ?").run(row.target_query_id);
|
|
@@ -15649,6 +16564,7 @@ async function failInterruptedRemoteAskCallbacks() {
|
|
|
15649
16564
|
var pendingAsks, MAX_ASK_AGE_MS;
|
|
15650
16565
|
var init_ask_callbacks = __esm(async () => {
|
|
15651
16566
|
await __promiseAll([
|
|
16567
|
+
init_peer_forward(),
|
|
15652
16568
|
init_forum_db(),
|
|
15653
16569
|
init_session_asks(),
|
|
15654
16570
|
init_storage_host()
|
|
@@ -15665,6 +16581,9 @@ var init_ask_callbacks = __esm(async () => {
|
|
|
15665
16581
|
created_at INTEGER NOT NULL
|
|
15666
16582
|
)
|
|
15667
16583
|
`);
|
|
16584
|
+
try {
|
|
16585
|
+
database.exec("ALTER TABLE remote_ask_callbacks ADD COLUMN route_json TEXT");
|
|
16586
|
+
} catch {}
|
|
15668
16587
|
});
|
|
15669
16588
|
pendingAsks = new Map;
|
|
15670
16589
|
MAX_ASK_AGE_MS = PENDING_ASK_TTL_MS;
|
|
@@ -17757,6 +18676,7 @@ __export(exports_turn_runner, {
|
|
|
17757
18676
|
startDurableTurnRequestWorker: () => startDurableTurnRequestWorker,
|
|
17758
18677
|
startAiTurn: () => startAiTurn,
|
|
17759
18678
|
selectableModel: () => selectableModel,
|
|
18679
|
+
sameTurnActor: () => sameTurnActor,
|
|
17760
18680
|
safeAttachmentFilename: () => safeAttachmentFilename,
|
|
17761
18681
|
resolveWorkerModel: () => resolveWorkerModel,
|
|
17762
18682
|
resolveWorkerDefaultModel: () => resolveWorkerDefaultModel,
|
|
@@ -18005,20 +18925,27 @@ async function markPendingAskFile(pending, state) {
|
|
|
18005
18925
|
logger.warn({ err: err2, pendingAsk: pending.pendingAsk, state }, "sessions: pending ask mark failed");
|
|
18006
18926
|
}
|
|
18007
18927
|
}
|
|
18008
|
-
async function deliverAskCallbackToCaller(pending, sourceLabel, body, kind) {
|
|
18928
|
+
async function deliverAskCallbackToCaller(pending, sourceLabel, body, kind, options = {}) {
|
|
18009
18929
|
if (pending.remoteReply) {
|
|
18010
|
-
const { deliverPeerReply: deliverPeerReply2 } = await
|
|
18011
|
-
|
|
18930
|
+
const { deliverPeerReply: deliverPeerReply2 } = await init_peer_forward().then(() => exports_peer_forward);
|
|
18931
|
+
const delivered = await deliverPeerReply2(pending.remoteReply, sourceLabel, body, kind);
|
|
18932
|
+
if (delivered)
|
|
18933
|
+
options.onRecorded?.();
|
|
18934
|
+
return delivered;
|
|
18012
18935
|
}
|
|
18013
18936
|
const callerTopic = getTopic(pending.callerTopicId);
|
|
18014
18937
|
const heading = kind === "error" ? `Error from ${sourceLabel}` : `Reply from ${sourceLabel}`;
|
|
18015
18938
|
const prompt = `[${heading}]
|
|
18016
18939
|
|
|
18017
18940
|
${body}`;
|
|
18941
|
+
const record = (agentType) => db.transaction(() => {
|
|
18942
|
+
appendAskReplyMessage(pending.callerTopicId, prompt, sourceLabel, body, kind, agentType ?? undefined, pending.callerThreadRootId);
|
|
18943
|
+
options.onRecorded?.();
|
|
18944
|
+
})();
|
|
18018
18945
|
await markPendingAskFile(pending, "reply_ready");
|
|
18019
18946
|
if (!callerTopic?.agent) {
|
|
18020
18947
|
try {
|
|
18021
|
-
|
|
18948
|
+
record(undefined);
|
|
18022
18949
|
await clearPendingAskFile(pending);
|
|
18023
18950
|
return true;
|
|
18024
18951
|
} catch (err2) {
|
|
@@ -18049,16 +18976,25 @@ ${body}`;
|
|
|
18049
18976
|
}
|
|
18050
18977
|
});
|
|
18051
18978
|
if (queued) {
|
|
18052
|
-
|
|
18979
|
+
try {
|
|
18980
|
+
record(callerTopic.agent);
|
|
18981
|
+
} catch (err2) {
|
|
18982
|
+
interSessionQueue.remove(pending.callerTopicId, pending.requestId);
|
|
18983
|
+
logger.warn({ err: err2, requestId: pending.requestId, callerTopicId: pending.callerTopicId }, "sessions: ask callback record failed after enqueue; queue entry withdrawn");
|
|
18984
|
+
return false;
|
|
18985
|
+
}
|
|
18053
18986
|
await markPendingAskFile(pending, "queued_for_caller");
|
|
18054
18987
|
return true;
|
|
18055
18988
|
}
|
|
18056
18989
|
if (interSessionQueue.hasRequest(pending.callerTopicId, pending.requestId)) {
|
|
18990
|
+
db.transaction(() => {
|
|
18991
|
+
options.onRecorded?.();
|
|
18992
|
+
})();
|
|
18057
18993
|
await markPendingAskFile(pending, "queued_for_caller");
|
|
18058
18994
|
return true;
|
|
18059
18995
|
}
|
|
18060
18996
|
logger.warn({ requestId: pending.requestId, callerTopicId: pending.callerTopicId, source: sourceLabel }, "sessions: ask callback could not enter caller batch; appending direct fallback");
|
|
18061
|
-
|
|
18997
|
+
record(callerTopic.agent);
|
|
18062
18998
|
await clearPendingAskFile(pending);
|
|
18063
18999
|
return true;
|
|
18064
19000
|
}
|
|
@@ -18128,6 +19064,15 @@ function resolveTurnHostMcpServers(topic, sessionType) {
|
|
|
18128
19064
|
return;
|
|
18129
19065
|
return getTopicHostMcpGrant(topic.id) ?? undefined;
|
|
18130
19066
|
}
|
|
19067
|
+
function sameTurnActor(runningActorUserId, incomingActorUserId) {
|
|
19068
|
+
return (runningActorUserId ?? null) === (incomingActorUserId ?? null);
|
|
19069
|
+
}
|
|
19070
|
+
function remoteTurnHasSameActor(topicId, incomingActorUserId, ownRequestIds) {
|
|
19071
|
+
const active = getActiveRuntimeUserTurnRequest(topicId);
|
|
19072
|
+
if (!active || ownRequestIds.includes(active.requestId))
|
|
19073
|
+
return false;
|
|
19074
|
+
return sameTurnActor(active.execution?.actorUserId, incomingActorUserId);
|
|
19075
|
+
}
|
|
18131
19076
|
function mergeSupersedingUserTurn(running, incoming) {
|
|
18132
19077
|
const userMessages = [
|
|
18133
19078
|
...running.userMessages ?? [legacyUserTurnEnvelope(running.prompt, running.attachments)],
|
|
@@ -18148,6 +19093,8 @@ function serializableUserTurnExecution(params) {
|
|
|
18148
19093
|
runtimeEpoch: params._runtimeEpoch ?? getRuntimeTopicEpoch(params.topic.id),
|
|
18149
19094
|
sourceRequestId: params.requestId,
|
|
18150
19095
|
actorUserId: params.actorUserId,
|
|
19096
|
+
actorTopicScope: params.actorTopicScope,
|
|
19097
|
+
remoteSession: params.remoteSession,
|
|
18151
19098
|
agentOverride: params.agentOverride,
|
|
18152
19099
|
modelOverride: params.modelOverride,
|
|
18153
19100
|
effortOverride: params.effortOverride,
|
|
@@ -18233,6 +19180,8 @@ async function drainOneDurableUserTurn() {
|
|
|
18233
19180
|
topic,
|
|
18234
19181
|
userId: request.userId,
|
|
18235
19182
|
actorUserId: execution?.actorUserId,
|
|
19183
|
+
actorTopicScope: actorTopicScopeFrom(execution?.actorTopicScope),
|
|
19184
|
+
remoteSession: remoteSessionGrantFrom(execution?.remoteSession),
|
|
18236
19185
|
vaultUserId: execution?.vaultUserId,
|
|
18237
19186
|
prompt: request.prompt,
|
|
18238
19187
|
_userMessages: request.userMessages,
|
|
@@ -18446,7 +19395,9 @@ function startAiTurn(params) {
|
|
|
18446
19395
|
return null;
|
|
18447
19396
|
}
|
|
18448
19397
|
if (decision.action === "remote-abort-wait") {
|
|
18449
|
-
|
|
19398
|
+
if (remoteTurnHasSameActor(topicId, params.actorUserId, durableRequestIds)) {
|
|
19399
|
+
requestRuntimeTurnAbort(topicId, "internal");
|
|
19400
|
+
}
|
|
18450
19401
|
const queuedQueryId = waitToStartRemoteUserTurn({
|
|
18451
19402
|
...params,
|
|
18452
19403
|
topic,
|
|
@@ -18464,6 +19415,23 @@ function startAiTurn(params) {
|
|
|
18464
19415
|
}
|
|
18465
19416
|
if (decision.action === "abort-replace") {
|
|
18466
19417
|
const running = decision.running;
|
|
19418
|
+
if (isUserOrigin(running.origin) && !sameTurnActor(running.actorUserId, params.actorUserId)) {
|
|
19419
|
+
const queuedQueryId2 = waitToStartRemoteUserTurn({
|
|
19420
|
+
...params,
|
|
19421
|
+
topic,
|
|
19422
|
+
prompt,
|
|
19423
|
+
attachments: attachments2,
|
|
19424
|
+
sessionId,
|
|
19425
|
+
_userMessages: userMessages,
|
|
19426
|
+
_conversationPrompts: conversationPrompts,
|
|
19427
|
+
_loggedUserMessageCount: loggedUserMessageCount,
|
|
19428
|
+
_durableRequestIds: durableRequestIds,
|
|
19429
|
+
_runtimeEpoch: runtimeEpoch
|
|
19430
|
+
}, queryId);
|
|
19431
|
+
announceQueuedUserTurn({ ...params, topic }, queuedQueryId2);
|
|
19432
|
+
logger.info({ topicId, queryId: queuedQueryId2, runningQueryId: running.queryId }, "ai: user turn from another actor queued behind the running turn");
|
|
19433
|
+
return queuedQueryId2;
|
|
19434
|
+
}
|
|
18467
19435
|
let omitRequestIds;
|
|
18468
19436
|
if (isUserOrigin(running.origin)) {
|
|
18469
19437
|
if (running.providerTurnContentObserved && running.sessionId) {
|
|
@@ -18523,6 +19491,7 @@ function startAiTurn(params) {
|
|
|
18523
19491
|
origin,
|
|
18524
19492
|
prompt,
|
|
18525
19493
|
userMessages,
|
|
19494
|
+
...isUserOrigin(origin) && params.actorUserId !== undefined ? { actorUserId: params.actorUserId } : {},
|
|
18526
19495
|
durableRequestIds,
|
|
18527
19496
|
attachments: attachments2,
|
|
18528
19497
|
sessionId,
|
|
@@ -18568,7 +19537,9 @@ function startAiTurn(params) {
|
|
|
18568
19537
|
return rejectIsolatedTurnBeforeDispatch("failed to claim isolated turn slot");
|
|
18569
19538
|
}
|
|
18570
19539
|
if (isUserOrigin(origin)) {
|
|
18571
|
-
|
|
19540
|
+
if (remoteTurnHasSameActor(topicId, params.actorUserId, durableRequestIds)) {
|
|
19541
|
+
requestRuntimeTurnAbort(topicId, "internal");
|
|
19542
|
+
}
|
|
18572
19543
|
const queuedQueryId = waitToStartRemoteUserTurn({
|
|
18573
19544
|
...params,
|
|
18574
19545
|
topic,
|
|
@@ -18828,6 +19799,8 @@ function startAiTurn(params) {
|
|
|
18828
19799
|
sessionId,
|
|
18829
19800
|
userId,
|
|
18830
19801
|
actorUserId: params.actorUserId,
|
|
19802
|
+
actorTopicScope: params.actorTopicScope,
|
|
19803
|
+
remoteSession: params.remoteSession,
|
|
18831
19804
|
vaultUserId,
|
|
18832
19805
|
session: sessionName,
|
|
18833
19806
|
sessionType: sessionType ?? (isManager ? "manager" : "forum"),
|
|
@@ -18892,12 +19865,10 @@ function startAiTurn(params) {
|
|
|
18892
19865
|
error: "Provider session expired and conversation history could not be rebuilt; the existing session was preserved for retry."
|
|
18893
19866
|
};
|
|
18894
19867
|
} else {
|
|
18895
|
-
if (!silent)
|
|
18896
|
-
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
18897
19868
|
const retrySessionId = retry.sessionId;
|
|
18898
19869
|
logger.info({
|
|
18899
19870
|
topicId,
|
|
18900
|
-
|
|
19871
|
+
queryId,
|
|
18901
19872
|
agent: agentKind,
|
|
18902
19873
|
retrySessionId: retrySessionId ? retrySessionId.slice(0, 8) : null
|
|
18903
19874
|
}, "ai: retrying query after session expiry");
|
|
@@ -18908,6 +19879,9 @@ function startAiTurn(params) {
|
|
|
18908
19879
|
startAiTurn({
|
|
18909
19880
|
topic,
|
|
18910
19881
|
userId,
|
|
19882
|
+
actorUserId: params.actorUserId,
|
|
19883
|
+
actorTopicScope: params.actorTopicScope,
|
|
19884
|
+
remoteSession: params.remoteSession,
|
|
18911
19885
|
vaultUserId,
|
|
18912
19886
|
prompt,
|
|
18913
19887
|
_userMessages: userMessages,
|
|
@@ -18942,7 +19916,8 @@ function startAiTurn(params) {
|
|
|
18942
19916
|
peerBridge,
|
|
18943
19917
|
askReplySources,
|
|
18944
19918
|
_runtimeEpoch: runtimeEpoch,
|
|
18945
|
-
_sessionRetried: true
|
|
19919
|
+
_sessionRetried: true,
|
|
19920
|
+
_queryId: queryId
|
|
18946
19921
|
});
|
|
18947
19922
|
return;
|
|
18948
19923
|
}
|
|
@@ -18951,12 +19926,13 @@ function startAiTurn(params) {
|
|
|
18951
19926
|
if (emptyResponseRetried) {
|
|
18952
19927
|
outcome = { kind: "provider-error", error: outcome.error };
|
|
18953
19928
|
} else {
|
|
18954
|
-
|
|
18955
|
-
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
18956
|
-
logger.info({ topicId, prevQueryId: queryId, agent: agentKind }, "ai: retrying query after empty provider response");
|
|
19929
|
+
logger.info({ topicId, queryId, agent: agentKind }, "ai: retrying query after empty provider response");
|
|
18957
19930
|
startAiTurn({
|
|
18958
19931
|
topic,
|
|
18959
19932
|
userId,
|
|
19933
|
+
actorUserId: params.actorUserId,
|
|
19934
|
+
actorTopicScope: params.actorTopicScope,
|
|
19935
|
+
remoteSession: params.remoteSession,
|
|
18960
19936
|
vaultUserId,
|
|
18961
19937
|
prompt,
|
|
18962
19938
|
_userMessages: userMessages,
|
|
@@ -18992,7 +19968,8 @@ function startAiTurn(params) {
|
|
|
18992
19968
|
askReplySources,
|
|
18993
19969
|
_runtimeEpoch: runtimeEpoch,
|
|
18994
19970
|
_sessionRetried: sessionRetried,
|
|
18995
|
-
_emptyResponseRetried: true
|
|
19971
|
+
_emptyResponseRetried: true,
|
|
19972
|
+
_queryId: queryId
|
|
18996
19973
|
});
|
|
18997
19974
|
return;
|
|
18998
19975
|
}
|
|
@@ -19164,8 +20141,10 @@ var init_turn_runner = __esm(async () => {
|
|
|
19164
20141
|
init_builders();
|
|
19165
20142
|
init_state();
|
|
19166
20143
|
init_types2();
|
|
20144
|
+
init_actor_topic_scope();
|
|
19167
20145
|
init_attachments();
|
|
19168
20146
|
init_errors();
|
|
20147
|
+
init_remote_session_grant();
|
|
19169
20148
|
init_user_turn_envelope();
|
|
19170
20149
|
init_wiki_summary_names();
|
|
19171
20150
|
init_user_turn_envelope();
|
|
@@ -19191,6 +20170,7 @@ var init_turn_runner = __esm(async () => {
|
|
|
19191
20170
|
init_app_settings(),
|
|
19192
20171
|
init_browser_profiles(),
|
|
19193
20172
|
init_conversations(),
|
|
20173
|
+
init_forum_db(),
|
|
19194
20174
|
init_runtime_leases(),
|
|
19195
20175
|
init_runtime_topic_state(),
|
|
19196
20176
|
init_runtime_turn_requests(),
|
|
@@ -19647,6 +20627,18 @@ ${card.task}${reportInstruction}`;
|
|
|
19647
20627
|
].join(`
|
|
19648
20628
|
`));
|
|
19649
20629
|
}
|
|
20630
|
+
function assertedActorScope(ctx) {
|
|
20631
|
+
if (ctx.surface !== "otium" || !ctx.actorTopicScope)
|
|
20632
|
+
return null;
|
|
20633
|
+
const input = {
|
|
20634
|
+
surface: ctx.surface,
|
|
20635
|
+
currentTopicId: ctx.topicId,
|
|
20636
|
+
actorTopicScope: ctx.actorTopicScope
|
|
20637
|
+
};
|
|
20638
|
+
const visible = actorReachableTopicIds(input);
|
|
20639
|
+
const owned = actorOwnedTopicIds(input);
|
|
20640
|
+
return visible && owned ? { visible, owned } : null;
|
|
20641
|
+
}
|
|
19650
20642
|
function ownedSubagentTree(ctx) {
|
|
19651
20643
|
const parent = host.storage.getTopic(ctx.topicId);
|
|
19652
20644
|
if (!parent)
|
|
@@ -19660,37 +20652,46 @@ ${card.task}${reportInstruction}`;
|
|
|
19660
20652
|
error: "Error: subagent management is only available in agent rooms."
|
|
19661
20653
|
};
|
|
19662
20654
|
}
|
|
20655
|
+
const actorScope = assertedActorScope(ctx);
|
|
19663
20656
|
const rootOwnerId = parent.participants.find((participant) => participant.role === "owner")?.userId;
|
|
20657
|
+
const descendants = rootOwnerId ? (() => {
|
|
20658
|
+
const topics = host.storage.listTopics();
|
|
20659
|
+
const byParent = new Map;
|
|
20660
|
+
for (const topic of topics) {
|
|
20661
|
+
if (!topic.parentTopicId || !topic.isSubagent)
|
|
20662
|
+
continue;
|
|
20663
|
+
const siblings = byParent.get(topic.parentTopicId) ?? [];
|
|
20664
|
+
siblings.push(topic);
|
|
20665
|
+
byParent.set(topic.parentTopicId, siblings);
|
|
20666
|
+
}
|
|
20667
|
+
for (const siblings of byParent.values()) {
|
|
20668
|
+
siblings.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
20669
|
+
}
|
|
20670
|
+
const descendants2 = [];
|
|
20671
|
+
const visited = new Set([ctx.topicId]);
|
|
20672
|
+
const visit = (parentId) => {
|
|
20673
|
+
for (const child of byParent.get(parentId) ?? []) {
|
|
20674
|
+
if (visited.has(child.id))
|
|
20675
|
+
continue;
|
|
20676
|
+
visited.add(child.id);
|
|
20677
|
+
if (!child.participants.some((participant) => participant.userId === rootOwnerId && participant.role === "owner")) {
|
|
20678
|
+
continue;
|
|
20679
|
+
}
|
|
20680
|
+
descendants2.push(child);
|
|
20681
|
+
visit(child.id);
|
|
20682
|
+
}
|
|
20683
|
+
};
|
|
20684
|
+
visit(ctx.topicId);
|
|
20685
|
+
return descendants2;
|
|
20686
|
+
})() : [];
|
|
20687
|
+
const children = actorScope ? descendants.filter((child) => actorScope.visible.has(child.id)) : descendants;
|
|
19664
20688
|
return {
|
|
19665
20689
|
ok: true,
|
|
19666
20690
|
parent,
|
|
19667
|
-
children
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
|
|
19671
|
-
if (!topic.parentTopicId || !topic.isSubagent)
|
|
19672
|
-
continue;
|
|
19673
|
-
const siblings = byParent.get(topic.parentTopicId) ?? [];
|
|
19674
|
-
siblings.push(topic);
|
|
19675
|
-
byParent.set(topic.parentTopicId, siblings);
|
|
19676
|
-
}
|
|
19677
|
-
const descendants = [];
|
|
19678
|
-
const visited = new Set([ctx.topicId]);
|
|
19679
|
-
const visit = (parentId) => {
|
|
19680
|
-
for (const child of byParent.get(parentId) ?? []) {
|
|
19681
|
-
if (visited.has(child.id))
|
|
19682
|
-
continue;
|
|
19683
|
-
visited.add(child.id);
|
|
19684
|
-
if (!child.participants.some((participant) => participant.userId === rootOwnerId && participant.role === "owner")) {
|
|
19685
|
-
continue;
|
|
19686
|
-
}
|
|
19687
|
-
descendants.push(child);
|
|
19688
|
-
visit(child.id);
|
|
19689
|
-
}
|
|
19690
|
-
};
|
|
19691
|
-
visit(ctx.topicId);
|
|
19692
|
-
return descendants;
|
|
19693
|
-
})() : []
|
|
20691
|
+
children,
|
|
20692
|
+
owned: (childId) => children.some((child) => child.id === childId) && (!actorScope || actorScope.owned.has(childId)),
|
|
20693
|
+
visibleTarget: (topicId) => topicId === ctx.topicId || children.some((child) => child.id === topicId),
|
|
20694
|
+
visibleTellTargets: (targetIds) => actorScope ? targetIds.filter((targetId) => targetId === ctx.topicId || actorScope.visible.has(targetId) || children.some((child) => child.id === targetId)) : [...targetIds]
|
|
19694
20695
|
};
|
|
19695
20696
|
}
|
|
19696
20697
|
function subagentStatus(parentTopicId, childTopicId) {
|
|
@@ -19754,7 +20755,7 @@ ${card.task}${reportInstruction}`;
|
|
|
19754
20755
|
status: subagentStatus(child.parentTopicId ?? ctx.topicId, child.id),
|
|
19755
20756
|
agent: child.agent ?? null,
|
|
19756
20757
|
model: child.defaultModel ?? null,
|
|
19757
|
-
tell_target_topic_ids: host.sessionCommunication.listTellTargetIds(child.id),
|
|
20758
|
+
tell_target_topic_ids: result.visibleTellTargets(host.sessionCommunication.listTellTargetIds(child.id)),
|
|
19758
20759
|
created_at: child.createdAt
|
|
19759
20760
|
}));
|
|
19760
20761
|
return textResult(JSON.stringify({ subagents: children }, null, 2));
|
|
@@ -19771,7 +20772,7 @@ ${card.task}${reportInstruction}`;
|
|
|
19771
20772
|
if (!result.ok)
|
|
19772
20773
|
return errorResult(result.error);
|
|
19773
20774
|
const topicId = String(input.topic_id ?? "").trim();
|
|
19774
|
-
if (!result.
|
|
20775
|
+
if (!result.owned(topicId)) {
|
|
19775
20776
|
return errorResult("Error: topic is not a descendant subagent managed by this room.");
|
|
19776
20777
|
}
|
|
19777
20778
|
const cardMessage = findSubagentCardMessage(topicId);
|
|
@@ -19791,7 +20792,7 @@ ${card.task}${reportInstruction}`;
|
|
|
19791
20792
|
if (!result.ok)
|
|
19792
20793
|
return errorResult(result.error);
|
|
19793
20794
|
const topicId = typeof input.topic_id === "string" ? input.topic_id.trim() : "";
|
|
19794
|
-
const child = result.children.find((candidate) => candidate.id === topicId);
|
|
20795
|
+
const child = result.owned(topicId) ? result.children.find((candidate) => candidate.id === topicId) : undefined;
|
|
19795
20796
|
if (!child) {
|
|
19796
20797
|
return errorResult("Error: no owned descendant subagent with that topic_id exists under this room.");
|
|
19797
20798
|
}
|
|
@@ -19817,11 +20818,10 @@ ${card.task}${reportInstruction}`;
|
|
|
19817
20818
|
return errorResult(result.error);
|
|
19818
20819
|
const sourceId = String(input.subagent_topic_id ?? "").trim();
|
|
19819
20820
|
const targetId = String(input.target_topic_id ?? "").trim();
|
|
19820
|
-
|
|
19821
|
-
if (!descendantIds.has(sourceId)) {
|
|
20821
|
+
if (!result.owned(sourceId)) {
|
|
19822
20822
|
return errorResult("Error: source is not a descendant subagent managed by this room.");
|
|
19823
20823
|
}
|
|
19824
|
-
if (
|
|
20824
|
+
if (!result.visibleTarget(targetId)) {
|
|
19825
20825
|
return errorResult("Error: target is outside this room's managed subagent tree.");
|
|
19826
20826
|
}
|
|
19827
20827
|
if (host.storage.getTopic(sourceId)?.parentTopicId === targetId) {
|
|
@@ -19844,11 +20844,10 @@ ${card.task}${reportInstruction}`;
|
|
|
19844
20844
|
return errorResult(result.error);
|
|
19845
20845
|
const sourceId = String(input.subagent_topic_id ?? "").trim();
|
|
19846
20846
|
const targetId = String(input.target_topic_id ?? "").trim();
|
|
19847
|
-
|
|
19848
|
-
if (!descendantIds.has(sourceId)) {
|
|
20847
|
+
if (!result.owned(sourceId)) {
|
|
19849
20848
|
return errorResult("Error: source is not a descendant subagent managed by this room.");
|
|
19850
20849
|
}
|
|
19851
|
-
if (
|
|
20850
|
+
if (!result.visibleTarget(targetId)) {
|
|
19852
20851
|
return errorResult("Error: target is outside this room's managed subagent tree.");
|
|
19853
20852
|
}
|
|
19854
20853
|
const removed = host.sessionCommunication.revokeTellTarget(sourceId, targetId);
|
|
@@ -19930,6 +20929,7 @@ var init_spawn_subagent = __esm(async () => {
|
|
|
19930
20929
|
init_types();
|
|
19931
20930
|
await __promiseAll([
|
|
19932
20931
|
init_bus(),
|
|
20932
|
+
init_actor_topic_reach(),
|
|
19933
20933
|
init_api_messages(),
|
|
19934
20934
|
init_api_topics(),
|
|
19935
20935
|
init_runtime_leases(),
|
|
@@ -20072,6 +21072,7 @@ var init_spawn_subagent = __esm(async () => {
|
|
|
20072
21072
|
|
|
20073
21073
|
// ../../packages/core/src/agents/public-helpers.ts
|
|
20074
21074
|
init_codex_tree_kill();
|
|
21075
|
+
init_explicit_agent_switch();
|
|
20075
21076
|
await __promiseAll([
|
|
20076
21077
|
init_archiver(),
|
|
20077
21078
|
init_auth_check(),
|
|
@@ -20439,6 +21440,7 @@ function switchApiTopicAgent(opts) {
|
|
|
20439
21440
|
}
|
|
20440
21441
|
|
|
20441
21442
|
// ../../packages/core/src/agents/self-config-core.ts
|
|
21443
|
+
init_explicit_agent_switch();
|
|
20442
21444
|
init_model_catalog();
|
|
20443
21445
|
init_config();
|
|
20444
21446
|
init_config_helpers();
|
|
@@ -20480,47 +21482,6 @@ function err(text2) {
|
|
|
20480
21482
|
function errorMessage(error) {
|
|
20481
21483
|
return error instanceof Error ? error.message : String(error);
|
|
20482
21484
|
}
|
|
20483
|
-
function escapeRegExp2(value) {
|
|
20484
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20485
|
-
}
|
|
20486
|
-
function agentAliases(agent) {
|
|
20487
|
-
switch (agent) {
|
|
20488
|
-
case "codex":
|
|
20489
|
-
return ["codex", "\uCF54\uB371\uC2A4", "gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol", "gpt-6-astra"];
|
|
20490
|
-
case "claude":
|
|
20491
|
-
return ["claude", "\uD074\uB85C\uB4DC", "sonnet", "opus", "fable"];
|
|
20492
|
-
case "maestro":
|
|
20493
|
-
return [
|
|
20494
|
-
"maestro",
|
|
20495
|
-
"\uB9C8\uC5D0\uC2A4\uD2B8\uB85C",
|
|
20496
|
-
"\uBA54\uC2A4\uD2B8\uB85C",
|
|
20497
|
-
"deepseek",
|
|
20498
|
-
"deepseek-pro",
|
|
20499
|
-
"kimi",
|
|
20500
|
-
"kimi-pro",
|
|
20501
|
-
"kimi-k3",
|
|
20502
|
-
"kimi-code",
|
|
20503
|
-
"kimi-k2.7-code",
|
|
20504
|
-
"\uB525\uC2DC\uD06C",
|
|
20505
|
-
"\uD0A4\uBBF8"
|
|
20506
|
-
];
|
|
20507
|
-
}
|
|
20508
|
-
}
|
|
20509
|
-
function hasExplicitAgentSwitchRequest(prompt, agent) {
|
|
20510
|
-
if (!prompt?.trim())
|
|
20511
|
-
return false;
|
|
20512
|
-
const text2 = prompt.toLowerCase().replace(/\s+/g, " ").trim();
|
|
20513
|
-
const target = `(?:${agentAliases(agent).map(escapeRegExp2).join("|")})`;
|
|
20514
|
-
const switchVerb = "(?:\uBC14\uAFD4|\uBC14\uAFD4\uC918|\uBCC0\uACBD|\uBCC0\uACBD\uD574|\uC804\uD658|\uC804\uD658\uD574|\uC124\uC815|\uC124\uC815\uD574|\uC368\uC918|\uC0AC\uC6A9|\uAC00|switch|change|set|use)";
|
|
20515
|
-
const switchSubject = "(?:agent|runtime|model|\uC5D0\uC774\uC804\uD2B8|\uB7F0\uD0C0\uC784|\uBAA8\uB378)";
|
|
20516
|
-
return [
|
|
20517
|
-
new RegExp(`^/(?:agent|runtime)\\s+${target}(?:\\s|$)`, "iu"),
|
|
20518
|
-
new RegExp(`${target}\\s*(?:\uB85C|\uC73C\uB85C)\\s*.{0,16}${switchVerb}`, "iu"),
|
|
20519
|
-
new RegExp(`${switchSubject}.{0,24}${target}.{0,24}${switchVerb}`, "iu"),
|
|
20520
|
-
new RegExp(`${switchVerb}.{0,24}${switchSubject}.{0,24}${target}`, "iu"),
|
|
20521
|
-
new RegExp(`(?:switch|change|set|use).{0,24}${target}`, "iu")
|
|
20522
|
-
].some((pattern) => pattern.test(text2));
|
|
20523
|
-
}
|
|
20524
21485
|
function createSelfConfigCore(host, productOverrides = {}) {
|
|
20525
21486
|
const product = Object.freeze({
|
|
20526
21487
|
...DEFAULT_SELF_CONFIG_PRODUCT,
|
|
@@ -20608,7 +21569,8 @@ function createSelfConfigCore(host, productOverrides = {}) {
|
|
|
20608
21569
|
if (config.agentLocked) {
|
|
20609
21570
|
return err("Agent for this topic is locked by the user. Cannot override.");
|
|
20610
21571
|
}
|
|
20611
|
-
|
|
21572
|
+
const explicitlyRequested = ctx.explicitAgentSwitchTargets ? ctx.explicitAgentSwitchTargets.includes(agent) : hasExplicitAgentSwitchRequest(ctx.currentUserPrompt, agent);
|
|
21573
|
+
if (!explicitlyRequested) {
|
|
20612
21574
|
return err(`Agent switch to '${agent}' requires an explicit request in the current user message.`);
|
|
20613
21575
|
}
|
|
20614
21576
|
const switched = host.runtime.switchAgent({
|
|
@@ -20766,7 +21728,10 @@ function createSelfConfigCore(host, productOverrides = {}) {
|
|
|
20766
21728
|
return topic;
|
|
20767
21729
|
let derived;
|
|
20768
21730
|
try {
|
|
20769
|
-
derived = await host.derivedTopics.create(topic.id, ctx.userId, copyHistory, {
|
|
21731
|
+
derived = await host.derivedTopics.create(topic.id, ctx.userId, copyHistory, {
|
|
21732
|
+
name,
|
|
21733
|
+
...ctx.actorUserId && ctx.actorUserId !== ctx.userId ? { derivedByUserId: ctx.actorUserId } : {}
|
|
21734
|
+
});
|
|
20770
21735
|
} catch (error) {
|
|
20771
21736
|
if (host.derivedTopics.isTitleConflict(error)) {
|
|
20772
21737
|
return err(`${errorMessage(error)} \u2014 pick a different name.`);
|
|
@@ -21131,9 +22096,12 @@ export {
|
|
|
21131
22096
|
normalizeAskUserChoices,
|
|
21132
22097
|
killOwnedCodexTreesForShutdown,
|
|
21133
22098
|
killCodexTrees,
|
|
22099
|
+
isExplicitAgentSwitchTargets,
|
|
22100
|
+
hasExplicitAgentSwitchRequest,
|
|
21134
22101
|
formatToolUse,
|
|
21135
22102
|
forkAgentSession,
|
|
21136
22103
|
findNewCodexChildren,
|
|
22104
|
+
explicitAgentSwitchTargets,
|
|
21137
22105
|
defaultAskUserDurabilityHost,
|
|
21138
22106
|
createTopicLogMaintenance,
|
|
21139
22107
|
createSubagentLifecycle,
|
|
@@ -21161,4 +22129,4 @@ export {
|
|
|
21161
22129
|
DEFAULT_SELF_CONFIG_PRODUCT
|
|
21162
22130
|
};
|
|
21163
22131
|
|
|
21164
|
-
//# debugId=
|
|
22132
|
+
//# debugId=2C4B1E194210D46564756E2164756E21
|