negotium 0.19.2 → 0.20.1
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 +1147 -213
- package/dist/agent-helpers.js.map +28 -20
- package/dist/background-bash.js +2 -2
- package/dist/background-bash.js.map +3 -3
- package/dist/browser-runtime.js +2 -2
- package/dist/browser-runtime.js.map +3 -3
- package/dist/{chunk-aqqmzmft.js → chunk-143j4nxd.js} +1 -1
- package/dist/{chunk-55dd0xw7.js → chunk-myhmb8tn.js} +3 -3
- package/dist/{chunk-55dd0xw7.js.map → chunk-myhmb8tn.js.map} +3 -3
- package/dist/{chunk-cv7vdsrn.js → chunk-t7vs5k9t.js} +287 -10
- package/dist/{chunk-cv7vdsrn.js.map → chunk-t7vs5k9t.js.map} +11 -8
- package/dist/{chunk-exqz8bez.js → chunk-wpp1x0tx.js} +3 -3
- package/dist/{chunk-exqz8bez.js.map → chunk-wpp1x0tx.js.map} +2 -2
- package/dist/{chunk-kkftbxh2.js → chunk-zkyp090x.js} +1 -1
- package/dist/hosted-agent.js +290 -12
- package/dist/hosted-agent.js.map +10 -7
- package/dist/main.js +5019 -3134
- package/dist/main.js.map +51 -40
- package/dist/mcp-factories.js +1167 -163
- package/dist/mcp-factories.js.map +28 -20
- package/dist/media.js +2 -2
- package/dist/media.js.map +3 -3
- package/dist/prompts.js +2 -2
- package/dist/prompts.js.map +3 -3
- package/dist/query-runtime.js +2 -2
- package/dist/query-runtime.js.map +5 -5
- 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/public-helpers.ts +5 -0
- package/dist/runtime/src/agents/self-config-core.ts +28 -58
- 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/bashrs-completions.ts +19 -1
- 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/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 +13 -3
- package/dist/runtime-helpers.js.map +5 -5
- package/dist/storage.js +16 -4
- package/dist/storage.js.map +4 -4
- 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 +2 -2
- package/dist/vault.js.map +3 -3
- package/install-browser-rs.mjs +4 -4
- package/package.json +1 -1
- /package/dist/{chunk-aqqmzmft.js.map → chunk-143j4nxd.js.map} +0 -0
- /package/dist/{chunk-kkftbxh2.js.map → chunk-zkyp090x.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.
|
|
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.7.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.1";
|
|
3973
4330
|
|
|
3974
4331
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3975
4332
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -8797,7 +9154,7 @@ function isTopicSummaryFile(filename, topicId, topicTitle) {
|
|
|
8797
9154
|
return false;
|
|
8798
9155
|
if (topicTitle) {
|
|
8799
9156
|
const titleSlug = wikiSummarySlug(topicTitle);
|
|
8800
|
-
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)) {
|
|
8801
9158
|
return true;
|
|
8802
9159
|
}
|
|
8803
9160
|
}
|
|
@@ -8810,7 +9167,7 @@ function isTopicBriefFile(filename, topicId, topicTitle) {
|
|
|
8810
9167
|
const idSlug = wikiSummarySlug(topicId);
|
|
8811
9168
|
return filename === `${idSlug}.md` || filename.endsWith(`--${idSlug}.md`);
|
|
8812
9169
|
}
|
|
8813
|
-
function
|
|
9170
|
+
function escapeRegExp2(value) {
|
|
8814
9171
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8815
9172
|
}
|
|
8816
9173
|
var WIKI_SUMMARY_DATE_PREFIX;
|
|
@@ -13656,6 +14013,9 @@ function rowToRequest(row) {
|
|
|
13656
14013
|
runningQueryId: row.running_query_id ?? undefined
|
|
13657
14014
|
};
|
|
13658
14015
|
}
|
|
14016
|
+
function requestActor(actorUserId) {
|
|
14017
|
+
return actorUserId ?? null;
|
|
14018
|
+
}
|
|
13659
14019
|
function loggedMessageCount(request) {
|
|
13660
14020
|
const explicit = request.execution?.loggedUserMessageCount;
|
|
13661
14021
|
if (typeof explicit === "number" && Number.isInteger(explicit)) {
|
|
@@ -13672,7 +14032,13 @@ function mergeRuntimeUserTurnRequest(input) {
|
|
|
13672
14032
|
return db.transaction(() => {
|
|
13673
14033
|
const rows = db.query("SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? ORDER BY created_at ASC, rowid ASC").all(input.topicId);
|
|
13674
14034
|
const thread = input.execution.threadRootId;
|
|
13675
|
-
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);
|
|
13676
14042
|
const omittedRequestIds = new Set([
|
|
13677
14043
|
...input.omitRequestIds ?? [],
|
|
13678
14044
|
...previous.filter((request) => Boolean(request.execution?.providerSessionId)).map((request) => request.requestId)
|
|
@@ -13691,8 +14057,18 @@ function mergeRuntimeUserTurnRequest(input) {
|
|
|
13691
14057
|
];
|
|
13692
14058
|
const incomingLoggedCount = Math.min(Math.max(0, input.execution.loggedUserMessageCount ?? 0), input.userMessages.length);
|
|
13693
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
|
+
]);
|
|
13694
14068
|
const execution = {
|
|
13695
14069
|
...input.execution,
|
|
14070
|
+
actorTopicScope,
|
|
14071
|
+
remoteSession,
|
|
13696
14072
|
loggedUserMessageCount,
|
|
13697
14073
|
supersededRequestIds: [
|
|
13698
14074
|
...new Set(previous.flatMap((request) => [
|
|
@@ -13828,12 +14204,21 @@ function cancelRuntimeUserTurnRequests(topicId) {
|
|
|
13828
14204
|
db.query("DELETE FROM runtime_user_turn_requests WHERE topic_id = ?").run(topicId);
|
|
13829
14205
|
return rows.map((row) => row.request_id);
|
|
13830
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
|
+
}
|
|
13831
14214
|
function getRuntimeUserTurnRequest(topicId) {
|
|
13832
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);
|
|
13833
14216
|
return row ? rowToRequest(row) : null;
|
|
13834
14217
|
}
|
|
13835
14218
|
var REQUEST_CLAIM_STALE_MS;
|
|
13836
14219
|
var init_runtime_turn_requests = __esm(async () => {
|
|
14220
|
+
init_actor_topic_scope();
|
|
14221
|
+
init_remote_session_grant();
|
|
13837
14222
|
init_user_turn_envelope();
|
|
13838
14223
|
await __promiseAll([
|
|
13839
14224
|
init_forum_db(),
|
|
@@ -14746,6 +15131,7 @@ async function createDerivedTopicImpl(topic, sourceTopicId, userId, copyHistory,
|
|
|
14746
15131
|
parentTopicId: sourceTopicId,
|
|
14747
15132
|
...subagent?.memoryTopicId ? { memoryTopicId: subagent.memoryTopicId } : {},
|
|
14748
15133
|
isFork: copyHistory,
|
|
15134
|
+
...opts?.derivedByUserId ? { derivedByUserId: opts.derivedByUserId } : {},
|
|
14749
15135
|
...subagent ? { isSubagent: true } : {},
|
|
14750
15136
|
visibility: topic.visibility,
|
|
14751
15137
|
surface,
|
|
@@ -15053,6 +15439,50 @@ var init_state = __esm(() => {
|
|
|
15053
15439
|
});
|
|
15054
15440
|
});
|
|
15055
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
|
+
|
|
15056
15486
|
// ../../packages/core/src/query/session-inbox-path.ts
|
|
15057
15487
|
import { join as join25 } from "path";
|
|
15058
15488
|
function sessionInboxPath(userId, topicId) {
|
|
@@ -15143,96 +15573,210 @@ var init_session_inbox_cleanup = __esm(async () => {
|
|
|
15143
15573
|
await init_session_inbox();
|
|
15144
15574
|
});
|
|
15145
15575
|
|
|
15146
|
-
// ../../packages/core/src/mcp/session-comm/
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
deliverPeerReply: () => deliverPeerReply
|
|
15153
|
-
});
|
|
15154
|
-
function loopbackBridgeConfig() {
|
|
15155
|
-
const rawUrl = process.env[PEER_BRIDGE_URL_ENV]?.trim();
|
|
15156
|
-
const token = process.env[PEER_BRIDGE_TOKEN_ENV]?.trim();
|
|
15157
|
-
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))
|
|
15158
15582
|
return null;
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
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")
|
|
15162
15590
|
return null;
|
|
15163
|
-
|
|
15164
|
-
return { url: url.toString(), token };
|
|
15165
|
-
} catch {
|
|
15591
|
+
} else if (status !== "active" && status !== "ready") {
|
|
15166
15592
|
return null;
|
|
15167
15593
|
}
|
|
15594
|
+
if (description !== undefined && description !== null && typeof description !== "string") {
|
|
15595
|
+
return null;
|
|
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
|
+
};
|
|
15168
15606
|
}
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15180
|
-
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
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);
|
|
15188
15627
|
}
|
|
15628
|
+
return { node, sessions: parsed };
|
|
15189
15629
|
}
|
|
15190
|
-
function
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
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;
|
|
15196
15641
|
}
|
|
15197
|
-
|
|
15198
|
-
if (
|
|
15199
|
-
return
|
|
15200
|
-
const
|
|
15201
|
-
|
|
15202
|
-
|
|
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) {
|
|
15203
15681
|
return {
|
|
15204
|
-
|
|
15205
|
-
|
|
15682
|
+
authorization: `Bearer ${bearer}`,
|
|
15683
|
+
"content-type": "application/json",
|
|
15684
|
+
accept: "application/json"
|
|
15206
15685
|
};
|
|
15207
15686
|
}
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
}
|
|
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 };
|
|
15233
15762
|
}
|
|
15234
|
-
var
|
|
15235
|
-
|
|
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
|
+
});
|
|
15236
15780
|
// ../../packages/core/src/storage/session-asks.ts
|
|
15237
15781
|
var exports_session_asks = {};
|
|
15238
15782
|
__export(exports_session_asks, {
|
|
@@ -15598,6 +16142,326 @@ var init_session_asks = __esm(async () => {
|
|
|
15598
16142
|
PENDING_ASK_TTL_MS = 15 * 60 * 1000;
|
|
15599
16143
|
});
|
|
15600
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
|
+
|
|
15601
16465
|
// ../../packages/core/src/runtime/ask-callbacks.ts
|
|
15602
16466
|
var exports_ask_callbacks = {};
|
|
15603
16467
|
__export(exports_ask_callbacks, {
|
|
@@ -15605,21 +16469,25 @@ __export(exports_ask_callbacks, {
|
|
|
15605
16469
|
registerAskCallback: () => registerAskCallback,
|
|
15606
16470
|
purgeStaleAsks: () => purgeStaleAsks,
|
|
15607
16471
|
failInterruptedRemoteAskCallbacks: () => failInterruptedRemoteAskCallbacks,
|
|
16472
|
+
deleteHubRemoteAskCallbackRow: () => deleteHubRemoteAskCallbackRow,
|
|
15608
16473
|
cancelAskCallbacksForTopic: () => cancelAskCallbacksForTopic
|
|
15609
16474
|
});
|
|
15610
16475
|
function registerAskCallback(entry) {
|
|
15611
16476
|
pendingAsks.set(entry.targetQueryId, entry);
|
|
15612
16477
|
if (entry.remoteReply) {
|
|
16478
|
+
const route = entry.remoteReply;
|
|
16479
|
+
const hub = isHubRemoteReplyRoute(route);
|
|
15613
16480
|
db.query(`INSERT INTO remote_ask_callbacks
|
|
15614
|
-
(target_query_id, request_id, node_name, node_cell_id, topic_id, user_id, created_at)
|
|
15615
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
16481
|
+
(target_query_id, request_id, node_name, node_cell_id, topic_id, user_id, created_at, route_json)
|
|
16482
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
15616
16483
|
ON CONFLICT(target_query_id) DO UPDATE SET
|
|
15617
16484
|
request_id = excluded.request_id,
|
|
15618
16485
|
node_name = excluded.node_name,
|
|
15619
16486
|
node_cell_id = excluded.node_cell_id,
|
|
15620
16487
|
topic_id = excluded.topic_id,
|
|
15621
16488
|
user_id = excluded.user_id,
|
|
15622
|
-
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);
|
|
15623
16491
|
}
|
|
15624
16492
|
}
|
|
15625
16493
|
function resolveAskCallback(queryId) {
|
|
@@ -15630,9 +16498,14 @@ function resolveAskCallback(queryId) {
|
|
|
15630
16498
|
entry.timedOut = true;
|
|
15631
16499
|
}
|
|
15632
16500
|
pendingAsks.delete(queryId);
|
|
15633
|
-
|
|
16501
|
+
if (!entry.remoteReply || !isHubRemoteReplyRoute(entry.remoteReply)) {
|
|
16502
|
+
db.query("DELETE FROM remote_ask_callbacks WHERE target_query_id = ?").run(queryId);
|
|
16503
|
+
}
|
|
15634
16504
|
return entry;
|
|
15635
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
|
+
}
|
|
15636
16509
|
function purgeStaleAsks(now = Date.now()) {
|
|
15637
16510
|
for (const [id, entry] of pendingAsks) {
|
|
15638
16511
|
if (now - entry.createdAt > MAX_ASK_AGE_MS)
|
|
@@ -15651,19 +16524,36 @@ function cancelAskCallbacksForTopic(topicId) {
|
|
|
15651
16524
|
}
|
|
15652
16525
|
return cancelled;
|
|
15653
16526
|
}
|
|
15654
|
-
|
|
15655
|
-
|
|
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
|
|
15656
16545
|
FROM remote_ask_callbacks
|
|
15657
16546
|
ORDER BY created_at ASC`).all();
|
|
15658
16547
|
let failed = 0;
|
|
15659
16548
|
for (const row of rows) {
|
|
15660
|
-
const
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15664
|
-
|
|
15665
|
-
|
|
15666
|
-
|
|
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");
|
|
15667
16557
|
if (!delivered)
|
|
15668
16558
|
continue;
|
|
15669
16559
|
db.query("DELETE FROM remote_ask_callbacks WHERE target_query_id = ?").run(row.target_query_id);
|
|
@@ -15674,6 +16564,7 @@ async function failInterruptedRemoteAskCallbacks() {
|
|
|
15674
16564
|
var pendingAsks, MAX_ASK_AGE_MS;
|
|
15675
16565
|
var init_ask_callbacks = __esm(async () => {
|
|
15676
16566
|
await __promiseAll([
|
|
16567
|
+
init_peer_forward(),
|
|
15677
16568
|
init_forum_db(),
|
|
15678
16569
|
init_session_asks(),
|
|
15679
16570
|
init_storage_host()
|
|
@@ -15690,6 +16581,9 @@ var init_ask_callbacks = __esm(async () => {
|
|
|
15690
16581
|
created_at INTEGER NOT NULL
|
|
15691
16582
|
)
|
|
15692
16583
|
`);
|
|
16584
|
+
try {
|
|
16585
|
+
database.exec("ALTER TABLE remote_ask_callbacks ADD COLUMN route_json TEXT");
|
|
16586
|
+
} catch {}
|
|
15693
16587
|
});
|
|
15694
16588
|
pendingAsks = new Map;
|
|
15695
16589
|
MAX_ASK_AGE_MS = PENDING_ASK_TTL_MS;
|
|
@@ -17782,6 +18676,7 @@ __export(exports_turn_runner, {
|
|
|
17782
18676
|
startDurableTurnRequestWorker: () => startDurableTurnRequestWorker,
|
|
17783
18677
|
startAiTurn: () => startAiTurn,
|
|
17784
18678
|
selectableModel: () => selectableModel,
|
|
18679
|
+
sameTurnActor: () => sameTurnActor,
|
|
17785
18680
|
safeAttachmentFilename: () => safeAttachmentFilename,
|
|
17786
18681
|
resolveWorkerModel: () => resolveWorkerModel,
|
|
17787
18682
|
resolveWorkerDefaultModel: () => resolveWorkerDefaultModel,
|
|
@@ -18030,20 +18925,27 @@ async function markPendingAskFile(pending, state) {
|
|
|
18030
18925
|
logger.warn({ err: err2, pendingAsk: pending.pendingAsk, state }, "sessions: pending ask mark failed");
|
|
18031
18926
|
}
|
|
18032
18927
|
}
|
|
18033
|
-
async function deliverAskCallbackToCaller(pending, sourceLabel, body, kind) {
|
|
18928
|
+
async function deliverAskCallbackToCaller(pending, sourceLabel, body, kind, options = {}) {
|
|
18034
18929
|
if (pending.remoteReply) {
|
|
18035
|
-
const { deliverPeerReply: deliverPeerReply2 } = await
|
|
18036
|
-
|
|
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;
|
|
18037
18935
|
}
|
|
18038
18936
|
const callerTopic = getTopic(pending.callerTopicId);
|
|
18039
18937
|
const heading = kind === "error" ? `Error from ${sourceLabel}` : `Reply from ${sourceLabel}`;
|
|
18040
18938
|
const prompt = `[${heading}]
|
|
18041
18939
|
|
|
18042
18940
|
${body}`;
|
|
18941
|
+
const record = (agentType) => db.transaction(() => {
|
|
18942
|
+
appendAskReplyMessage(pending.callerTopicId, prompt, sourceLabel, body, kind, agentType ?? undefined, pending.callerThreadRootId);
|
|
18943
|
+
options.onRecorded?.();
|
|
18944
|
+
})();
|
|
18043
18945
|
await markPendingAskFile(pending, "reply_ready");
|
|
18044
18946
|
if (!callerTopic?.agent) {
|
|
18045
18947
|
try {
|
|
18046
|
-
|
|
18948
|
+
record(undefined);
|
|
18047
18949
|
await clearPendingAskFile(pending);
|
|
18048
18950
|
return true;
|
|
18049
18951
|
} catch (err2) {
|
|
@@ -18074,16 +18976,25 @@ ${body}`;
|
|
|
18074
18976
|
}
|
|
18075
18977
|
});
|
|
18076
18978
|
if (queued) {
|
|
18077
|
-
|
|
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
|
+
}
|
|
18078
18986
|
await markPendingAskFile(pending, "queued_for_caller");
|
|
18079
18987
|
return true;
|
|
18080
18988
|
}
|
|
18081
18989
|
if (interSessionQueue.hasRequest(pending.callerTopicId, pending.requestId)) {
|
|
18990
|
+
db.transaction(() => {
|
|
18991
|
+
options.onRecorded?.();
|
|
18992
|
+
})();
|
|
18082
18993
|
await markPendingAskFile(pending, "queued_for_caller");
|
|
18083
18994
|
return true;
|
|
18084
18995
|
}
|
|
18085
18996
|
logger.warn({ requestId: pending.requestId, callerTopicId: pending.callerTopicId, source: sourceLabel }, "sessions: ask callback could not enter caller batch; appending direct fallback");
|
|
18086
|
-
|
|
18997
|
+
record(callerTopic.agent);
|
|
18087
18998
|
await clearPendingAskFile(pending);
|
|
18088
18999
|
return true;
|
|
18089
19000
|
}
|
|
@@ -18153,6 +19064,15 @@ function resolveTurnHostMcpServers(topic, sessionType) {
|
|
|
18153
19064
|
return;
|
|
18154
19065
|
return getTopicHostMcpGrant(topic.id) ?? undefined;
|
|
18155
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
|
+
}
|
|
18156
19076
|
function mergeSupersedingUserTurn(running, incoming) {
|
|
18157
19077
|
const userMessages = [
|
|
18158
19078
|
...running.userMessages ?? [legacyUserTurnEnvelope(running.prompt, running.attachments)],
|
|
@@ -18173,6 +19093,8 @@ function serializableUserTurnExecution(params) {
|
|
|
18173
19093
|
runtimeEpoch: params._runtimeEpoch ?? getRuntimeTopicEpoch(params.topic.id),
|
|
18174
19094
|
sourceRequestId: params.requestId,
|
|
18175
19095
|
actorUserId: params.actorUserId,
|
|
19096
|
+
actorTopicScope: params.actorTopicScope,
|
|
19097
|
+
remoteSession: params.remoteSession,
|
|
18176
19098
|
agentOverride: params.agentOverride,
|
|
18177
19099
|
modelOverride: params.modelOverride,
|
|
18178
19100
|
effortOverride: params.effortOverride,
|
|
@@ -18258,6 +19180,8 @@ async function drainOneDurableUserTurn() {
|
|
|
18258
19180
|
topic,
|
|
18259
19181
|
userId: request.userId,
|
|
18260
19182
|
actorUserId: execution?.actorUserId,
|
|
19183
|
+
actorTopicScope: actorTopicScopeFrom(execution?.actorTopicScope),
|
|
19184
|
+
remoteSession: remoteSessionGrantFrom(execution?.remoteSession),
|
|
18261
19185
|
vaultUserId: execution?.vaultUserId,
|
|
18262
19186
|
prompt: request.prompt,
|
|
18263
19187
|
_userMessages: request.userMessages,
|
|
@@ -18471,7 +19395,9 @@ function startAiTurn(params) {
|
|
|
18471
19395
|
return null;
|
|
18472
19396
|
}
|
|
18473
19397
|
if (decision.action === "remote-abort-wait") {
|
|
18474
|
-
|
|
19398
|
+
if (remoteTurnHasSameActor(topicId, params.actorUserId, durableRequestIds)) {
|
|
19399
|
+
requestRuntimeTurnAbort(topicId, "internal");
|
|
19400
|
+
}
|
|
18475
19401
|
const queuedQueryId = waitToStartRemoteUserTurn({
|
|
18476
19402
|
...params,
|
|
18477
19403
|
topic,
|
|
@@ -18489,6 +19415,23 @@ function startAiTurn(params) {
|
|
|
18489
19415
|
}
|
|
18490
19416
|
if (decision.action === "abort-replace") {
|
|
18491
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
|
+
}
|
|
18492
19435
|
let omitRequestIds;
|
|
18493
19436
|
if (isUserOrigin(running.origin)) {
|
|
18494
19437
|
if (running.providerTurnContentObserved && running.sessionId) {
|
|
@@ -18548,6 +19491,7 @@ function startAiTurn(params) {
|
|
|
18548
19491
|
origin,
|
|
18549
19492
|
prompt,
|
|
18550
19493
|
userMessages,
|
|
19494
|
+
...isUserOrigin(origin) && params.actorUserId !== undefined ? { actorUserId: params.actorUserId } : {},
|
|
18551
19495
|
durableRequestIds,
|
|
18552
19496
|
attachments: attachments2,
|
|
18553
19497
|
sessionId,
|
|
@@ -18593,7 +19537,9 @@ function startAiTurn(params) {
|
|
|
18593
19537
|
return rejectIsolatedTurnBeforeDispatch("failed to claim isolated turn slot");
|
|
18594
19538
|
}
|
|
18595
19539
|
if (isUserOrigin(origin)) {
|
|
18596
|
-
|
|
19540
|
+
if (remoteTurnHasSameActor(topicId, params.actorUserId, durableRequestIds)) {
|
|
19541
|
+
requestRuntimeTurnAbort(topicId, "internal");
|
|
19542
|
+
}
|
|
18597
19543
|
const queuedQueryId = waitToStartRemoteUserTurn({
|
|
18598
19544
|
...params,
|
|
18599
19545
|
topic,
|
|
@@ -18853,6 +19799,8 @@ function startAiTurn(params) {
|
|
|
18853
19799
|
sessionId,
|
|
18854
19800
|
userId,
|
|
18855
19801
|
actorUserId: params.actorUserId,
|
|
19802
|
+
actorTopicScope: params.actorTopicScope,
|
|
19803
|
+
remoteSession: params.remoteSession,
|
|
18856
19804
|
vaultUserId,
|
|
18857
19805
|
session: sessionName,
|
|
18858
19806
|
sessionType: sessionType ?? (isManager ? "manager" : "forum"),
|
|
@@ -18917,12 +19865,10 @@ function startAiTurn(params) {
|
|
|
18917
19865
|
error: "Provider session expired and conversation history could not be rebuilt; the existing session was preserved for retry."
|
|
18918
19866
|
};
|
|
18919
19867
|
} else {
|
|
18920
|
-
if (!silent)
|
|
18921
|
-
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
18922
19868
|
const retrySessionId = retry.sessionId;
|
|
18923
19869
|
logger.info({
|
|
18924
19870
|
topicId,
|
|
18925
|
-
|
|
19871
|
+
queryId,
|
|
18926
19872
|
agent: agentKind,
|
|
18927
19873
|
retrySessionId: retrySessionId ? retrySessionId.slice(0, 8) : null
|
|
18928
19874
|
}, "ai: retrying query after session expiry");
|
|
@@ -18933,6 +19879,9 @@ function startAiTurn(params) {
|
|
|
18933
19879
|
startAiTurn({
|
|
18934
19880
|
topic,
|
|
18935
19881
|
userId,
|
|
19882
|
+
actorUserId: params.actorUserId,
|
|
19883
|
+
actorTopicScope: params.actorTopicScope,
|
|
19884
|
+
remoteSession: params.remoteSession,
|
|
18936
19885
|
vaultUserId,
|
|
18937
19886
|
prompt,
|
|
18938
19887
|
_userMessages: userMessages,
|
|
@@ -18967,7 +19916,8 @@ function startAiTurn(params) {
|
|
|
18967
19916
|
peerBridge,
|
|
18968
19917
|
askReplySources,
|
|
18969
19918
|
_runtimeEpoch: runtimeEpoch,
|
|
18970
|
-
_sessionRetried: true
|
|
19919
|
+
_sessionRetried: true,
|
|
19920
|
+
_queryId: queryId
|
|
18971
19921
|
});
|
|
18972
19922
|
return;
|
|
18973
19923
|
}
|
|
@@ -18976,12 +19926,13 @@ function startAiTurn(params) {
|
|
|
18976
19926
|
if (emptyResponseRetried) {
|
|
18977
19927
|
outcome = { kind: "provider-error", error: outcome.error };
|
|
18978
19928
|
} else {
|
|
18979
|
-
|
|
18980
|
-
WsHub.get().broadcastAborted(topicId, queryId, "stopped");
|
|
18981
|
-
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");
|
|
18982
19930
|
startAiTurn({
|
|
18983
19931
|
topic,
|
|
18984
19932
|
userId,
|
|
19933
|
+
actorUserId: params.actorUserId,
|
|
19934
|
+
actorTopicScope: params.actorTopicScope,
|
|
19935
|
+
remoteSession: params.remoteSession,
|
|
18985
19936
|
vaultUserId,
|
|
18986
19937
|
prompt,
|
|
18987
19938
|
_userMessages: userMessages,
|
|
@@ -19017,7 +19968,8 @@ function startAiTurn(params) {
|
|
|
19017
19968
|
askReplySources,
|
|
19018
19969
|
_runtimeEpoch: runtimeEpoch,
|
|
19019
19970
|
_sessionRetried: sessionRetried,
|
|
19020
|
-
_emptyResponseRetried: true
|
|
19971
|
+
_emptyResponseRetried: true,
|
|
19972
|
+
_queryId: queryId
|
|
19021
19973
|
});
|
|
19022
19974
|
return;
|
|
19023
19975
|
}
|
|
@@ -19189,8 +20141,10 @@ var init_turn_runner = __esm(async () => {
|
|
|
19189
20141
|
init_builders();
|
|
19190
20142
|
init_state();
|
|
19191
20143
|
init_types2();
|
|
20144
|
+
init_actor_topic_scope();
|
|
19192
20145
|
init_attachments();
|
|
19193
20146
|
init_errors();
|
|
20147
|
+
init_remote_session_grant();
|
|
19194
20148
|
init_user_turn_envelope();
|
|
19195
20149
|
init_wiki_summary_names();
|
|
19196
20150
|
init_user_turn_envelope();
|
|
@@ -19216,6 +20170,7 @@ var init_turn_runner = __esm(async () => {
|
|
|
19216
20170
|
init_app_settings(),
|
|
19217
20171
|
init_browser_profiles(),
|
|
19218
20172
|
init_conversations(),
|
|
20173
|
+
init_forum_db(),
|
|
19219
20174
|
init_runtime_leases(),
|
|
19220
20175
|
init_runtime_topic_state(),
|
|
19221
20176
|
init_runtime_turn_requests(),
|
|
@@ -19672,6 +20627,18 @@ ${card.task}${reportInstruction}`;
|
|
|
19672
20627
|
].join(`
|
|
19673
20628
|
`));
|
|
19674
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
|
+
}
|
|
19675
20642
|
function ownedSubagentTree(ctx) {
|
|
19676
20643
|
const parent = host.storage.getTopic(ctx.topicId);
|
|
19677
20644
|
if (!parent)
|
|
@@ -19685,37 +20652,46 @@ ${card.task}${reportInstruction}`;
|
|
|
19685
20652
|
error: "Error: subagent management is only available in agent rooms."
|
|
19686
20653
|
};
|
|
19687
20654
|
}
|
|
20655
|
+
const actorScope = assertedActorScope(ctx);
|
|
19688
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;
|
|
19689
20688
|
return {
|
|
19690
20689
|
ok: true,
|
|
19691
20690
|
parent,
|
|
19692
|
-
children
|
|
19693
|
-
|
|
19694
|
-
|
|
19695
|
-
|
|
19696
|
-
if (!topic.parentTopicId || !topic.isSubagent)
|
|
19697
|
-
continue;
|
|
19698
|
-
const siblings = byParent.get(topic.parentTopicId) ?? [];
|
|
19699
|
-
siblings.push(topic);
|
|
19700
|
-
byParent.set(topic.parentTopicId, siblings);
|
|
19701
|
-
}
|
|
19702
|
-
const descendants = [];
|
|
19703
|
-
const visited = new Set([ctx.topicId]);
|
|
19704
|
-
const visit = (parentId) => {
|
|
19705
|
-
for (const child of byParent.get(parentId) ?? []) {
|
|
19706
|
-
if (visited.has(child.id))
|
|
19707
|
-
continue;
|
|
19708
|
-
visited.add(child.id);
|
|
19709
|
-
if (!child.participants.some((participant) => participant.userId === rootOwnerId && participant.role === "owner")) {
|
|
19710
|
-
continue;
|
|
19711
|
-
}
|
|
19712
|
-
descendants.push(child);
|
|
19713
|
-
visit(child.id);
|
|
19714
|
-
}
|
|
19715
|
-
};
|
|
19716
|
-
visit(ctx.topicId);
|
|
19717
|
-
return descendants;
|
|
19718
|
-
})() : []
|
|
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]
|
|
19719
20695
|
};
|
|
19720
20696
|
}
|
|
19721
20697
|
function subagentStatus(parentTopicId, childTopicId) {
|
|
@@ -19779,7 +20755,7 @@ ${card.task}${reportInstruction}`;
|
|
|
19779
20755
|
status: subagentStatus(child.parentTopicId ?? ctx.topicId, child.id),
|
|
19780
20756
|
agent: child.agent ?? null,
|
|
19781
20757
|
model: child.defaultModel ?? null,
|
|
19782
|
-
tell_target_topic_ids: host.sessionCommunication.listTellTargetIds(child.id),
|
|
20758
|
+
tell_target_topic_ids: result.visibleTellTargets(host.sessionCommunication.listTellTargetIds(child.id)),
|
|
19783
20759
|
created_at: child.createdAt
|
|
19784
20760
|
}));
|
|
19785
20761
|
return textResult(JSON.stringify({ subagents: children }, null, 2));
|
|
@@ -19796,7 +20772,7 @@ ${card.task}${reportInstruction}`;
|
|
|
19796
20772
|
if (!result.ok)
|
|
19797
20773
|
return errorResult(result.error);
|
|
19798
20774
|
const topicId = String(input.topic_id ?? "").trim();
|
|
19799
|
-
if (!result.
|
|
20775
|
+
if (!result.owned(topicId)) {
|
|
19800
20776
|
return errorResult("Error: topic is not a descendant subagent managed by this room.");
|
|
19801
20777
|
}
|
|
19802
20778
|
const cardMessage = findSubagentCardMessage(topicId);
|
|
@@ -19816,7 +20792,7 @@ ${card.task}${reportInstruction}`;
|
|
|
19816
20792
|
if (!result.ok)
|
|
19817
20793
|
return errorResult(result.error);
|
|
19818
20794
|
const topicId = typeof input.topic_id === "string" ? input.topic_id.trim() : "";
|
|
19819
|
-
const child = result.children.find((candidate) => candidate.id === topicId);
|
|
20795
|
+
const child = result.owned(topicId) ? result.children.find((candidate) => candidate.id === topicId) : undefined;
|
|
19820
20796
|
if (!child) {
|
|
19821
20797
|
return errorResult("Error: no owned descendant subagent with that topic_id exists under this room.");
|
|
19822
20798
|
}
|
|
@@ -19842,11 +20818,10 @@ ${card.task}${reportInstruction}`;
|
|
|
19842
20818
|
return errorResult(result.error);
|
|
19843
20819
|
const sourceId = String(input.subagent_topic_id ?? "").trim();
|
|
19844
20820
|
const targetId = String(input.target_topic_id ?? "").trim();
|
|
19845
|
-
|
|
19846
|
-
if (!descendantIds.has(sourceId)) {
|
|
20821
|
+
if (!result.owned(sourceId)) {
|
|
19847
20822
|
return errorResult("Error: source is not a descendant subagent managed by this room.");
|
|
19848
20823
|
}
|
|
19849
|
-
if (
|
|
20824
|
+
if (!result.visibleTarget(targetId)) {
|
|
19850
20825
|
return errorResult("Error: target is outside this room's managed subagent tree.");
|
|
19851
20826
|
}
|
|
19852
20827
|
if (host.storage.getTopic(sourceId)?.parentTopicId === targetId) {
|
|
@@ -19869,11 +20844,10 @@ ${card.task}${reportInstruction}`;
|
|
|
19869
20844
|
return errorResult(result.error);
|
|
19870
20845
|
const sourceId = String(input.subagent_topic_id ?? "").trim();
|
|
19871
20846
|
const targetId = String(input.target_topic_id ?? "").trim();
|
|
19872
|
-
|
|
19873
|
-
if (!descendantIds.has(sourceId)) {
|
|
20847
|
+
if (!result.owned(sourceId)) {
|
|
19874
20848
|
return errorResult("Error: source is not a descendant subagent managed by this room.");
|
|
19875
20849
|
}
|
|
19876
|
-
if (
|
|
20850
|
+
if (!result.visibleTarget(targetId)) {
|
|
19877
20851
|
return errorResult("Error: target is outside this room's managed subagent tree.");
|
|
19878
20852
|
}
|
|
19879
20853
|
const removed = host.sessionCommunication.revokeTellTarget(sourceId, targetId);
|
|
@@ -19955,6 +20929,7 @@ var init_spawn_subagent = __esm(async () => {
|
|
|
19955
20929
|
init_types();
|
|
19956
20930
|
await __promiseAll([
|
|
19957
20931
|
init_bus(),
|
|
20932
|
+
init_actor_topic_reach(),
|
|
19958
20933
|
init_api_messages(),
|
|
19959
20934
|
init_api_topics(),
|
|
19960
20935
|
init_runtime_leases(),
|
|
@@ -20097,6 +21072,7 @@ var init_spawn_subagent = __esm(async () => {
|
|
|
20097
21072
|
|
|
20098
21073
|
// ../../packages/core/src/agents/public-helpers.ts
|
|
20099
21074
|
init_codex_tree_kill();
|
|
21075
|
+
init_explicit_agent_switch();
|
|
20100
21076
|
await __promiseAll([
|
|
20101
21077
|
init_archiver(),
|
|
20102
21078
|
init_auth_check(),
|
|
@@ -20464,6 +21440,7 @@ function switchApiTopicAgent(opts) {
|
|
|
20464
21440
|
}
|
|
20465
21441
|
|
|
20466
21442
|
// ../../packages/core/src/agents/self-config-core.ts
|
|
21443
|
+
init_explicit_agent_switch();
|
|
20467
21444
|
init_model_catalog();
|
|
20468
21445
|
init_config();
|
|
20469
21446
|
init_config_helpers();
|
|
@@ -20505,56 +21482,6 @@ function err(text2) {
|
|
|
20505
21482
|
function errorMessage(error) {
|
|
20506
21483
|
return error instanceof Error ? error.message : String(error);
|
|
20507
21484
|
}
|
|
20508
|
-
function escapeRegExp2(value) {
|
|
20509
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20510
|
-
}
|
|
20511
|
-
function agentAliases(agent) {
|
|
20512
|
-
switch (agent) {
|
|
20513
|
-
case "codex":
|
|
20514
|
-
return [
|
|
20515
|
-
"codex",
|
|
20516
|
-
"\uCF54\uB371\uC2A4",
|
|
20517
|
-
"gpt-6-luna",
|
|
20518
|
-
"gpt-6-sol",
|
|
20519
|
-
"gpt-5.6-luna",
|
|
20520
|
-
"gpt-5.6-terra",
|
|
20521
|
-
"gpt-5.6-sol",
|
|
20522
|
-
"gpt-6-astra"
|
|
20523
|
-
];
|
|
20524
|
-
case "claude":
|
|
20525
|
-
return ["claude", "\uD074\uB85C\uB4DC", "sonnet", "opus", "fable"];
|
|
20526
|
-
case "maestro":
|
|
20527
|
-
return [
|
|
20528
|
-
"maestro",
|
|
20529
|
-
"\uB9C8\uC5D0\uC2A4\uD2B8\uB85C",
|
|
20530
|
-
"\uBA54\uC2A4\uD2B8\uB85C",
|
|
20531
|
-
"deepseek",
|
|
20532
|
-
"deepseek-pro",
|
|
20533
|
-
"kimi",
|
|
20534
|
-
"kimi-pro",
|
|
20535
|
-
"kimi-k3",
|
|
20536
|
-
"kimi-code",
|
|
20537
|
-
"kimi-k2.7-code",
|
|
20538
|
-
"\uB525\uC2DC\uD06C",
|
|
20539
|
-
"\uD0A4\uBBF8"
|
|
20540
|
-
];
|
|
20541
|
-
}
|
|
20542
|
-
}
|
|
20543
|
-
function hasExplicitAgentSwitchRequest(prompt, agent) {
|
|
20544
|
-
if (!prompt?.trim())
|
|
20545
|
-
return false;
|
|
20546
|
-
const text2 = prompt.toLowerCase().replace(/\s+/g, " ").trim();
|
|
20547
|
-
const target = `(?:${agentAliases(agent).map(escapeRegExp2).join("|")})`;
|
|
20548
|
-
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)";
|
|
20549
|
-
const switchSubject = "(?:agent|runtime|model|\uC5D0\uC774\uC804\uD2B8|\uB7F0\uD0C0\uC784|\uBAA8\uB378)";
|
|
20550
|
-
return [
|
|
20551
|
-
new RegExp(`^/(?:agent|runtime)\\s+${target}(?:\\s|$)`, "iu"),
|
|
20552
|
-
new RegExp(`${target}\\s*(?:\uB85C|\uC73C\uB85C)\\s*.{0,16}${switchVerb}`, "iu"),
|
|
20553
|
-
new RegExp(`${switchSubject}.{0,24}${target}.{0,24}${switchVerb}`, "iu"),
|
|
20554
|
-
new RegExp(`${switchVerb}.{0,24}${switchSubject}.{0,24}${target}`, "iu"),
|
|
20555
|
-
new RegExp(`(?:switch|change|set|use).{0,24}${target}`, "iu")
|
|
20556
|
-
].some((pattern) => pattern.test(text2));
|
|
20557
|
-
}
|
|
20558
21485
|
function createSelfConfigCore(host, productOverrides = {}) {
|
|
20559
21486
|
const product = Object.freeze({
|
|
20560
21487
|
...DEFAULT_SELF_CONFIG_PRODUCT,
|
|
@@ -20642,7 +21569,8 @@ function createSelfConfigCore(host, productOverrides = {}) {
|
|
|
20642
21569
|
if (config.agentLocked) {
|
|
20643
21570
|
return err("Agent for this topic is locked by the user. Cannot override.");
|
|
20644
21571
|
}
|
|
20645
|
-
|
|
21572
|
+
const explicitlyRequested = ctx.explicitAgentSwitchTargets ? ctx.explicitAgentSwitchTargets.includes(agent) : hasExplicitAgentSwitchRequest(ctx.currentUserPrompt, agent);
|
|
21573
|
+
if (!explicitlyRequested) {
|
|
20646
21574
|
return err(`Agent switch to '${agent}' requires an explicit request in the current user message.`);
|
|
20647
21575
|
}
|
|
20648
21576
|
const switched = host.runtime.switchAgent({
|
|
@@ -20800,7 +21728,10 @@ function createSelfConfigCore(host, productOverrides = {}) {
|
|
|
20800
21728
|
return topic;
|
|
20801
21729
|
let derived;
|
|
20802
21730
|
try {
|
|
20803
|
-
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
|
+
});
|
|
20804
21735
|
} catch (error) {
|
|
20805
21736
|
if (host.derivedTopics.isTitleConflict(error)) {
|
|
20806
21737
|
return err(`${errorMessage(error)} \u2014 pick a different name.`);
|
|
@@ -21165,9 +22096,12 @@ export {
|
|
|
21165
22096
|
normalizeAskUserChoices,
|
|
21166
22097
|
killOwnedCodexTreesForShutdown,
|
|
21167
22098
|
killCodexTrees,
|
|
22099
|
+
isExplicitAgentSwitchTargets,
|
|
22100
|
+
hasExplicitAgentSwitchRequest,
|
|
21168
22101
|
formatToolUse,
|
|
21169
22102
|
forkAgentSession,
|
|
21170
22103
|
findNewCodexChildren,
|
|
22104
|
+
explicitAgentSwitchTargets,
|
|
21171
22105
|
defaultAskUserDurabilityHost,
|
|
21172
22106
|
createTopicLogMaintenance,
|
|
21173
22107
|
createSubagentLifecycle,
|
|
@@ -21195,4 +22129,4 @@ export {
|
|
|
21195
22129
|
DEFAULT_SELF_CONFIG_PRODUCT
|
|
21196
22130
|
};
|
|
21197
22131
|
|
|
21198
|
-
//# debugId=
|
|
22132
|
+
//# debugId=457F53A21F9DED1164756E2164756E21
|