@inhuman.tools/harness 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compact.prompt +12 -2
- package/dist/builtin-tools/catalog-entry.d.ts.map +1 -1
- package/dist/builtin-tools/catalog-entry.js +1 -0
- package/dist/builtin-tools/catalog-entry.js.map +1 -1
- package/dist/builtin-tools/index.d.ts +1 -2
- package/dist/builtin-tools/index.d.ts.map +1 -1
- package/dist/builtin-tools/index.js +1 -2
- package/dist/builtin-tools/index.js.map +1 -1
- package/dist/builtin-tools/metadata/context-need.d.ts +1 -0
- package/dist/builtin-tools/metadata/context-need.d.ts.map +1 -1
- package/dist/builtin-tools/metadata/context-need.js +13 -6
- package/dist/builtin-tools/metadata/context-need.js.map +1 -1
- package/dist/builtin-tools/metadata/context-recall.d.ts +1 -5
- package/dist/builtin-tools/metadata/context-recall.d.ts.map +1 -1
- package/dist/builtin-tools/metadata/context-recall.js +10 -7
- package/dist/builtin-tools/metadata/context-recall.js.map +1 -1
- package/dist/builtin-tools/metadata/update-metadata.d.ts +1 -0
- package/dist/builtin-tools/metadata/update-metadata.d.ts.map +1 -1
- package/dist/builtin-tools/metadata/update-metadata.js +12 -12
- package/dist/builtin-tools/metadata/update-metadata.js.map +1 -1
- package/dist/builtin-tools/named-agents/agent-catalog.d.ts.map +1 -1
- package/dist/builtin-tools/named-agents/agent-catalog.js +9 -9
- package/dist/builtin-tools/named-agents/agent-catalog.js.map +1 -1
- package/dist/builtin-tools/named-agents/agent-stdlib.d.ts.map +1 -1
- package/dist/builtin-tools/named-agents/agent-stdlib.js +2 -0
- package/dist/builtin-tools/named-agents/agent-stdlib.js.map +1 -1
- package/dist/builtin-tools/schemas/tool-result.d.ts +2 -0
- package/dist/builtin-tools/schemas/tool-result.d.ts.map +1 -1
- package/dist/builtin-tools/schemas/tool-result.js +2 -0
- package/dist/builtin-tools/schemas/tool-result.js.map +1 -1
- package/dist/builtin-tools/shell/job-stop.d.ts +1 -0
- package/dist/builtin-tools/shell/job-stop.d.ts.map +1 -1
- package/dist/builtin-tools/shell/shell-tool.d.ts +1 -0
- package/dist/builtin-tools/shell/shell-tool.d.ts.map +1 -1
- package/dist/builtin-tools/toolkit/dist-tools.d.ts +1 -0
- package/dist/builtin-tools/toolkit/dist-tools.d.ts.map +1 -1
- package/dist/builtin-tools/toolkit/dist-tools.js +1 -0
- package/dist/builtin-tools/toolkit/dist-tools.js.map +1 -1
- package/dist/connections/adopt.d.ts +2 -0
- package/dist/connections/adopt.d.ts.map +1 -1
- package/dist/connections/adopt.js.map +1 -1
- package/dist/connections/index.d.ts +1 -1
- package/dist/connections/index.d.ts.map +1 -1
- package/dist/extensions/behavior.d.ts.map +1 -1
- package/dist/extensions/behavior.js +17 -4
- package/dist/extensions/behavior.js.map +1 -1
- package/dist/extensions/consent.d.ts +0 -68
- package/dist/extensions/consent.d.ts.map +1 -1
- package/dist/extensions/consent.js +0 -69
- package/dist/extensions/consent.js.map +1 -1
- package/dist/extensions/contributions.d.ts.map +1 -1
- package/dist/extensions/contributions.js +1 -0
- package/dist/extensions/contributions.js.map +1 -1
- package/dist/extensions/dispatch.d.ts +1 -9
- package/dist/extensions/dispatch.d.ts.map +1 -1
- package/dist/extensions/dispatch.js +5 -7
- package/dist/extensions/dispatch.js.map +1 -1
- package/dist/extensions/eval.d.ts.map +1 -1
- package/dist/extensions/eval.js +2 -0
- package/dist/extensions/eval.js.map +1 -1
- package/dist/extensions/hook-deadline.d.ts +28 -0
- package/dist/extensions/hook-deadline.d.ts.map +1 -0
- package/dist/extensions/hook-deadline.js +72 -0
- package/dist/extensions/hook-deadline.js.map +1 -0
- package/dist/extensions/host.d.ts +2 -0
- package/dist/extensions/host.d.ts.map +1 -1
- package/dist/extensions/host.js +15 -0
- package/dist/extensions/host.js.map +1 -1
- package/dist/extensions/inventory.d.ts +9 -0
- package/dist/extensions/inventory.d.ts.map +1 -1
- package/dist/extensions/inventory.js +15 -1
- package/dist/extensions/inventory.js.map +1 -1
- package/dist/extensions/notes.d.ts +1 -0
- package/dist/extensions/notes.d.ts.map +1 -1
- package/dist/extensions/notes.js +1 -0
- package/dist/extensions/notes.js.map +1 -1
- package/dist/extensions/schemas/bot-definition.d.ts +1 -1
- package/dist/extensions/schemas/bot-definition.d.ts.map +1 -1
- package/dist/extensions/schemas/plugin-manifest.d.ts +4 -4
- package/dist/extensions/schemas/plugin-manifest.d.ts.map +1 -1
- package/dist/extensions/schemas/plugin-manifest.js +13 -1
- package/dist/extensions/schemas/plugin-manifest.js.map +1 -1
- package/dist/extensions/state.d.ts.map +1 -1
- package/dist/extensions/state.js +5 -0
- package/dist/extensions/state.js.map +1 -1
- package/dist/extensions/value.d.ts +4 -0
- package/dist/extensions/value.d.ts.map +1 -1
- package/dist/extensions/value.js.map +1 -1
- package/dist/protocol/schemas/health.d.ts +1 -3
- package/dist/protocol/schemas/health.d.ts.map +1 -1
- package/dist/protocol/schemas/health.js.map +1 -1
- package/dist/protocol/schemas/identity.d.ts +0 -4
- package/dist/protocol/schemas/identity.d.ts.map +1 -1
- package/dist/protocol/schemas/identity.js.map +1 -1
- package/dist/protocol/schemas/loss.d.ts +0 -2
- package/dist/protocol/schemas/loss.d.ts.map +1 -1
- package/dist/protocol/schemas/loss.js.map +1 -1
- package/dist/protocol/schemas/message.d.ts +0 -1
- package/dist/protocol/schemas/message.d.ts.map +1 -1
- package/dist/protocol/supervision/loss.d.ts +0 -1
- package/dist/protocol/supervision/loss.d.ts.map +1 -1
- package/dist/protocol/supervision/loss.js +0 -3
- package/dist/protocol/supervision/loss.js.map +1 -1
- package/dist/providers/auth/index.d.ts +2 -2
- package/dist/providers/auth/index.d.ts.map +1 -1
- package/dist/providers/auth/index.js +2 -2
- package/dist/providers/auth/index.js.map +1 -1
- package/dist/providers/auth/loopback.d.ts +0 -2
- package/dist/providers/auth/loopback.d.ts.map +1 -1
- package/dist/providers/auth/loopback.js.map +1 -1
- package/dist/providers/chatgpt/connection.d.ts +1 -0
- package/dist/providers/chatgpt/connection.d.ts.map +1 -1
- package/dist/providers/chatgpt/connection.js +1 -1
- package/dist/providers/chatgpt/connection.js.map +1 -1
- package/dist/providers/grok/connection.d.ts +2 -1
- package/dist/providers/grok/connection.d.ts.map +1 -1
- package/dist/providers/grok/connection.js +1 -1
- package/dist/providers/grok/connection.js.map +1 -1
- package/dist/providers/mcp/fetch.d.ts.map +1 -1
- package/dist/providers/mcp/fetch.js +2 -6
- package/dist/providers/mcp/fetch.js.map +1 -1
- package/dist/providers/nous/driver.d.ts +1 -0
- package/dist/providers/nous/driver.d.ts.map +1 -1
- package/dist/providers/openai-compat/actor.d.ts.map +1 -1
- package/dist/providers/openai-compat/actor.js +167 -39
- package/dist/providers/openai-compat/actor.js.map +1 -1
- package/dist/providers/openai-compat/adopt.d.ts +2 -0
- package/dist/providers/openai-compat/adopt.d.ts.map +1 -1
- package/dist/providers/openai-compat/adopt.js.map +1 -1
- package/dist/providers/openai-compat/connection.d.ts +9 -2
- package/dist/providers/openai-compat/connection.d.ts.map +1 -1
- package/dist/providers/openai-compat/connection.js +35 -27
- package/dist/providers/openai-compat/connection.js.map +1 -1
- package/dist/providers/openai-compat/fetch-types.d.ts.map +1 -1
- package/dist/providers/openai-compat/fetch-types.js +2 -6
- package/dist/providers/openai-compat/fetch-types.js.map +1 -1
- package/dist/providers/openai-compat/index.d.ts +2 -2
- package/dist/providers/openai-compat/index.d.ts.map +1 -1
- package/dist/providers/openai-compat/index.js +2 -2
- package/dist/providers/openai-compat/index.js.map +1 -1
- package/dist/providers/openai-compat/model-capabilities.d.ts +7 -0
- package/dist/providers/openai-compat/model-capabilities.d.ts.map +1 -0
- package/dist/providers/openai-compat/model-capabilities.js +24 -0
- package/dist/providers/openai-compat/model-capabilities.js.map +1 -0
- package/dist/providers/openai-compat/schema.d.ts +1 -1
- package/dist/providers/web/registrations/ids.d.ts +0 -2
- package/dist/providers/web/registrations/ids.d.ts.map +1 -1
- package/dist/providers/web/registrations/ids.js +0 -1
- package/dist/providers/web/registrations/ids.js.map +1 -1
- package/dist/runtime/bot-mcp.d.ts.map +1 -1
- package/dist/runtime/bot-mcp.js +1 -4
- package/dist/runtime/bot-mcp.js.map +1 -1
- package/dist/runtime/intent-recovery.d.ts +3 -20
- package/dist/runtime/intent-recovery.d.ts.map +1 -1
- package/dist/runtime/intent-recovery.js +1 -122
- package/dist/runtime/intent-recovery.js.map +1 -1
- package/dist/server/ask-answer.d.ts.map +1 -1
- package/dist/server/ask-answer.js +3 -3
- package/dist/server/ask-answer.js.map +1 -1
- package/dist/server/daemon/kernel/KernelDaemon.d.ts +4 -0
- package/dist/server/daemon/kernel/KernelDaemon.d.ts.map +1 -1
- package/dist/server/daemon/kernel/KernelDaemon.js +100 -40
- package/dist/server/daemon/kernel/KernelDaemon.js.map +1 -1
- package/dist/server/daemon/kernel/completion-catalog.d.ts +8 -0
- package/dist/server/daemon/kernel/completion-catalog.d.ts.map +1 -1
- package/dist/server/daemon/kernel/completion-catalog.js +16 -0
- package/dist/server/daemon/kernel/completion-catalog.js.map +1 -1
- package/dist/server/daemon/kernel/fs-reflection/reflectKernel.d.ts.map +1 -1
- package/dist/server/daemon/kernel/fs-reflection/reflectKernel.js +47 -1
- package/dist/server/daemon/kernel/fs-reflection/reflectKernel.js.map +1 -1
- package/dist/server/daemon/kernel/glance-ask-line.d.ts +1 -6
- package/dist/server/daemon/kernel/glance-ask-line.d.ts.map +1 -1
- package/dist/server/daemon/kernel/glance-ask-line.js +1 -35
- package/dist/server/daemon/kernel/glance-ask-line.js.map +1 -1
- package/dist/server/daemon/kernel/settings-files.d.ts +1 -1
- package/dist/server/daemon/kernel/settings-files.d.ts.map +1 -1
- package/dist/server/daemon/kernel/settings-files.js +3 -6
- package/dist/server/daemon/kernel/settings-files.js.map +1 -1
- package/dist/server/daemon/project/ProjectDaemon.d.ts +4 -0
- package/dist/server/daemon/project/ProjectDaemon.d.ts.map +1 -1
- package/dist/server/daemon/project/ProjectDaemon.js +11 -0
- package/dist/server/daemon/project/ProjectDaemon.js.map +1 -1
- package/dist/server/daemon/session/SessionDaemon.d.ts +5 -3
- package/dist/server/daemon/session/SessionDaemon.d.ts.map +1 -1
- package/dist/server/daemon/session/SessionDaemon.js +54 -37
- package/dist/server/daemon/session/SessionDaemon.js.map +1 -1
- package/dist/server/daemon/session/SessionOrchestrator.d.ts +1 -0
- package/dist/server/daemon/session/SessionOrchestrator.d.ts.map +1 -1
- package/dist/server/daemon/session/SessionOrchestrator.js +1371 -1146
- package/dist/server/daemon/session/SessionOrchestrator.js.map +1 -1
- package/dist/server/daemon/session/agent-runtime.d.ts +2 -0
- package/dist/server/daemon/session/agent-runtime.d.ts.map +1 -1
- package/dist/server/daemon/session/agent-runtime.js +55 -6
- package/dist/server/daemon/session/agent-runtime.js.map +1 -1
- package/dist/server/daemon/session/ambient-compact.d.ts +3 -21
- package/dist/server/daemon/session/ambient-compact.d.ts.map +1 -1
- package/dist/server/daemon/session/ambient-compact.js +7 -116
- package/dist/server/daemon/session/ambient-compact.js.map +1 -1
- package/dist/server/daemon/session/behavior-host.d.ts +9 -3
- package/dist/server/daemon/session/behavior-host.d.ts.map +1 -1
- package/dist/server/daemon/session/behavior-host.js +222 -47
- package/dist/server/daemon/session/behavior-host.js.map +1 -1
- package/dist/server/daemon/session/compact.d.ts +0 -1
- package/dist/server/daemon/session/compact.d.ts.map +1 -1
- package/dist/server/daemon/session/compact.js +7 -1
- package/dist/server/daemon/session/compact.js.map +1 -1
- package/dist/server/daemon/session/plugin-abi.d.ts.map +1 -1
- package/dist/server/daemon/session/plugin-abi.js +7 -11
- package/dist/server/daemon/session/plugin-abi.js.map +1 -1
- package/dist/server/daemon/session/prompt-message-of.d.ts +1 -8
- package/dist/server/daemon/session/prompt-message-of.d.ts.map +1 -1
- package/dist/server/daemon/session/prompt-message-of.js +2 -12
- package/dist/server/daemon/session/prompt-message-of.js.map +1 -1
- package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.d.ts +3 -42
- package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.d.ts.map +1 -1
- package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.js +4 -145
- package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.js.map +1 -1
- package/dist/server/daemon/session/round-boundary.d.ts +13 -0
- package/dist/server/daemon/session/round-boundary.d.ts.map +1 -0
- package/dist/server/daemon/session/round-boundary.js +196 -0
- package/dist/server/daemon/session/round-boundary.js.map +1 -0
- package/dist/server/daemon/session/round-stop.d.ts +1 -25
- package/dist/server/daemon/session/round-stop.d.ts.map +1 -1
- package/dist/server/daemon/session/round-stop.js +1 -75
- package/dist/server/daemon/session/round-stop.js.map +1 -1
- package/dist/server/daemon/session/turn-iterator.d.ts +1 -44
- package/dist/server/daemon/session/turn-iterator.d.ts.map +1 -1
- package/dist/server/daemon/session/turn-iterator.js +1 -435
- package/dist/server/daemon/session/turn-iterator.js.map +1 -1
- package/dist/server/daemon/session/turn-resolvers.d.ts +1 -16
- package/dist/server/daemon/session/turn-resolvers.d.ts.map +1 -1
- package/dist/server/daemon/session/turn-resolvers.js +1 -28
- package/dist/server/daemon/session/turn-resolvers.js.map +1 -1
- package/dist/server/daemon/session/visual-store.d.ts +2 -2
- package/dist/server/daemon/session/visual-store.d.ts.map +1 -1
- package/dist/server/daemon/session/visual-store.js +19 -20
- package/dist/server/daemon/session/visual-store.js.map +1 -1
- package/dist/server/drivers/request-timeout.d.ts +1 -0
- package/dist/server/drivers/request-timeout.d.ts.map +1 -1
- package/dist/server/drivers/request-timeout.js +2 -1
- package/dist/server/drivers/request-timeout.js.map +1 -1
- package/dist/server/runtime-bin.js +5 -8
- package/dist/server/runtime-bin.js.map +1 -1
- package/dist/server/schemas/index.d.ts +0 -1
- package/dist/server/schemas/index.d.ts.map +1 -1
- package/dist/server/schemas/index.js +0 -1
- package/dist/server/schemas/index.js.map +1 -1
- package/dist/server/schemas/json-record.d.ts +0 -6
- package/dist/server/schemas/json-record.d.ts.map +1 -1
- package/dist/server/schemas/json-record.js +0 -16
- package/dist/server/schemas/json-record.js.map +1 -1
- package/dist/server/schemas/well-known.d.ts +0 -2
- package/dist/server/schemas/well-known.d.ts.map +1 -1
- package/dist/server/schemas/well-known.js +0 -1
- package/dist/server/schemas/well-known.js.map +1 -1
- package/dist/server/testkit/scripted-model.d.ts +11 -0
- package/dist/server/testkit/scripted-model.d.ts.map +1 -1
- package/dist/server/testkit/scripted-model.js +42 -3
- package/dist/server/testkit/scripted-model.js.map +1 -1
- package/dist/server/toolkit/actor.d.ts +0 -1
- package/dist/server/toolkit/actor.d.ts.map +1 -1
- package/dist/server/toolkit/actor.js +2 -5
- package/dist/server/toolkit/actor.js.map +1 -1
- package/dist/server/toolkit/catalog.d.ts +0 -2
- package/dist/server/toolkit/catalog.d.ts.map +1 -1
- package/dist/server/toolkit/catalog.js +0 -81
- package/dist/server/toolkit/catalog.js.map +1 -1
- package/dist/server/toolkit/pack-catalog.d.ts +2 -1
- package/dist/server/toolkit/pack-catalog.d.ts.map +1 -1
- package/dist/server/toolkit/pack-catalog.js +1 -0
- package/dist/server/toolkit/pack-catalog.js.map +1 -1
- package/dist/server/toolkit/tool-entry.d.ts.map +1 -1
- package/dist/server/toolkit/tool-entry.js +1 -0
- package/dist/server/toolkit/tool-entry.js.map +1 -1
- package/dist/server/toolkit/tool-table.d.ts +0 -1
- package/dist/server/toolkit/tool-table.d.ts.map +1 -1
- package/dist/server/toolkit/tool-table.js +0 -3
- package/dist/server/toolkit/tool-table.js.map +1 -1
- package/dist/server/util/read-field.d.ts +0 -2
- package/dist/server/util/read-field.d.ts.map +1 -1
- package/dist/server/util/read-field.js +0 -9
- package/dist/server/util/read-field.js.map +1 -1
- package/dist/server/util/tool-call-json.d.ts +0 -1
- package/dist/server/util/tool-call-json.d.ts.map +1 -1
- package/dist/server/util/tool-call-json.js +0 -25
- package/dist/server/util/tool-call-json.js.map +1 -1
- package/dist/session/index.d.ts +4 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +10 -1
- package/dist/session/index.js.map +1 -1
- package/dist/tool-runtime/guard-tool.d.ts +3 -0
- package/dist/tool-runtime/guard-tool.d.ts.map +1 -1
- package/dist/tool-runtime/guard-tool.js +37 -3
- package/dist/tool-runtime/guard-tool.js.map +1 -1
- package/dist/yjs/document-service.d.ts +2 -4
- package/dist/yjs/document-service.d.ts.map +1 -1
- package/dist/yjs/document-service.js +0 -6
- package/dist/yjs/document-service.js.map +1 -1
- package/package.json +40 -40
|
@@ -1,36 +1,73 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
36
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
37
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
38
|
+
};
|
|
1
39
|
import { readFileSync } from "node:fs";
|
|
2
40
|
import { EvalTrace } from "@here.build/arrival/provenance";
|
|
3
41
|
import { docTag, fail, step, trace } from "@inhuman.tools/harness-diagnostics";
|
|
4
|
-
import { AGENT_ASK, AGENT_SEND, AgentCallbackJob, AgentCallJob, BotCallJob, CallJob, AgentMessageJob, AgentNotifyJob, ApprovableJob, cancelOutcomeText, CompletionExpectation, CompletionJob,
|
|
5
|
-
import {
|
|
42
|
+
import { AGENT_ASK, AGENT_SEND, AgentCallbackJob, AgentCallJob, BotCallJob, CallJob, AgentMessageJob, AgentNotifyJob, ApprovableJob, AskAgentSpec, cancelOutcomeText, CompletionExpectation, CompletionJob, delegatesPermissionMode, deskPermissionSnap, hostingTurnOf, McpToolExpectation, MCPToolJob, liveBackgroundJobs, permissionDeskSessionId, ProgrammaticJob, projectWorkerPermissions, ReACTChunk, SessionTurn, SystemToolJob, parseGrantRows, tapeCompletion, ToolCallJob, WebExtractToolJob, WebSearchToolJob, } from "@inhuman.tools/harness-model";
|
|
43
|
+
import { reActLoopIntentDetectedTriggerFor } from "@inhuman.tools/harness-model/shared";
|
|
6
44
|
import { connectionSupports, } from "@inhuman.tools/harness-provider-sdk";
|
|
7
45
|
import { interpretSchemeRepl, liveModelsOf } from "@inhuman.tools/harness-scheme";
|
|
8
46
|
import { walkWork, WorkExpectation } from "@inhuman.tools/plexus-expectations";
|
|
9
47
|
import { ConnectionMiss } from "@inhuman.tools/plexus-expectations/driver";
|
|
10
48
|
import { dropConnection, Orchestrator } from "@inhuman.tools/plexus-expectations/executor";
|
|
11
49
|
import { closeTurn, isCallPaused, joinReleased, schemeDeskReadonly, shouldSkipTurnCancel, } from "@inhuman.tools/turn/agent-calls";
|
|
12
|
-
import {
|
|
50
|
+
import { createTurnResolvers, SessionOwnershipError } from "@inhuman.tools/turn/runtime";
|
|
51
|
+
import { autorun, comparer, computed, reaction, runInAction, when } from "mobx";
|
|
13
52
|
import invariant from "tiny-invariant";
|
|
14
53
|
import { admitPrivateMcpTool } from "../../../extensions/index.js";
|
|
54
|
+
import { pluginHookTimeoutMs } from "../../../extensions/hook-deadline.js";
|
|
15
55
|
import { license, noteDenied } from "../../../policy-processor/license.js";
|
|
16
56
|
import { scopes } from "../../../policy-processor/scope.js";
|
|
17
57
|
import { documentKey } from "../../../protocol/document/index.js";
|
|
18
58
|
import { hydrateMcpAlwaysAllows, MCP_HTTP_DRIVER, mcpSessionPersistAllows, mcpSessionPersistDenies, } from "../../../providers/mcp/index.js";
|
|
19
59
|
import { borrowedEnsureKey, memoBorrowedEnsure, NOUS_GATEWAY_CONNECTION_ID, nousGatewaySourceOf, secretsConnectionId, } from "../../../providers/nous/index.js";
|
|
20
60
|
import { isPrivatePluginConnection, parsePrivateConnectionKey, persistJobIntent } from "../../../runtime/index.js";
|
|
21
|
-
import { abortParkableWork, dispatchedMcpSettled, failLeftoverApprovables, liveEvalWork, mcpJobOnWire, turnLoopStopped, } from "../../../session/index.js";
|
|
61
|
+
import { abortParkableWork, dispatchedMcpSettled, failLeftoverApprovables, liveEvalWork, isWorkReachable, mcpJobOnWire, turnLoopStopped, } from "../../../session/index.js";
|
|
22
62
|
import { behaviorEntriesOf } from "../../../tool-catalog/index.js";
|
|
23
|
-
import {
|
|
63
|
+
import { agentGuardedCall, distPolicySource, executeSystemTool, guardedCallOf, behaviorEntriesThatRun, guardHostTool, inboundTurnGuard, loadPolicySource, publishPolicyError, cancelUnstartedTool, stampLicenseDeny, stampUnentered, } from "../../../tool-runtime/index.js";
|
|
24
64
|
import { completeCallExceptionally, deliverAgentMessage, executeAgentCall, executeBotCall, loadOpenCall, notifyAgentCall, sealCall, settleAgentCallback, } from "./agent-runtime.js";
|
|
25
65
|
import { startAmbientCompact } from "./ambient-compact.js";
|
|
26
66
|
import { isParkAbort, SessionBehavior } from "./behavior-host.js";
|
|
27
67
|
import { loadCompactProgram } from "./compact.js";
|
|
28
68
|
import { CompletionFlights } from "./completion-flight.js";
|
|
29
|
-
import { pluginHookTimeoutMs } from "./hook-deadline.js";
|
|
30
69
|
import { PROJECT_LAYER_POLL_MS, readProjectAuthorization } from "./project-authorization.js";
|
|
31
|
-
import {
|
|
32
|
-
import { iterateTurn } from "./turn-iterator.js";
|
|
33
|
-
import { createTurnResolvers } from "./turn-resolvers.js";
|
|
70
|
+
import { internJudgeWire, assessTextOnlyContinueIntern } from "./re-act-loop-intent-detected-trigger.js";
|
|
34
71
|
import { resolveCompactStrategyPath } from "../../defaults/index.js";
|
|
35
72
|
import { internedComplete } from "../../drivers/interned-complete.js";
|
|
36
73
|
import { pickWebExtractConnection, pickWebSearchConnection } from "../../drivers/web-search/pick.js";
|
|
@@ -42,1238 +79,1426 @@ const OUTSIDE_WORKER_CEILING = "outside worker tool ceiling";
|
|
|
42
79
|
function licenseDecisionNow(job) {
|
|
43
80
|
return job.licenseDecision;
|
|
44
81
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
82
|
+
let SessionOrchestrator = (() => {
|
|
83
|
+
let _classSuper = Orchestrator;
|
|
84
|
+
let _instanceExtraInitializers = [];
|
|
85
|
+
let _private_get_callbackPending_decorators;
|
|
86
|
+
let _private_get_callbackPending_descriptor;
|
|
87
|
+
return class SessionOrchestrator extends _classSuper {
|
|
88
|
+
static {
|
|
89
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
90
|
+
__esDecorate(this, _private_get_callbackPending_descriptor = { get: __setFunctionName(function () {
|
|
91
|
+
for (const job of this.session.root.motionWork()) {
|
|
92
|
+
if (job instanceof AgentCallbackJob && job.startedAtMs != null && !job.completed && !job.cancelled)
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}, "#callbackPending", "get") }, _private_get_callbackPending_decorators, { kind: "getter", name: "#callbackPending", static: false, private: true, access: { has: obj => #callbackPending in obj, get: obj => obj.#callbackPending }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
97
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
98
|
+
}
|
|
99
|
+
sessionDaemon = __runInitializers(this, _instanceExtraInitializers);
|
|
100
|
+
#disposeConnections;
|
|
101
|
+
#disposeBoundWire;
|
|
102
|
+
#disposeLive;
|
|
103
|
+
#disposePromptQueue;
|
|
104
|
+
#disposeAutoAllow;
|
|
105
|
+
#disposeDesk;
|
|
106
|
+
#disposeCompletions;
|
|
107
|
+
#closeScope;
|
|
108
|
+
#closed = false;
|
|
109
|
+
/** Judge pairs that cannot send json_schema. Sticky skip, not retry. */
|
|
110
|
+
#reActLoopIntentDetectedTriggerHaltKeys = new Set();
|
|
111
|
+
/** Cutoff round → prefix key of the ambient completion still running. */
|
|
112
|
+
#ambientInflight = new Map();
|
|
113
|
+
#flights = new CompletionFlights((req) => this.#driveCompletion(req));
|
|
114
|
+
/**
|
|
115
|
+
* Rounds already open at {@link activate}. Recovery does not replay them
|
|
116
|
+
* (`unknown-no-replay`); honor adopts one only after `startedAtMs`.
|
|
117
|
+
*/
|
|
118
|
+
#completionBaseline = null;
|
|
119
|
+
#loggedError = new Map();
|
|
120
|
+
#loggedDriver = new Map();
|
|
121
|
+
#gatewayEnsure = new Map();
|
|
122
|
+
#behavior;
|
|
123
|
+
/** One-shot recipe calls whose tools an agent guard narrowed, until the worker is minted. */
|
|
124
|
+
#agentTools = new Map();
|
|
125
|
+
get #behaviorNotes() {
|
|
126
|
+
return this.sessionDaemon.kernel.behavior.notes;
|
|
127
|
+
}
|
|
128
|
+
constructor(sessionDaemon) {
|
|
129
|
+
super(sessionDaemon.kernel.plexus, sessionDaemon.plexus);
|
|
130
|
+
this.sessionDaemon = sessionDaemon;
|
|
131
|
+
this.#behavior = new SessionBehavior(sessionDaemon.kernel.behavior, this.session.root, sessionDaemon.project.path, sessionDaemon.kernel.catalog, {
|
|
132
|
+
syncTools: (turn) => sessionDaemon.syncBehaviorTools(turn),
|
|
133
|
+
hookTimeoutMs: () => pluginHookTimeoutMs(this.session.root.kvs, this.kernel.root.kvs),
|
|
134
|
+
canApplyResults: () => this.canApplyResults,
|
|
135
|
+
ownershipSignal: sessionDaemon.sessionRuntime.ownershipSignal,
|
|
136
|
+
});
|
|
137
|
+
const scope = scopes.get(this.session.root);
|
|
138
|
+
const ownershipSignal = sessionDaemon.sessionRuntime.ownershipSignal;
|
|
139
|
+
this.#closeScope = () => {
|
|
140
|
+
ownershipSignal.removeEventListener("abort", this.#closeScope);
|
|
141
|
+
scope.close();
|
|
142
|
+
};
|
|
143
|
+
ownershipSignal.addEventListener("abort", this.#closeScope, { once: true });
|
|
144
|
+
runInAction(() => {
|
|
145
|
+
scope.cap = this.kernel.root.speculativePolicyProcessingTools;
|
|
146
|
+
stampSessionPrograms(this.session.root, this.sessionDaemon.kernel.root, this.kernel.root);
|
|
147
|
+
});
|
|
148
|
+
scope.complete = (req) => this.complete(req);
|
|
149
|
+
scope.judgeFallbackKvs = this.kernel.root.kvs;
|
|
150
|
+
scope.judgeConfiguredModel = (connectionId) => this.#configuredModel(connectionId);
|
|
151
|
+
scope.onPolicyError = (text) => publishPolicyError(this.session, text);
|
|
152
|
+
scope.mcpPersistAllows = (job) => job instanceof MCPToolJob &&
|
|
153
|
+
mcpSessionPersistAllows(job.connectionKey, job.serverToolName, this.session.root.toolAllowlist, this.session.root.toolBlocklist);
|
|
154
|
+
scope.mcpPersistDenies = (job) => job instanceof MCPToolJob &&
|
|
155
|
+
mcpSessionPersistDenies(job.connectionKey, job.serverToolName, this.session.root.toolBlocklist);
|
|
156
|
+
runInAction(() => {
|
|
157
|
+
hydrateMcpAlwaysAllows(this.session.root.toolAllowlist, this.kernel.root.connections);
|
|
158
|
+
});
|
|
159
|
+
// The authorization table: the user layer from `config.json`, the project layer from
|
|
160
|
+
// `<repo>/.inhuman/authorization.json` (tighten only). Both are re-stamped when their files change, so a
|
|
161
|
+
// mid-session tightening applies to the next call.
|
|
162
|
+
const projectPath = this.sessionDaemon.project.path;
|
|
163
|
+
const syncAuthorization = () => {
|
|
164
|
+
if (!this.canApplyResults)
|
|
165
|
+
return;
|
|
166
|
+
const session = this.session.root;
|
|
167
|
+
const user = this.kernel.root.authorizationJson;
|
|
168
|
+
if (session.authorizationUserJson !== user)
|
|
169
|
+
session.authorizationUserJson = user;
|
|
170
|
+
const project = readProjectAuthorization(projectPath);
|
|
171
|
+
if (session.authorizationProjectJson !== project)
|
|
172
|
+
session.authorizationProjectJson = project;
|
|
173
|
+
};
|
|
119
174
|
runInAction(syncAuthorization);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
const poll = setInterval(() => {
|
|
176
|
+
if (this.activated && !this.#closed)
|
|
177
|
+
runInAction(syncAuthorization);
|
|
178
|
+
}, PROJECT_LAYER_POLL_MS);
|
|
179
|
+
poll.unref();
|
|
180
|
+
const stopAuthorization = reaction(() => (this.activated ? this.kernel.root.authorizationJson : null), () => {
|
|
181
|
+
if (!this.activated)
|
|
182
|
+
return;
|
|
183
|
+
runInAction(syncAuthorization);
|
|
184
|
+
});
|
|
185
|
+
// Stored grants of project, bot and global scope come from the kernel's `grants.json`.
|
|
186
|
+
scope.persistentGrants = () => parseGrantRows(this.kernel.root.grantsJson);
|
|
187
|
+
scope.botName = () => {
|
|
188
|
+
const spec = this.session.root.agent.spec;
|
|
189
|
+
return spec.name.trim() === "" ? undefined : spec.name;
|
|
190
|
+
};
|
|
191
|
+
const stopGrants = reaction(() => this.kernel.root.grantsJson, () => scope.reconcileGrants());
|
|
192
|
+
scope.onRunStopped = (stop) => {
|
|
193
|
+
publishPolicyError(this.session, `Run stopped: ${stop.reason}`);
|
|
194
|
+
};
|
|
195
|
+
scope.onRunNotice = (notice) => {
|
|
196
|
+
publishPolicyError(this.session, notice.text);
|
|
197
|
+
};
|
|
198
|
+
this.#disposeAutoAllow = () => {
|
|
199
|
+
clearInterval(poll);
|
|
200
|
+
stopAuthorization();
|
|
201
|
+
stopGrants();
|
|
202
|
+
};
|
|
203
|
+
this.#disposeDesk = reaction(() => (this.activated ? this.#deskPermissionSnap() : null), (snap) => {
|
|
204
|
+
if (snap == null || !this.canApplyResults)
|
|
205
|
+
return;
|
|
206
|
+
const desk = this.#permissionDesk();
|
|
207
|
+
if (desk == null)
|
|
208
|
+
return;
|
|
209
|
+
runInAction(() => projectWorkerPermissions(this.session.root, desk));
|
|
210
|
+
}, { equals: comparer.structural, fireImmediately: true });
|
|
211
|
+
this.#disposePromptQueue = reaction(() => {
|
|
212
|
+
if (!this.activated || this.#closed || !this.canApplyResults)
|
|
213
|
+
return "";
|
|
214
|
+
const root = this.session.root;
|
|
215
|
+
if (root.findRunningTurn() != null)
|
|
216
|
+
return "";
|
|
217
|
+
const first = root.promptQueue[0];
|
|
218
|
+
if (first == null)
|
|
219
|
+
return "";
|
|
220
|
+
return `${first.uuid}:${(root.kvs.connection ?? "").trim()}`;
|
|
221
|
+
}, (key) => {
|
|
222
|
+
if (key === "" || !this.canApplyResults)
|
|
223
|
+
return;
|
|
224
|
+
void this.sessionDaemon.drainPromptQueue();
|
|
225
|
+
});
|
|
226
|
+
this.registerExecutor(SystemToolJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
227
|
+
signal = this.#ownedSignal(signal);
|
|
228
|
+
if (await this.#bodyRefused(job, signal))
|
|
229
|
+
return;
|
|
230
|
+
try {
|
|
231
|
+
await executeSystemTool(job, signal, {
|
|
232
|
+
canApplyResults: () => this.canApplyResults,
|
|
233
|
+
shellTailBytes: this.kernel.root.shellTailBytes,
|
|
234
|
+
stopJob: async (jobId) => {
|
|
235
|
+
const nodes = this.session.root.motionWork();
|
|
236
|
+
const id = jobId.trim();
|
|
237
|
+
if (id === "")
|
|
238
|
+
return { ok: true, value: { jobs: await liveBackgroundJobs(nodes) } };
|
|
239
|
+
for (const node of nodes) {
|
|
240
|
+
if (!(node instanceof ProgrammaticJob || node instanceof CallJob || node instanceof SystemToolJob))
|
|
241
|
+
continue;
|
|
242
|
+
if (!node.isTrackedBackground || node.toolCallId !== id)
|
|
243
|
+
continue;
|
|
244
|
+
if (node.completed)
|
|
245
|
+
return { ok: true, value: { job_id: id, status: "completed" } };
|
|
246
|
+
if (node.cancelled)
|
|
247
|
+
return { ok: true, value: { job_id: id, status: "cancelled" } };
|
|
248
|
+
this.#abortWork(node);
|
|
181
249
|
return { ok: true, value: { job_id: id, status: "cancelled" } };
|
|
182
|
-
|
|
183
|
-
return { ok: true, value: { job_id: id, status: "
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
job.markComplete();
|
|
193
|
-
}
|
|
194
|
-
}, { seal: false });
|
|
195
|
-
this.registerExecutor(MCPToolJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
196
|
-
if (await this.#bodyRefused(job, signal))
|
|
197
|
-
return;
|
|
198
|
-
try {
|
|
199
|
-
await this.#runMcp(job, signal);
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
await this.#behavior.finishTool(job, signal);
|
|
203
|
-
}
|
|
204
|
-
}, { seal: false });
|
|
205
|
-
this.registerExecutor(WebSearchToolJob, (job) => this.#guardTool(job), (job, signal) => this.#runWeb(job, signal), { seal: false });
|
|
206
|
-
this.registerExecutor(WebExtractToolJob, (job) => this.#guardTool(job), (job, signal) => this.#runWeb(job, signal), { seal: false });
|
|
207
|
-
this.registerExecutor(BotCallJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
208
|
-
try {
|
|
209
|
-
await executeBotCall(job, this.#agentHost());
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
sealCall(job, "error", error instanceof Error ? error.message : String(error));
|
|
213
|
-
}
|
|
214
|
-
finally {
|
|
215
|
-
await waitUntil(() => job.completed, signal);
|
|
216
|
-
await this.#finishCall(job, signal);
|
|
217
|
-
}
|
|
218
|
-
}, { seal: false });
|
|
219
|
-
this.registerExecutor(AgentCallJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
220
|
-
try {
|
|
221
|
-
await executeAgentCall(job, this.#agentHost(), this.#agentTools.get(job));
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
sealCall(job, "error", error instanceof Error ? error.message : String(error));
|
|
225
|
-
}
|
|
226
|
-
finally {
|
|
227
|
-
await waitUntil(() => job.completed, signal);
|
|
228
|
-
await this.#finishCall(job, signal);
|
|
229
|
-
}
|
|
230
|
-
}, { seal: false });
|
|
231
|
-
this.registerExecutor(AgentCallbackJob, () => true, async (job, signal) => {
|
|
232
|
-
if (await this.#bodyRefused(job, signal))
|
|
233
|
-
return;
|
|
234
|
-
await settleAgentCallback(job, this.#agentHost(), (node) => this.#abortWork(node));
|
|
235
|
-
});
|
|
236
|
-
this.registerExecutor(AgentNotifyJob, () => true, async (job, signal) => {
|
|
237
|
-
if (await this.#bodyRefused(job, signal))
|
|
238
|
-
return;
|
|
239
|
-
notifyAgentCall(job, this.#agentHost());
|
|
240
|
-
});
|
|
241
|
-
this.registerExecutor(AgentMessageJob, (job) => this.#license(job), async (job, signal) => {
|
|
242
|
-
if (await this.#bodyRefused(job, signal))
|
|
243
|
-
return;
|
|
244
|
-
await deliverAgentMessage(job, this.#agentHost(), signal);
|
|
245
|
-
});
|
|
246
|
-
this.registerExecutor(ContextNeedToolJob, (job) => this.#license(job), async (job) => {
|
|
247
|
-
job.outcomeSnap ??= { ok: true };
|
|
248
|
-
});
|
|
249
|
-
this.registerExecutor(ProgrammaticJob,
|
|
250
|
-
// Bag is not remainder. Inner System/MCP/agent leaves still `#license`.
|
|
251
|
-
() => true, async (job, signal) => {
|
|
252
|
-
if (!(await this.#waitEvalAdmission(signal)))
|
|
253
|
-
return;
|
|
254
|
-
const catalog = sessionCatalog(this.session.root) ?? new KernelToolCatalog();
|
|
255
|
-
await interpretSchemeRepl(job, {
|
|
256
|
-
catalog,
|
|
257
|
-
root: this.session.root.path.trim() === "" ? process.cwd() : this.session.root.path,
|
|
258
|
-
signal,
|
|
259
|
-
sessionId: this.session.root.sessionId,
|
|
260
|
-
observe: (run) => {
|
|
261
|
-
const evalTrace = new EvalTrace();
|
|
262
|
-
evalTrace.withAutoBindings();
|
|
263
|
-
return run(evalTrace);
|
|
264
|
-
},
|
|
265
|
-
readonly: schemeDeskReadonly(this.session.root, enclosingCallOf(this.sessionDaemon.kernel, this.session.root, job)),
|
|
266
|
-
session: this.session.root,
|
|
267
|
-
doors: this.sessionDaemon.doors,
|
|
268
|
-
evalProgram: (hostingTurnOf(this.session.root, job) ?? this.session.root.head)?.evalSpec != null,
|
|
269
|
-
connectionId: this.session.root.kvs.connection?.trim() || undefined,
|
|
270
|
-
completionModel: this.session.root.kvs.model?.trim() || undefined,
|
|
271
|
-
complete: async (req) => {
|
|
272
|
-
try {
|
|
273
|
-
return await this.complete(req);
|
|
250
|
+
}
|
|
251
|
+
return { ok: true, value: { job_id: id, status: "absent" } };
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
if (this.canApplyResults) {
|
|
257
|
+
await this.#behavior.finishTool(job, signal);
|
|
258
|
+
if (this.canApplyResults && !job.completed && !job.cancelled)
|
|
259
|
+
job.markComplete();
|
|
274
260
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
261
|
+
}
|
|
262
|
+
}, { seal: false });
|
|
263
|
+
this.registerExecutor(MCPToolJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
264
|
+
if (await this.#bodyRefused(job, signal))
|
|
265
|
+
return;
|
|
266
|
+
try {
|
|
267
|
+
await this.#runMcp(job, signal);
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
if (this.canApplyResults)
|
|
271
|
+
await this.#behavior.finishTool(job, signal);
|
|
272
|
+
}
|
|
273
|
+
}, { seal: false });
|
|
274
|
+
this.registerExecutor(WebSearchToolJob, (job) => this.#guardTool(job), (job, signal) => this.#runWeb(job, signal), { seal: false });
|
|
275
|
+
this.registerExecutor(WebExtractToolJob, (job) => this.#guardTool(job), (job, signal) => this.#runWeb(job, signal), { seal: false });
|
|
276
|
+
this.registerExecutor(BotCallJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
277
|
+
try {
|
|
278
|
+
await executeBotCall(job, this.#agentHost());
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (this.canApplyResults)
|
|
282
|
+
sealCall(job, "error", error instanceof Error ? error.message : String(error));
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
if (this.canApplyResults) {
|
|
286
|
+
await waitUntil(() => job.completed, signal);
|
|
287
|
+
if (this.canApplyResults)
|
|
288
|
+
await this.#finishCall(job, signal);
|
|
279
289
|
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return;
|
|
290
|
+
}
|
|
291
|
+
}, { seal: false });
|
|
292
|
+
this.registerExecutor(AgentCallJob, (job) => this.#guardTool(job), async (job, signal) => {
|
|
293
|
+
try {
|
|
294
|
+
await executeAgentCall(job, this.#agentHost(), this.#agentTools.get(job));
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
if (this.canApplyResults)
|
|
298
|
+
sealCall(job, "error", error instanceof Error ? error.message : String(error));
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
if (this.canApplyResults) {
|
|
302
|
+
await waitUntil(() => job.completed, signal);
|
|
303
|
+
if (this.canApplyResults)
|
|
304
|
+
await this.#finishCall(job, signal);
|
|
296
305
|
}
|
|
297
|
-
|
|
298
|
-
|
|
306
|
+
}
|
|
307
|
+
}, { seal: false });
|
|
308
|
+
this.registerExecutor(AgentCallbackJob, () => true, async (job, signal) => {
|
|
309
|
+
if (await this.#bodyRefused(job, signal))
|
|
310
|
+
return;
|
|
311
|
+
await settleAgentCallback(job, this.#agentHost(), (node) => this.#abortWork(node));
|
|
299
312
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
await
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
loopLimits: () => {
|
|
330
|
-
const root = this.kernel.root;
|
|
331
|
-
// A thread reached through bot/ask has its own round cap.
|
|
332
|
-
const bot = session.agent.spec.botDepth > 0;
|
|
333
|
-
return {
|
|
334
|
-
maxRounds: bot ? root.maxBotRounds : root.maxRounds,
|
|
335
|
-
lengthContinuations: root.lengthContinuations,
|
|
336
|
-
};
|
|
313
|
+
this.registerExecutor(AgentNotifyJob, () => true, async (job, signal) => {
|
|
314
|
+
if (await this.#bodyRefused(job, signal))
|
|
315
|
+
return;
|
|
316
|
+
notifyAgentCall(job, this.#agentHost());
|
|
317
|
+
});
|
|
318
|
+
this.registerExecutor(AgentMessageJob, (job) => this.#license(job), async (job, signal) => {
|
|
319
|
+
if (await this.#bodyRefused(job, signal))
|
|
320
|
+
return;
|
|
321
|
+
await deliverAgentMessage(job, this.#agentHost(), signal);
|
|
322
|
+
});
|
|
323
|
+
this.registerExecutor(ProgrammaticJob,
|
|
324
|
+
// Bag is not remainder. Inner System/MCP/agent leaves still `#license`.
|
|
325
|
+
() => true, async (job, signal) => {
|
|
326
|
+
signal = this.#ownedSignal(signal);
|
|
327
|
+
if (!(await this.#waitEvalAdmission(signal)))
|
|
328
|
+
return;
|
|
329
|
+
if (!this.canApplyResults)
|
|
330
|
+
return;
|
|
331
|
+
const catalog = sessionCatalog(this.session.root) ?? new KernelToolCatalog();
|
|
332
|
+
await interpretSchemeRepl(job, {
|
|
333
|
+
canApplyResults: () => this.canApplyResults,
|
|
334
|
+
catalog,
|
|
335
|
+
root: this.session.root.path.trim() === "" ? process.cwd() : this.session.root.path,
|
|
336
|
+
signal,
|
|
337
|
+
sessionId: this.session.root.sessionId,
|
|
338
|
+
observe: (run) => {
|
|
339
|
+
const evalTrace = new EvalTrace();
|
|
340
|
+
evalTrace.withAutoBindings();
|
|
341
|
+
return run(evalTrace);
|
|
337
342
|
},
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
343
|
+
readonly: schemeDeskReadonly(this.session.root, enclosingCallOf(this.sessionDaemon.kernel, this.session.root, job)),
|
|
344
|
+
session: this.session.root,
|
|
345
|
+
doors: this.sessionDaemon.doors,
|
|
346
|
+
evalProgram: (hostingTurnOf(this.session.root, job) ?? this.session.root.head)?.evalSpec != null,
|
|
347
|
+
connectionId: this.session.root.kvs.connection?.trim() || undefined,
|
|
348
|
+
completionModel: this.session.root.kvs.model?.trim() || undefined,
|
|
349
|
+
complete: async (req) => {
|
|
350
|
+
try {
|
|
351
|
+
return await this.complete(req);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (error instanceof ConnectionMiss)
|
|
355
|
+
throw new TypeError(error.message);
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
350
358
|
},
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return (
|
|
359
|
+
connectionModels: (name) => {
|
|
360
|
+
const spec = this.sessionDaemon.kernel.plexus.root.connections[name];
|
|
361
|
+
invariant(spec != null, `unknown connection ${name}`);
|
|
362
|
+
return liveModelsOf(spec);
|
|
355
363
|
},
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
inflight: this.#ambientInflight,
|
|
364
|
+
admit: (call) => {
|
|
365
|
+
if (!this.canApplyResults)
|
|
366
|
+
throw new SessionOwnershipError();
|
|
367
|
+
return this.sessionDaemon.project.toolCalls.admit(call, catalog, {
|
|
368
|
+
session: this.session.root,
|
|
369
|
+
loadAgentCall: (uuid) => loadOpenCall(this.sessionDaemon.kernel, this.session.root, uuid),
|
|
363
370
|
});
|
|
364
371
|
},
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
372
|
+
abort: (leaf, reason) => this.#abortLeaf(leaf, reason),
|
|
373
|
+
notify: (note) => {
|
|
374
|
+
if (!this.canApplyResults)
|
|
375
|
+
return;
|
|
376
|
+
const call = enclosingCallOf(this.sessionDaemon.kernel, this.session.root, job);
|
|
377
|
+
if (call != null) {
|
|
378
|
+
call.notifications.push(note);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
job.notifications.push(note);
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
}, { seal: false });
|
|
385
|
+
this.registerExecutor(SessionTurn, (turn) => inboundTurnGuard(turn), async (turn, signal) => {
|
|
386
|
+
if (!this.canApplyResults)
|
|
387
|
+
return;
|
|
388
|
+
signal = AbortSignal.any([signal, this.sessionDaemon.sessionRuntime.signal]);
|
|
389
|
+
// Any other abort runs the close below; the iterator stops at once.
|
|
390
|
+
if (!(await this.#waitEvalAdmission(signal)) && isParkAbort(signal))
|
|
391
|
+
return;
|
|
392
|
+
if (!this.canApplyResults)
|
|
393
|
+
return;
|
|
394
|
+
if (turn.reducerExited)
|
|
395
|
+
return;
|
|
396
|
+
// Rewound away before it ran. Its close touches only itself: the steers and holds belong to the live head.
|
|
397
|
+
if (!this.reachableJobs.includes(turn)) {
|
|
398
|
+
closeTurn(turn, { kind: "rewound" }, this.#closeOptions());
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const session = this.session.root;
|
|
402
|
+
const toolCalls = this.sessionDaemon.project.toolCalls;
|
|
403
|
+
let broke = "";
|
|
404
|
+
try {
|
|
405
|
+
const connectionId = session.kvs.connection?.trim() ?? "";
|
|
406
|
+
const model = session.kvs.model?.trim() ?? "";
|
|
407
|
+
trace("harness:turn", `open ${turn.uuid} connection=${connectionId || "(none)"} model=${model || "(none)"}`);
|
|
408
|
+
await this.sessionDaemon.sessionRuntime.run(turn, {
|
|
409
|
+
session,
|
|
410
|
+
resolvers: createTurnResolvers({
|
|
411
|
+
complete: (req) => this.complete(req),
|
|
412
|
+
join: (job, abort) => waitCompleted(job, abort, this.sessionDaemon.kernel.catalog),
|
|
413
|
+
compactHost: () => this.#compactHost(turn, session, connectionId, model),
|
|
414
|
+
}),
|
|
415
|
+
notes: this.#behaviorNotes,
|
|
416
|
+
diagnostics: { step, trace, fail },
|
|
417
|
+
compactKeepTurns: this.kernel.root.compactKeepTurns,
|
|
418
|
+
hardCompactTrigger: this.kernel.root.compactTriggers.hard,
|
|
419
|
+
loopLimits: () => {
|
|
420
|
+
const root = this.kernel.root;
|
|
421
|
+
// A thread reached through bot/ask has its own round cap.
|
|
422
|
+
const bot = session.agent.spec.botDepth > 0;
|
|
423
|
+
return {
|
|
424
|
+
maxRounds: bot ? root.maxBotRounds : root.maxRounds,
|
|
425
|
+
lengthContinuations: root.lengthContinuations,
|
|
426
|
+
};
|
|
427
|
+
},
|
|
428
|
+
remoteMcp: (key) => this.kernel.root.connections[key]?.driver?.name === MCP_HTTP_DRIVER,
|
|
429
|
+
admit: (call) => {
|
|
430
|
+
if (!this.canApplyResults)
|
|
431
|
+
throw new SessionOwnershipError();
|
|
432
|
+
const catalog = sessionCatalog(session) ?? toolCalls.kernel.catalog;
|
|
433
|
+
const ctx = {
|
|
434
|
+
session,
|
|
435
|
+
originTurn: turn,
|
|
436
|
+
parent: turn,
|
|
437
|
+
loadAgentCall: (uuid) => loadOpenCall(this.sessionDaemon.kernel, session, uuid),
|
|
438
|
+
};
|
|
439
|
+
return toolCalls.admit(call, catalog, ctx);
|
|
440
|
+
},
|
|
441
|
+
waitInbound: (abort) => this.#inboundResumed(turn, abort, () => turnLoopStopped(turn, abort)),
|
|
442
|
+
openRound: async (resumed) => {
|
|
443
|
+
const behavior = await this.#behavior.enterTurn(turn, resumed, signal);
|
|
444
|
+
return (behavior != null && !turnLoopStopped(turn, signal) && (await this.#behavior.openRound(turn, behavior)));
|
|
445
|
+
},
|
|
446
|
+
closeRound: (settle) => this.#behavior.closeRound(turn, settle),
|
|
447
|
+
classifyIntent: (pew, abort) => this.#reActLoopIntentDetectedTrigger(turn, pew, abort),
|
|
448
|
+
startAmbientCompact: (options) => {
|
|
449
|
+
void startAmbientCompact({
|
|
450
|
+
...options,
|
|
451
|
+
complete: (req) => this.complete(req),
|
|
452
|
+
inflight: this.#ambientInflight,
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
}, signal);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
if (!this.canApplyResults)
|
|
459
|
+
return;
|
|
460
|
+
broke = error instanceof Error ? error.message : String(error);
|
|
461
|
+
fail("turn", error);
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
if (!this.canApplyResults || signal.reason === "park")
|
|
465
|
+
this.#behavior.dropTurn(turn);
|
|
466
|
+
else {
|
|
467
|
+
trace("harness:turn", `close ${turn.uuid} cancel=${turn.cancelRequested} park=false`);
|
|
468
|
+
// The post hooks are bounded, but a throw must still reach the close below.
|
|
469
|
+
try {
|
|
470
|
+
await this.#behavior.leaveTurn(turn, broke || turn.broke);
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
if (this.canApplyResults)
|
|
474
|
+
fail("turn boundary", error);
|
|
475
|
+
}
|
|
476
|
+
if (this.canApplyResults) {
|
|
477
|
+
const ending = turn.cancelRequested
|
|
478
|
+
? { kind: "cancel" }
|
|
479
|
+
: (broke || turn.broke) === ""
|
|
480
|
+
? { kind: "finish" }
|
|
481
|
+
: { kind: "broke", detail: broke || turn.broke };
|
|
482
|
+
closeTurn(turn, ending, this.#closeOptions());
|
|
483
|
+
this.#releaseUnansweredCall(turn);
|
|
484
|
+
// Self-service lane: the turn that was head takes the next queued message itself.
|
|
485
|
+
// The prompt-queue reaction stays as the fallback (a restart re-arms it).
|
|
486
|
+
void this.sessionDaemon.drainPromptQueue();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}, { seal: false });
|
|
491
|
+
this.#disposeLive = autorun(() => {
|
|
492
|
+
if (!this.activated || this.#closed || !this.canApplyResults)
|
|
493
|
+
return;
|
|
494
|
+
const reachable = this.reachableJobs;
|
|
495
|
+
for (const job of this.inFlightJobs()) {
|
|
496
|
+
if (!(job instanceof SessionTurn) || job.reducerExited || reachable.includes(job))
|
|
497
|
+
continue;
|
|
498
|
+
failLeftoverApprovables(job, (leaf) => this.#abortLeaf(leaf), this.sessionDaemon.kernel.catalog);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
// Honor refuses an unstamped completion (`shouldHonor`). An executor would
|
|
502
|
+
// stamp `orchestrationResult` or `completed` on every historical round.
|
|
503
|
+
// Snapshot at activate is the recovery set; only a round that appears after
|
|
504
|
+
// that is a new attempt, including an isolated `.prompt`.
|
|
505
|
+
const stopBaseline = reaction(() => this.activated, (activated) => {
|
|
506
|
+
if (!activated || this.#completionBaseline !== null)
|
|
507
|
+
return;
|
|
508
|
+
this.#completionBaseline = new Set(this.#openUnstartedCompletions());
|
|
509
|
+
});
|
|
510
|
+
const stopDrive = reaction(() => this.#openUnstartedCompletions(), (jobs) => {
|
|
511
|
+
const baseline = this.#completionBaseline;
|
|
512
|
+
if (baseline === null)
|
|
513
|
+
return;
|
|
514
|
+
for (const job of jobs) {
|
|
515
|
+
if (baseline.has(job))
|
|
516
|
+
continue;
|
|
517
|
+
baseline.add(job);
|
|
518
|
+
const pew = job.expectation;
|
|
519
|
+
void this.complete(completionRequest(pew)).catch((error) => {
|
|
520
|
+
if (!this.canApplyResults || pew.isDone || this.#flights.aborted(pew))
|
|
521
|
+
return;
|
|
522
|
+
pew.fail("crash", error instanceof Error ? error.message : String(error), "null");
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}, { equals: comparer.shallow });
|
|
526
|
+
this.#disposeCompletions = () => {
|
|
527
|
+
stopBaseline();
|
|
528
|
+
stopDrive();
|
|
529
|
+
};
|
|
530
|
+
this.#disposeConnections = autorun(() => {
|
|
531
|
+
if (!this.activated || this.#closed)
|
|
532
|
+
return;
|
|
533
|
+
for (const name of Object.keys(this.drivers)) {
|
|
534
|
+
const spec = this.drivers[name];
|
|
535
|
+
if (spec === undefined)
|
|
536
|
+
continue;
|
|
537
|
+
if (!this.#loggedDriver.has(name)) {
|
|
538
|
+
this.#loggedDriver.set(name, "");
|
|
539
|
+
step(`driver ${name}`);
|
|
379
540
|
}
|
|
380
|
-
|
|
381
|
-
|
|
541
|
+
if (spec.readyState === "ready" && this.#loggedDriver.get(name) !== "ready") {
|
|
542
|
+
this.#loggedDriver.set(name, "ready");
|
|
543
|
+
step(`driver ${name} ready`);
|
|
544
|
+
}
|
|
545
|
+
if (spec.readyState === "failed") {
|
|
546
|
+
const detail = spec.error === "" ? "failed" : spec.error;
|
|
547
|
+
if (this.#loggedError.get(`driver:${name}`) !== detail) {
|
|
548
|
+
this.#loggedError.set(`driver:${name}`, detail);
|
|
549
|
+
fail(`driver ${name}`, detail);
|
|
550
|
+
}
|
|
382
551
|
}
|
|
383
|
-
const ending = turn.cancelRequested
|
|
384
|
-
? { kind: "cancel" }
|
|
385
|
-
: broke === ""
|
|
386
|
-
? { kind: "finish" }
|
|
387
|
-
: { kind: "broke", detail: broke };
|
|
388
|
-
closeTurn(turn, ending, this.#closeOptions());
|
|
389
|
-
this.#releaseUnansweredCall(turn);
|
|
390
|
-
// Self-service lane: the turn that was head takes the next queued message itself.
|
|
391
|
-
// The prompt-queue reaction stays as the fallback (a restart re-arms it).
|
|
392
|
-
void this.sessionDaemon.drainPromptQueue();
|
|
393
552
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
553
|
+
for (const connectionId of Object.keys(this.connections)) {
|
|
554
|
+
const spec = this.connections[connectionId];
|
|
555
|
+
if (spec === undefined)
|
|
556
|
+
continue;
|
|
557
|
+
// Catalog is report. Honor ensures a wire when work names it.
|
|
558
|
+
const live = spec.live;
|
|
559
|
+
const models = Array.isArray(live.models) ? live.models.map(String) : [];
|
|
560
|
+
const can = spec.driver?.abilityClaim ?? [];
|
|
561
|
+
const liveSig = `${spec.readyState}|${can.join(",")}|${models.length}|${models.slice(0, 8).join(",")}`;
|
|
562
|
+
if (this.#loggedError.get(`live:${connectionId}`) !== liveSig) {
|
|
563
|
+
this.#loggedError.set(`live:${connectionId}`, liveSig);
|
|
564
|
+
const authoring = spec.__doc__;
|
|
565
|
+
const transport = this.kernel.doc;
|
|
566
|
+
step(`kernel live: connection=${connectionId} readyState=${spec.readyState} can=${can.join(",") || "(none)"} models=${models.length} ${models.length === 0 ? "(none)" : models.slice(0, 8).join(",")} shadow=${docTag(authoring)} main=${docTag(transport)} sameDoc=${authoring === transport}`);
|
|
567
|
+
}
|
|
568
|
+
if (spec.readyState !== "failed")
|
|
569
|
+
continue;
|
|
570
|
+
const detail = spec.error === "" ? "failed" : spec.error;
|
|
571
|
+
if (this.#loggedError.get(connectionId) === detail)
|
|
572
|
+
continue;
|
|
573
|
+
this.#loggedError.set(connectionId, detail);
|
|
574
|
+
fail(`connection ${connectionId}`, detail);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
this.#disposeBoundWire = reaction(() => (this.activated ? (this.session.root.kvs.connection ?? "").trim() : ""), (id) => {
|
|
578
|
+
if (!this.activated || this.#closed || id === "" || this.connections[id] === undefined)
|
|
579
|
+
return;
|
|
580
|
+
void this.ensure(id);
|
|
581
|
+
}, { fireImmediately: true });
|
|
582
|
+
}
|
|
583
|
+
async #runMcp(job, signal) {
|
|
584
|
+
signal = this.#ownedSignal(signal);
|
|
585
|
+
if (!this.canApplyResults)
|
|
412
586
|
return;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const stopDrive = reaction(() => this.#openUnstartedCompletions(), (jobs) => {
|
|
416
|
-
const baseline = this.#completionBaseline;
|
|
417
|
-
if (baseline === null)
|
|
587
|
+
if (this.#outsideWorkerCeiling(job)) {
|
|
588
|
+
closeMcpFailure(job, OUTSIDE_WORKER_CEILING);
|
|
418
589
|
return;
|
|
419
|
-
for (const job of jobs) {
|
|
420
|
-
if (baseline.has(job))
|
|
421
|
-
continue;
|
|
422
|
-
baseline.add(job);
|
|
423
|
-
const pew = job.expectation;
|
|
424
|
-
void this.complete(completionRequest(pew)).catch((error) => {
|
|
425
|
-
if (pew.isDone || this.#flights.aborted(pew))
|
|
426
|
-
return;
|
|
427
|
-
pew.fail("crash", error instanceof Error ? error.message : String(error), "null");
|
|
428
|
-
});
|
|
429
590
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
stopBaseline();
|
|
433
|
-
stopDrive();
|
|
434
|
-
};
|
|
435
|
-
this.#disposeConnections = autorun(() => {
|
|
436
|
-
if (!this.activated || this.#closed)
|
|
591
|
+
if (mcpSessionPersistDenies(job.connectionKey, job.serverToolName, this.session.root.toolBlocklist)) {
|
|
592
|
+
closeMcpFailure(job, `unknown MCP tool "${job.serverToolName.trim() || job.name}"`);
|
|
437
593
|
return;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
|
|
444
|
-
|
|
594
|
+
}
|
|
595
|
+
if (isPrivatePluginConnection(job.connectionKey)) {
|
|
596
|
+
const parsed = parsePrivateConnectionKey(job.connectionKey);
|
|
597
|
+
const recipe = this.session.root.agent.spec.name;
|
|
598
|
+
if (parsed == null || recipe === "") {
|
|
599
|
+
closeMcpFailure(job, "private MCP tool not admitted");
|
|
600
|
+
return;
|
|
445
601
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
602
|
+
const admission = admitPrivateMcpTool(this.sessionDaemon.kernel.catalog.pluginInstall, recipe, parsed.pluginName, parsed.serverName, job.serverToolName);
|
|
603
|
+
if (!admission.allowed) {
|
|
604
|
+
closeMcpFailure(job, admission.reason ?? "private MCP tool not admitted");
|
|
605
|
+
return;
|
|
449
606
|
}
|
|
450
|
-
if (
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
607
|
+
if (job.serverToolName === "commit") {
|
|
608
|
+
const requestId = job.argString("requestId");
|
|
609
|
+
const open = loadOpenCall(this.sessionDaemon.kernel, this.session.root, requestId);
|
|
610
|
+
if (open == null) {
|
|
611
|
+
closeMcpFailure(job, "commit requestId is not an open writer Call");
|
|
612
|
+
return;
|
|
455
613
|
}
|
|
456
614
|
}
|
|
457
615
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
616
|
+
const resumed = await this.#inboundResumed(hostingTurnOf(this.session.root, job) ?? this.session.root.head, signal);
|
|
617
|
+
if (!this.canApplyResults)
|
|
618
|
+
return;
|
|
619
|
+
if (!resumed) {
|
|
620
|
+
if (isParkAbort(signal))
|
|
621
|
+
return;
|
|
622
|
+
closeMcpFailure(job, signal.aborted ? cancelOutcomeText(signal.reason) : "contribution paused");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (signal.aborted) {
|
|
626
|
+
if (isParkAbort(signal)) {
|
|
627
|
+
await waitDispatchedMcp(job);
|
|
628
|
+
if (this.canApplyResults)
|
|
629
|
+
sealMcpIfSettled(job);
|
|
630
|
+
return;
|
|
472
631
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const detail = spec.error === "" ? "failed" : spec.error;
|
|
476
|
-
if (this.#loggedError.get(connectionId) === detail)
|
|
477
|
-
continue;
|
|
478
|
-
this.#loggedError.set(connectionId, detail);
|
|
479
|
-
fail(`connection ${connectionId}`, detail);
|
|
632
|
+
closeMcpFailure(job, cancelOutcomeText(signal.reason));
|
|
633
|
+
return;
|
|
480
634
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
635
|
+
const key = job.connectionKey.trim();
|
|
636
|
+
if (key === "") {
|
|
637
|
+
const name = job.serverToolName.trim() || job.name.trim() || "mcp";
|
|
638
|
+
closeMcpFailure(job, `${name}: missing MCP connection`);
|
|
484
639
|
return;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
640
|
+
}
|
|
641
|
+
if (job.dispatched && !job.completed) {
|
|
642
|
+
const settled = await this.#settleLeftoverPew(job.expectation, key, "MCP connection", signal);
|
|
643
|
+
if (!this.canApplyResults)
|
|
644
|
+
return;
|
|
645
|
+
if (settled === "settled") {
|
|
646
|
+
sealMcpIfSettled(job);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (isParkAbort(signal)) {
|
|
650
|
+
await waitDispatchedMcp(job);
|
|
651
|
+
if (this.canApplyResults)
|
|
652
|
+
sealMcpIfSettled(job);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
job.outcomeSnap ??= { ok: false, text: cancelOutcomeText(signal.reason) };
|
|
656
|
+
this.#terminatePew(job.expectation);
|
|
657
|
+
job.markComplete();
|
|
502
658
|
return;
|
|
503
659
|
}
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
506
|
-
closeMcpFailure(job,
|
|
660
|
+
const missing = this.#connectionFailure(key, "MCP connection");
|
|
661
|
+
if (missing !== null) {
|
|
662
|
+
closeMcpFailure(job, missing);
|
|
507
663
|
return;
|
|
508
664
|
}
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
665
|
+
try {
|
|
666
|
+
const instance = (await this.ensure(key));
|
|
667
|
+
if (!this.canApplyResults)
|
|
668
|
+
return;
|
|
669
|
+
if (!connectionSupports(instance, "mcp") || instance.callTool == null) {
|
|
670
|
+
closeMcpFailure(job, `${key}: missing MCP connection`);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const args = job.args;
|
|
674
|
+
if (args === null) {
|
|
675
|
+
closeMcpFailure(job, "mcp: arguments");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const result = await instance.callTool({
|
|
679
|
+
serverToolName: job.serverToolName,
|
|
680
|
+
args,
|
|
681
|
+
signal,
|
|
682
|
+
onDispatch: () => {
|
|
683
|
+
if (!this.canApplyResults)
|
|
684
|
+
throw new SessionOwnershipError();
|
|
685
|
+
job.dispatched = true;
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
if (!this.canApplyResults)
|
|
689
|
+
return;
|
|
690
|
+
job.outcomeSnap ??= result;
|
|
691
|
+
await this.#behavior.finishTool(job, signal);
|
|
692
|
+
if (this.canApplyResults && !job.completed && !job.cancelled)
|
|
693
|
+
job.markComplete();
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
if (!this.canApplyResults)
|
|
697
|
+
return;
|
|
698
|
+
if (isParkAbort(signal)) {
|
|
699
|
+
await waitDispatchedMcp(job);
|
|
700
|
+
if (this.canApplyResults)
|
|
701
|
+
sealMcpIfSettled(job);
|
|
514
702
|
return;
|
|
515
703
|
}
|
|
704
|
+
if (error instanceof ConnectionMiss) {
|
|
705
|
+
closeMcpFailure(job, error.code === "noconnect" ? `${key}: missing MCP connection` : error.message);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
closeMcpFailure(job, signal.aborted ? cancelOutcomeText(signal.reason) : error instanceof Error ? error.message : String(error));
|
|
516
709
|
}
|
|
517
710
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
711
|
+
/**
|
|
712
|
+
* A paused inbound call holds its turn's work. True when the call is not
|
|
713
|
+
* paused or resumed while waiting; false when it ended, stopped, or aborted.
|
|
714
|
+
*/
|
|
715
|
+
async #inboundResumed(turn, signal, stopped = () => false) {
|
|
716
|
+
const inboundId = turn?.agentRequestId.trim() ?? "";
|
|
717
|
+
if (inboundId === "")
|
|
718
|
+
return true;
|
|
719
|
+
const catalog = this.sessionDaemon.kernel.catalog;
|
|
720
|
+
const inbound = loadOpenCall(this.sessionDaemon.kernel, this.session.root, inboundId);
|
|
721
|
+
if (inbound == null || !isCallPaused(inbound, catalog))
|
|
722
|
+
return true;
|
|
723
|
+
await waitUntil(() => !isCallPaused(inbound, catalog) || inbound.completed || stopped(), signal);
|
|
724
|
+
return !signal.aborted && !inbound.completed && !isCallPaused(inbound, catalog);
|
|
523
725
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Covering receipt before BODY. Failed write is not dispatch. Recheck
|
|
728
|
+
* cancel after the receipt — local mutation is not a receipt.
|
|
729
|
+
*/
|
|
730
|
+
async #gateIntent(job, signal) {
|
|
731
|
+
if (!this.canApplyResults)
|
|
732
|
+
return { dispatch: false, persistFailed: false };
|
|
733
|
+
const documents = this.sessionDaemon.kernel.runtime.documents;
|
|
734
|
+
if (documents != null) {
|
|
735
|
+
const gate = await persistJobIntent(documents, documentKey("sessions", this.session.root.sessionId), job);
|
|
736
|
+
if (!this.canApplyResults)
|
|
737
|
+
return { dispatch: false, persistFailed: false };
|
|
738
|
+
if (!gate.ok)
|
|
739
|
+
return { dispatch: false, persistFailed: true };
|
|
740
|
+
}
|
|
741
|
+
if (job.cancelled ||
|
|
742
|
+
job.completed ||
|
|
743
|
+
signal.aborted ||
|
|
744
|
+
hostingTurnOf(this.session.root, job)?.cancelRequested === true) {
|
|
745
|
+
return { dispatch: false, persistFailed: false };
|
|
529
746
|
}
|
|
530
|
-
|
|
531
|
-
return;
|
|
747
|
+
return { dispatch: true };
|
|
532
748
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
749
|
+
/**
|
|
750
|
+
* A BODY the gate refused still settles and runs its finish step, so the
|
|
751
|
+
* turn's join returns. A park leaves the job open for the next wrap.
|
|
752
|
+
*/
|
|
753
|
+
async #bodyRefused(job, signal) {
|
|
754
|
+
const intent = await this.#gateIntent(job, signal);
|
|
755
|
+
if (!this.canApplyResults)
|
|
756
|
+
return true;
|
|
757
|
+
if (intent.dispatch)
|
|
758
|
+
return false;
|
|
759
|
+
if (!job.completed && (intent.persistFailed || !isParkAbort(signal))) {
|
|
760
|
+
const text = intent.persistFailed ? INTENT_NOT_PERSISTED : cancelOutcomeText(signal.reason);
|
|
761
|
+
job.outcomeSnap ??= { ok: false, text };
|
|
762
|
+
if (job instanceof MCPToolJob && !job.expectation.isDone)
|
|
763
|
+
job.expectation.fail("crash", text, "null");
|
|
764
|
+
job.markComplete();
|
|
765
|
+
}
|
|
766
|
+
await this.#behavior.finishTool(job, signal);
|
|
767
|
+
return true;
|
|
538
768
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
769
|
+
/** The two web tools differ only in their argument, their connection pick, and the call. */
|
|
770
|
+
async #runWeb(job, signal) {
|
|
771
|
+
signal = this.#ownedSignal(signal);
|
|
772
|
+
if (await this.#bodyRefused(job, signal))
|
|
542
773
|
return;
|
|
543
|
-
|
|
544
|
-
if (isParkAbort(signal)) {
|
|
545
|
-
await waitDispatchedMcp(job);
|
|
546
|
-
sealMcpIfSettled(job);
|
|
774
|
+
if (!this.canApplyResults)
|
|
547
775
|
return;
|
|
776
|
+
const web = job instanceof WebSearchToolJob
|
|
777
|
+
? {
|
|
778
|
+
name: "web_search",
|
|
779
|
+
ability: "web-search",
|
|
780
|
+
missing: job.query === "" ? "query is required — call web_search again with the text to search for" : "",
|
|
781
|
+
pick: pickWebSearchConnection,
|
|
782
|
+
call: (instance) => instance.webSearch?.({ query: job.query, limit: job.limit, signal }),
|
|
783
|
+
}
|
|
784
|
+
: {
|
|
785
|
+
name: "web_extract",
|
|
786
|
+
ability: "web-extract",
|
|
787
|
+
missing: job.url === "" ? "url is required — call web_extract again with the page url" : "",
|
|
788
|
+
pick: pickWebExtractConnection,
|
|
789
|
+
call: (instance) => instance.webExtract?.({ url: job.url, charLimit: job.charLimit, signal }),
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
if (this.#outsideWorkerCeiling(job)) {
|
|
793
|
+
job.outcomeSnap ??= { ok: false, text: OUTSIDE_WORKER_CEILING };
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (signal.aborted) {
|
|
797
|
+
job.outcomeSnap ??= { ok: false, text: cancelOutcomeText(signal.reason) };
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (web.missing !== "") {
|
|
801
|
+
job.outcomeSnap ??= { ok: false, text: `${web.name}: ${web.missing}` };
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const key = web.pick(this.kernel.root);
|
|
805
|
+
if (key === null) {
|
|
806
|
+
job.outcomeSnap ??= {
|
|
807
|
+
ok: false,
|
|
808
|
+
text: `${web.name}: no ${web.ability} connection — /connect a provider or leave the keyless ring on`,
|
|
809
|
+
};
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
job.connectionKey = key;
|
|
813
|
+
if (job.expectation.startedAtMs == null)
|
|
814
|
+
job.expectation.connectionId = key;
|
|
815
|
+
const leftoverPew = job.expectation;
|
|
816
|
+
if (leftoverPew.startedAtMs != null && !leftoverPew.isDone) {
|
|
817
|
+
const settled = await this.#settleLeftoverPew(leftoverPew, key, `${web.ability} connection`, signal);
|
|
818
|
+
if (!this.canApplyResults)
|
|
819
|
+
return;
|
|
820
|
+
if (settled === "aborted") {
|
|
821
|
+
job.outcomeSnap ??= { ok: false, text: cancelOutcomeText(signal.reason) };
|
|
822
|
+
this.#terminatePew(leftoverPew);
|
|
823
|
+
}
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const instance = (await this.ensure(key));
|
|
828
|
+
if (!this.canApplyResults)
|
|
829
|
+
return;
|
|
830
|
+
const found = connectionSupports(instance, web.ability) ? await web.call(instance) : undefined;
|
|
831
|
+
if (!this.canApplyResults)
|
|
832
|
+
return;
|
|
833
|
+
job.outcomeSnap ??= found ?? { ok: false, text: `${web.name}: intern failed` };
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
if (!this.canApplyResults)
|
|
837
|
+
return;
|
|
838
|
+
job.outcomeSnap ??= {
|
|
839
|
+
ok: false,
|
|
840
|
+
text: signal.aborted
|
|
841
|
+
? cancelOutcomeText(signal.reason)
|
|
842
|
+
: error instanceof Error
|
|
843
|
+
? error.message
|
|
844
|
+
: String(error),
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
finally {
|
|
849
|
+
if (this.canApplyResults) {
|
|
850
|
+
await this.#behavior.finishTool(job, signal);
|
|
851
|
+
if (this.canApplyResults)
|
|
852
|
+
this.#sealWebJob(job);
|
|
853
|
+
}
|
|
548
854
|
}
|
|
549
|
-
job.outcomeSnap ??= { ok: false, text: cancelOutcomeText(signal.reason) };
|
|
550
|
-
this.#terminatePew(job.expectation);
|
|
551
|
-
job.markComplete();
|
|
552
|
-
return;
|
|
553
855
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
return;
|
|
856
|
+
/** A named worker runs only the tools its catalog lists. */
|
|
857
|
+
#outsideWorkerCeiling(job) {
|
|
858
|
+
const root = this.session.root;
|
|
859
|
+
return root.agent.spec.name !== "" && sessionCatalog(root)?.catalogEntry(job.name) == null;
|
|
558
860
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
return
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
861
|
+
/** Why `key` cannot serve, or null while it may. */
|
|
862
|
+
#connectionFailure(key, what) {
|
|
863
|
+
const spec = this.connections[key];
|
|
864
|
+
if (spec === undefined)
|
|
865
|
+
return `${key}: missing ${what}`;
|
|
866
|
+
if (spec.readyState === "failed")
|
|
867
|
+
return spec.error === "" ? `${key}: failed` : spec.error;
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* A PEW an earlier wrap started settles on its own; it fails here only when
|
|
872
|
+
* its connection is gone. Aborted leaves the PEW to the caller.
|
|
873
|
+
*/
|
|
874
|
+
async #settleLeftoverPew(pew, key, what, signal) {
|
|
875
|
+
const settled = await waitUntil(() => pew.isDone || this.#connectionFailure(key, what) !== null, signal);
|
|
876
|
+
if (!this.canApplyResults)
|
|
877
|
+
return "aborted";
|
|
878
|
+
if (settled === "aborted")
|
|
879
|
+
return settled;
|
|
880
|
+
const detail = this.#connectionFailure(key, what);
|
|
881
|
+
if (detail !== null && !pew.isDone)
|
|
882
|
+
pew.fail("crash", detail, "null");
|
|
883
|
+
return settled;
|
|
884
|
+
}
|
|
885
|
+
#sealWebJob(job) {
|
|
886
|
+
if (job.completed || job.cancelled)
|
|
568
887
|
return;
|
|
888
|
+
if ((job.receiptJson != null && job.receiptJson !== "") || job.expectation.isDone) {
|
|
889
|
+
job.markComplete();
|
|
569
890
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Guard phase of a host tool. The session's plugin guards wrap the license:
|
|
894
|
+
* a guard may refuse, answer without running the tool, or narrow the
|
|
895
|
+
* arguments, which are written back before the license judges them.
|
|
896
|
+
* Pre hooks run once the call is licensed.
|
|
897
|
+
*/
|
|
898
|
+
async #guardTool(job) {
|
|
899
|
+
if (!this.canApplyResults)
|
|
900
|
+
return false;
|
|
901
|
+
// Origin-aware door. Scheme inner admits and RPC/statusline still enter
|
|
902
|
+
// here as model until those callers pass their origin through this path.
|
|
903
|
+
const result = await guardHostTool({
|
|
904
|
+
job,
|
|
905
|
+
signal: this.#behavior.signalFor(job),
|
|
906
|
+
stopped: () => !this.canApplyResults || !isWorkReachable(this.session.root, job),
|
|
907
|
+
origin: "model",
|
|
908
|
+
hookTimeoutMs: pluginHookTimeoutMs(this.session.root.kvs, this.kernel.root.kvs),
|
|
909
|
+
agent: this.#behavior.agent,
|
|
910
|
+
behaviorEntries: behaviorEntriesThatRun(behaviorEntriesOf(this.session.root), (wire) => sessionCatalog(this.session.root)?.catalogEntry(wire)),
|
|
911
|
+
guardedCall: (leaf) => this.#guardedCall(leaf),
|
|
912
|
+
license: (leaf) => this.#license(leaf),
|
|
913
|
+
turnId: (hostingTurnOf(this.session.root, job) ?? this.session.root.head)?.uuid ?? "",
|
|
914
|
+
narrowAgent: (leaf, tools) => this.#narrowAgent(leaf, tools),
|
|
915
|
+
onUnentered: (leaf, outcome) => {
|
|
916
|
+
if (this.canApplyResults)
|
|
917
|
+
stampUnentered(leaf, outcome);
|
|
576
918
|
},
|
|
919
|
+
onProblem: (what, error) => this.#behavior.problem(what, error),
|
|
920
|
+
onReport: (leaf, outcome) => this.#behavior.toolReport(leaf, outcome),
|
|
577
921
|
});
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
922
|
+
if (!this.canApplyResults) {
|
|
923
|
+
result.opened?.abandon();
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
if (result.opened != null)
|
|
927
|
+
this.#behavior.holdTool(job, result.opened);
|
|
928
|
+
return result.allowed;
|
|
582
929
|
}
|
|
583
|
-
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
930
|
+
#guardedCall(job) {
|
|
931
|
+
if (job instanceof BotCallJob) {
|
|
932
|
+
// Named bot calls retain the existing agent guard point, without child tool grants.
|
|
933
|
+
if (job.name !== AGENT_ASK && job.name !== AGENT_SEND)
|
|
934
|
+
return guardedCallOf(job, this.session.root.path);
|
|
935
|
+
const spec = job.botName || job.contributionBotName || job.argString("spec").trim();
|
|
936
|
+
const catalog = sessionCatalog(this.session.root) ?? this.sessionDaemon.kernel.catalog;
|
|
937
|
+
const recipe = catalog.recipe(spec);
|
|
938
|
+
return recipe == null ? null : agentGuardedCall(job, spec, recipe.spec.tools, false);
|
|
939
|
+
}
|
|
940
|
+
if (!(job instanceof AgentCallJob))
|
|
941
|
+
return guardedCallOf(job, this.session.root.path);
|
|
942
|
+
if (job.name !== AGENT_ASK && job.name !== AGENT_SEND)
|
|
943
|
+
return null;
|
|
944
|
+
const spec = job.argString("spec").trim();
|
|
945
|
+
const fresh = job.workerSessionId === "";
|
|
946
|
+
const catalog = sessionCatalog(this.session.root) ?? this.sessionDaemon.kernel.catalog;
|
|
947
|
+
if (spec === "") {
|
|
948
|
+
const tools = job.inheritCallerTools ? catalog.catalogWireNames() : job.tools;
|
|
949
|
+
return agentGuardedCall(job, "", tools, fresh);
|
|
950
|
+
}
|
|
951
|
+
const recipe = catalog.recipe(spec);
|
|
952
|
+
return recipe == null ? null : agentGuardedCall(job, spec, recipe.spec.tools, fresh);
|
|
953
|
+
}
|
|
954
|
+
/** An ad-hoc ask carries its tools on the job; a one-shot recipe worker takes them at mint. */
|
|
955
|
+
#narrowAgent(job, tools) {
|
|
956
|
+
if (!this.canApplyResults)
|
|
957
|
+
return;
|
|
958
|
+
if (job.argString("spec").trim() === "") {
|
|
959
|
+
job.tools = [...tools];
|
|
960
|
+
job.inheritCallerTools = false;
|
|
587
961
|
return;
|
|
588
962
|
}
|
|
589
|
-
|
|
590
|
-
|
|
963
|
+
this.#agentTools.set(job, tools);
|
|
964
|
+
// A call cancelled before its worker is minted never takes these.
|
|
965
|
+
void when(() => job.completed || job.cancelled).then(() => this.#agentTools.delete(job));
|
|
966
|
+
}
|
|
967
|
+
async #finishCall(job, signal) {
|
|
968
|
+
if (!this.canApplyResults)
|
|
591
969
|
return;
|
|
970
|
+
if (job instanceof AgentCallJob)
|
|
971
|
+
this.#agentTools.delete(job);
|
|
972
|
+
await this.#behavior.finishCall(job, signal);
|
|
973
|
+
}
|
|
974
|
+
async #license(job) {
|
|
975
|
+
if (!this.canApplyResults)
|
|
976
|
+
return false;
|
|
977
|
+
const started = Date.now();
|
|
978
|
+
trace("harness:license", `wait ${job.toolCallId || job.name} tool=${job.name} policy=${job.policyResult ?? "pending"}`);
|
|
979
|
+
if (licenseDecisionNow(job) === false) {
|
|
980
|
+
stampLicenseDeny(job, this.session.root.permissionMode);
|
|
981
|
+
await noteDenied(job, this.session.root);
|
|
982
|
+
trace("harness:license", `deny ${job.toolCallId || job.name}`);
|
|
983
|
+
return false;
|
|
592
984
|
}
|
|
593
|
-
|
|
985
|
+
const hostTurn = hostingTurnOf(this.session.root, job) ?? this.session.root.head;
|
|
986
|
+
const ok = await license(job, hostTurn, this.session.root);
|
|
987
|
+
if (!this.canApplyResults)
|
|
988
|
+
return false;
|
|
989
|
+
trace("harness:license", `${ok ? "allow" : "deny"} ${job.toolCallId || job.name} ms=${Date.now() - started} policy=${job.policyResult ?? ""} judge=${job.classifierResult ?? ""}`);
|
|
990
|
+
if (ok || job.cancelled || job.completed)
|
|
991
|
+
return ok;
|
|
992
|
+
const decision = licenseDecisionNow(job);
|
|
993
|
+
if (decision === false)
|
|
994
|
+
stampLicenseDeny(job, this.session.root.permissionMode);
|
|
995
|
+
else if (!this.#closed)
|
|
996
|
+
cancelUnstartedTool(job);
|
|
997
|
+
return false;
|
|
594
998
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const catalog = this.sessionDaemon.kernel.catalog;
|
|
605
|
-
const inbound = loadOpenCall(this.sessionDaemon.kernel, this.session.root, inboundId);
|
|
606
|
-
if (inbound == null || !isCallPaused(inbound, catalog))
|
|
607
|
-
return true;
|
|
608
|
-
await waitUntil(() => !isCallPaused(inbound, catalog) || inbound.completed || stopped(), signal);
|
|
609
|
-
return !signal.aborted && !inbound.completed && !isCallPaused(inbound, catalog);
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Covering receipt before BODY. Failed write is not dispatch. Recheck
|
|
613
|
-
* cancel after the receipt — local mutation is not a receipt.
|
|
614
|
-
*/
|
|
615
|
-
async #gateIntent(job, signal) {
|
|
616
|
-
const documents = this.sessionDaemon.kernel.runtime.documents;
|
|
617
|
-
if (documents != null) {
|
|
618
|
-
const gate = await persistJobIntent(documents, documentKey("sessions", this.session.root.sessionId), job);
|
|
619
|
-
if (!gate.ok)
|
|
620
|
-
return { dispatch: false, persistFailed: true };
|
|
999
|
+
#agentHost() {
|
|
1000
|
+
return {
|
|
1001
|
+
kernel: this.sessionDaemon.kernel,
|
|
1002
|
+
project: this.sessionDaemon.project,
|
|
1003
|
+
caller: this.session.root,
|
|
1004
|
+
problem: (what, error) => this.#behavior.problem(what, error),
|
|
1005
|
+
canApplyResults: () => this.canApplyResults,
|
|
1006
|
+
ownershipSignal: this.sessionDaemon.sessionRuntime.ownershipSignal,
|
|
1007
|
+
};
|
|
621
1008
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1009
|
+
#permissionDesk() {
|
|
1010
|
+
if (!delegatesPermissionMode(this.session.root))
|
|
1011
|
+
return null;
|
|
1012
|
+
const id = permissionDeskSessionId(this.session.root, this.kernel.root);
|
|
1013
|
+
if (id === "" || id === this.session.root.sessionId)
|
|
1014
|
+
return null;
|
|
1015
|
+
return this.sessionDaemon.kernel.sessionPlexus(id)?.root ?? null;
|
|
627
1016
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
* A BODY the gate refused still settles and runs its finish step, so the
|
|
632
|
-
* turn's join returns. A park leaves the job open for the next wrap.
|
|
633
|
-
*/
|
|
634
|
-
async #bodyRefused(job, signal) {
|
|
635
|
-
const intent = await this.#gateIntent(job, signal);
|
|
636
|
-
if (intent.dispatch)
|
|
637
|
-
return false;
|
|
638
|
-
if (!job.completed && (intent.persistFailed || !isParkAbort(signal))) {
|
|
639
|
-
const text = intent.persistFailed ? INTENT_NOT_PERSISTED : cancelOutcomeText(signal.reason);
|
|
640
|
-
job.outcomeSnap ??= { ok: false, text };
|
|
641
|
-
if (job instanceof MCPToolJob && !job.expectation.isDone)
|
|
642
|
-
job.expectation.fail("crash", text, "null");
|
|
643
|
-
job.markComplete();
|
|
1017
|
+
#deskPermissionSnap() {
|
|
1018
|
+
const desk = this.#permissionDesk();
|
|
1019
|
+
return desk == null ? null : deskPermissionSnap(desk);
|
|
644
1020
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
1021
|
+
ensure(connectionId) {
|
|
1022
|
+
if (connectionId === NOUS_GATEWAY_CONNECTION_ID)
|
|
1023
|
+
return this.#ensureBorrowedGateway();
|
|
1024
|
+
return this.#loggedEnsure(connectionId, this.isEnsured(connectionId), super.ensure(connectionId));
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* `web-nous` is one intern id whose person can move. Memo by intern+source;
|
|
1028
|
+
* {@link dropConnection} is the remint door so B is not A's lease.
|
|
1029
|
+
*/
|
|
1030
|
+
#ensureBorrowedGateway() {
|
|
1031
|
+
const connectionId = NOUS_GATEWAY_CONNECTION_ID;
|
|
1032
|
+
const source = nousGatewaySourceOf(this.kernel.root, connectionId);
|
|
1033
|
+
const tracked = this.#gatewayEnsure.has(borrowedEnsureKey(connectionId, source));
|
|
1034
|
+
return this.#loggedEnsure(connectionId, tracked, memoBorrowedEnsure(this.#gatewayEnsure, connectionId, source, (id) => this.ensure(id), () => super.ensure(connectionId), () => dropConnection(this.kernel.root, connectionId)));
|
|
1035
|
+
}
|
|
1036
|
+
#loggedEnsure(connectionId, tracked, pending) {
|
|
1037
|
+
if (tracked)
|
|
1038
|
+
return pending;
|
|
1039
|
+
step(`ensure ${connectionId}`);
|
|
1040
|
+
void pending.then(() => step(`ensure ${connectionId} ready`), (error) => fail(`ensure ${connectionId}`, error));
|
|
1041
|
+
return pending;
|
|
1042
|
+
}
|
|
1043
|
+
attachConnection(connectionId, instance) {
|
|
1044
|
+
const kernel = this.sessionDaemon.kernel;
|
|
1045
|
+
instance.bindSettingsWriter((patch) => {
|
|
1046
|
+
kernel.adjustConfig((config) => {
|
|
1047
|
+
if (!Object.hasOwn(config.connections, connectionId))
|
|
1048
|
+
return;
|
|
1049
|
+
const row = config.connections[connectionId];
|
|
1050
|
+
row.settings = { ...row.settings, ...patch };
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
connectionSecrets(connectionId) {
|
|
1055
|
+
const root = this.sessionDaemon.kernel.root;
|
|
1056
|
+
const ownerId = () => secretsConnectionId(this.kernel.root, connectionId);
|
|
1057
|
+
return {
|
|
1058
|
+
get: (field) => readConnectionSecrets(root, ownerId())[field],
|
|
1059
|
+
all: () => readConnectionSecrets(root, ownerId()),
|
|
1060
|
+
replace: (secrets) => {
|
|
1061
|
+
writeConnectionSecrets(root, ownerId(), { ...secrets });
|
|
1062
|
+
},
|
|
666
1063
|
};
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
return;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Remainder / compact `.prompt` / turn port. Interns the row, then
|
|
1067
|
+
* `connection.complete` → `handle(round)`. `KernelDaemon` has no `complete()`.
|
|
1068
|
+
*/
|
|
1069
|
+
complete(req) {
|
|
1070
|
+
if (!this.canApplyResults)
|
|
1071
|
+
return Promise.reject(new SessionOwnershipError());
|
|
1072
|
+
const ownership = this.sessionDaemon.sessionRuntime.ownershipSignal;
|
|
1073
|
+
const abort = new AbortController();
|
|
1074
|
+
// Losing the writer leaves recovery in charge of the open attempt.
|
|
1075
|
+
const onOwnershipLost = () => abort.abort("park");
|
|
1076
|
+
const onCallerAbort = () => abort.abort(ownership.aborted ? "park" : req.signal?.reason);
|
|
1077
|
+
ownership.addEventListener("abort", onOwnershipLost, { once: true });
|
|
1078
|
+
req.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1079
|
+
if (ownership.aborted)
|
|
1080
|
+
onOwnershipLost();
|
|
1081
|
+
else if (req.signal?.aborted)
|
|
1082
|
+
onCallerAbort();
|
|
1083
|
+
return this.#flights
|
|
1084
|
+
.complete({
|
|
1085
|
+
...req,
|
|
1086
|
+
signal: abort.signal,
|
|
1087
|
+
})
|
|
1088
|
+
.finally(() => {
|
|
1089
|
+
ownership.removeEventListener("abort", onOwnershipLost);
|
|
1090
|
+
req.signal?.removeEventListener("abort", onCallerAbort);
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Open completions honor will not start. `startedAtMs` means the attempt
|
|
1095
|
+
* already began. Tape completions belong to the turn resolution strategy. Only isolated
|
|
1096
|
+
* completions use this driver, so there is one dispatch owner per node.
|
|
1097
|
+
*/
|
|
1098
|
+
#openUnstartedCompletions() {
|
|
1099
|
+
if (!this.activated || this.#closed || !this.canApplyResults || this.sessionDaemon.sessionRuntime.admissionHeld)
|
|
1100
|
+
return [];
|
|
1101
|
+
const open = [];
|
|
1102
|
+
for (const node of this.reachableJobs) {
|
|
1103
|
+
if (!(node instanceof CompletionJob))
|
|
1104
|
+
continue;
|
|
1105
|
+
const pew = node.expectation;
|
|
1106
|
+
if (pew.isDone || pew.startedAtMs != null)
|
|
1107
|
+
continue;
|
|
1108
|
+
const parent = node.parent;
|
|
1109
|
+
if (parent instanceof SessionTurn && (parent.queue.includes(node) || parent.doneWork.includes(node)))
|
|
1110
|
+
continue;
|
|
1111
|
+
open.push(node);
|
|
687
1112
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1113
|
+
return open;
|
|
1114
|
+
}
|
|
1115
|
+
#stopCompletions(reason) {
|
|
1116
|
+
this.#disposeCompletions();
|
|
1117
|
+
this.#flights.abortAll(reason);
|
|
1118
|
+
}
|
|
1119
|
+
async #driveCompletion(req) {
|
|
1120
|
+
if (!this.canApplyResults)
|
|
1121
|
+
throw new SessionOwnershipError();
|
|
1122
|
+
const owner = req.round.parent;
|
|
1123
|
+
if (owner instanceof CompletionJob) {
|
|
1124
|
+
if (owner.orchestrationResult == null) {
|
|
1125
|
+
runInAction(() => {
|
|
1126
|
+
owner.orchestrationResult = true;
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
const intent = await this.#gateIntent(owner, req.signal ?? new AbortController().signal);
|
|
1130
|
+
if (!this.canApplyResults)
|
|
1131
|
+
throw new SessionOwnershipError();
|
|
1132
|
+
if (!intent.dispatch) {
|
|
1133
|
+
if (intent.persistFailed && req.round.outcome == null) {
|
|
1134
|
+
req.round.fail("crash", INTENT_NOT_PERSISTED, "null");
|
|
1135
|
+
}
|
|
1136
|
+
throw new Error(intent.persistFailed ? INTENT_NOT_PERSISTED : "completion cancelled");
|
|
696
1137
|
}
|
|
697
|
-
return;
|
|
698
1138
|
}
|
|
1139
|
+
// The retry budget is read once, here, and recorded on the round. A live setting change
|
|
1140
|
+
// does not reach a round that is already retrying.
|
|
1141
|
+
req.round.stampRetryLimits({
|
|
1142
|
+
maxMs: this.kernel.root.retryMaxMs,
|
|
1143
|
+
maxAttempts: this.kernel.root.retryMaxAttempts,
|
|
1144
|
+
});
|
|
1145
|
+
const started = Date.now();
|
|
1146
|
+
const id = req.round.uuid;
|
|
1147
|
+
trace("harness:complete", `start ${id} connection=${req.connectionId} model=${req.model} isolated=${req.round.completionMode === "isolated"}`);
|
|
1148
|
+
this.originCompletions.add(req.round);
|
|
699
1149
|
try {
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
1150
|
+
const result = await internedComplete(this, req);
|
|
1151
|
+
trace("harness:complete", `done ${id} ms=${Date.now() - started} chars=${result.text.length}`);
|
|
1152
|
+
return result;
|
|
703
1153
|
}
|
|
704
1154
|
catch (error) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
: error instanceof Error
|
|
710
|
-
? error.message
|
|
711
|
-
: String(error),
|
|
712
|
-
};
|
|
1155
|
+
trace("harness:complete", `fail ${id} ms=${Date.now() - started} ${error instanceof Error ? error.message : String(error)}`);
|
|
1156
|
+
if (error instanceof ConnectionMiss)
|
|
1157
|
+
this.originCompletions.delete(req.round);
|
|
1158
|
+
throw error;
|
|
713
1159
|
}
|
|
714
1160
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1161
|
+
/**
|
|
1162
|
+
* A guard or body threw. The job's joiners (the turn's `joinReleased`, a
|
|
1163
|
+
* program's leaf wait) read the receipt, so the model sees the error as
|
|
1164
|
+
* the tool result and the turn continues instead of waiting forever.
|
|
1165
|
+
*/
|
|
1166
|
+
stampCrash(job, phase, detail) {
|
|
1167
|
+
const text = `${phase === "license" ? "license crashed" : "tool crashed"}: ${detail === "" ? "unknown error" : detail}`;
|
|
1168
|
+
trace("harness:crash", `${job.__type__} ${phase} ${detail}`);
|
|
1169
|
+
if (job instanceof ToolCallJob)
|
|
1170
|
+
stampUnentered(job, { ok: false, text });
|
|
1171
|
+
else if (job instanceof ProgrammaticJob)
|
|
1172
|
+
job.outcomeSnap ??= { ok: false, text };
|
|
718
1173
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
return spec.error === "" ? `${key}: failed` : spec.error;
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* A PEW an earlier wrap started settles on its own; it fails here only when
|
|
736
|
-
* its connection is gone. Aborted leaves the PEW to the caller.
|
|
737
|
-
*/
|
|
738
|
-
async #settleLeftoverPew(pew, key, what, signal) {
|
|
739
|
-
const settled = await waitUntil(() => pew.isDone || this.#connectionFailure(key, what) !== null, signal);
|
|
740
|
-
if (settled === "aborted")
|
|
741
|
-
return settled;
|
|
742
|
-
const detail = this.#connectionFailure(key, what);
|
|
743
|
-
if (detail !== null && !pew.isDone)
|
|
744
|
-
pew.fail("crash", detail, "null");
|
|
745
|
-
return settled;
|
|
746
|
-
}
|
|
747
|
-
#sealWebJob(job) {
|
|
748
|
-
if (job.completed || job.cancelled)
|
|
749
|
-
return;
|
|
750
|
-
if ((job.receiptJson != null && job.receiptJson !== "") || job.expectation.isDone) {
|
|
751
|
-
job.markComplete();
|
|
1174
|
+
shouldHonor(node) {
|
|
1175
|
+
if (!this.canApplyResults)
|
|
1176
|
+
return false;
|
|
1177
|
+
if (this.sessionDaemon.sessionRuntime.admissionHeld)
|
|
1178
|
+
return false;
|
|
1179
|
+
if (!super.shouldHonor(node))
|
|
1180
|
+
return false;
|
|
1181
|
+
if (node instanceof McpToolExpectation)
|
|
1182
|
+
return node.startedAtMs != null || node.dispatched;
|
|
1183
|
+
if (node instanceof CompletionExpectation)
|
|
1184
|
+
return node.startedAtMs != null;
|
|
1185
|
+
return true;
|
|
752
1186
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
* Guard phase of a host tool. The session's plugin guards wrap the license:
|
|
756
|
-
* a guard may refuse, answer without running the tool, or narrow the
|
|
757
|
-
* arguments, which are written back before the license judges them.
|
|
758
|
-
* Pre hooks run once the call is licensed.
|
|
759
|
-
*/
|
|
760
|
-
async #guardTool(job) {
|
|
761
|
-
// Origin-aware door. Scheme inner admits and RPC/statusline still enter
|
|
762
|
-
// here as model until those callers pass their origin through this path.
|
|
763
|
-
const result = await guardHostTool({
|
|
764
|
-
job,
|
|
765
|
-
origin: "model",
|
|
766
|
-
agent: this.#behavior.agent,
|
|
767
|
-
behaviorEntries: behaviorEntriesThatRun(behaviorEntriesOf(this.session.root), (wire) => sessionCatalog(this.session.root)?.catalogEntry(wire)),
|
|
768
|
-
guardedCall: (leaf) => this.#guardedCall(leaf),
|
|
769
|
-
license: (leaf) => this.#license(leaf),
|
|
770
|
-
turnId: (hostingTurnOf(this.session.root, job) ?? this.session.root.head)?.uuid ?? "",
|
|
771
|
-
narrowAgent: (leaf, tools) => this.#narrowAgent(leaf, tools),
|
|
772
|
-
onUnentered: stampUnentered,
|
|
773
|
-
onProblem: (what, error) => this.#behavior.problem(what, error),
|
|
774
|
-
onReport: (leaf, outcome) => this.#behavior.toolReport(leaf, outcome),
|
|
775
|
-
});
|
|
776
|
-
if (result.opened != null)
|
|
777
|
-
this.#behavior.holdTool(job, result.opened);
|
|
778
|
-
return result.allowed;
|
|
779
|
-
}
|
|
780
|
-
#guardedCall(job) {
|
|
781
|
-
if (job instanceof BotCallJob) {
|
|
782
|
-
// Named bot calls retain the existing agent guard point, without child tool grants.
|
|
783
|
-
if (job.name !== AGENT_ASK && job.name !== AGENT_SEND)
|
|
784
|
-
return guardedCallOf(job, this.session.root.path);
|
|
785
|
-
const spec = job.botName || job.contributionBotName || job.argString("spec").trim();
|
|
786
|
-
const catalog = sessionCatalog(this.session.root) ?? this.sessionDaemon.kernel.catalog;
|
|
787
|
-
const recipe = catalog.recipe(spec);
|
|
788
|
-
return recipe == null ? null : agentGuardedCall(job, spec, recipe.spec.tools, false);
|
|
1187
|
+
get canApplyResults() {
|
|
1188
|
+
return super.canApplyResults && this.sessionDaemon.sessionRuntime.isCurrent;
|
|
789
1189
|
}
|
|
790
|
-
|
|
791
|
-
return
|
|
792
|
-
if (job.name !== AGENT_ASK && job.name !== AGENT_SEND)
|
|
793
|
-
return null;
|
|
794
|
-
const spec = job.argString("spec").trim();
|
|
795
|
-
const fresh = job.workerSessionId === "";
|
|
796
|
-
const catalog = sessionCatalog(this.session.root) ?? this.sessionDaemon.kernel.catalog;
|
|
797
|
-
if (spec === "") {
|
|
798
|
-
const tools = job.inheritCallerTools ? catalog.catalogWireNames() : job.tools;
|
|
799
|
-
return agentGuardedCall(job, "", tools, fresh);
|
|
1190
|
+
#ownedSignal(signal) {
|
|
1191
|
+
return AbortSignal.any([signal, this.sessionDaemon.sessionRuntime.ownershipSignal]);
|
|
800
1192
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
/** An ad-hoc ask carries its tools on the job; a one-shot recipe worker takes them at mint. */
|
|
805
|
-
#narrowAgent(job, tools) {
|
|
806
|
-
if (job.argString("spec").trim() === "") {
|
|
807
|
-
job.tools = [...tools];
|
|
808
|
-
job.inheritCallerTools = false;
|
|
809
|
-
return;
|
|
1193
|
+
async loadDriverModule(driver) {
|
|
1194
|
+
return this.sessionDaemon.kernel.runtime.loadDriver(driver);
|
|
810
1195
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if (job instanceof AgentCallJob)
|
|
817
|
-
this.#agentTools.delete(job);
|
|
818
|
-
await this.#behavior.finishCall(job, signal);
|
|
819
|
-
}
|
|
820
|
-
async #license(job) {
|
|
821
|
-
const started = Date.now();
|
|
822
|
-
trace("harness:license", `wait ${job.toolCallId || job.name} tool=${job.name} policy=${job.policyResult ?? "pending"}`);
|
|
823
|
-
if (licenseDecisionNow(job) === false) {
|
|
824
|
-
stampLicenseDeny(job, this.session.root.permissionMode);
|
|
825
|
-
await noteDenied(job, this.session.root);
|
|
826
|
-
trace("harness:license", `deny ${job.toolCallId || job.name}`);
|
|
827
|
-
return false;
|
|
1196
|
+
#holdingDispatchedMcp = false;
|
|
1197
|
+
#holdingEval = false;
|
|
1198
|
+
#holdingCallbacks = false;
|
|
1199
|
+
get holdingEval() {
|
|
1200
|
+
return this.#holdingEval;
|
|
828
1201
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (ok || job.cancelled || job.completed)
|
|
833
|
-
return ok;
|
|
834
|
-
const decision = licenseDecisionNow(job);
|
|
835
|
-
if (decision === false)
|
|
836
|
-
stampLicenseDeny(job, this.session.root.permissionMode);
|
|
837
|
-
else if (!this.#closed)
|
|
838
|
-
cancelUnstartedTool(job);
|
|
839
|
-
return false;
|
|
840
|
-
}
|
|
841
|
-
#agentHost() {
|
|
842
|
-
return {
|
|
843
|
-
kernel: this.sessionDaemon.kernel,
|
|
844
|
-
project: this.sessionDaemon.project,
|
|
845
|
-
caller: this.session.root,
|
|
846
|
-
problem: (what, error) => this.#behavior.problem(what, error),
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
#permissionDesk() {
|
|
850
|
-
if (!delegatesPermissionMode(this.session.root))
|
|
851
|
-
return null;
|
|
852
|
-
const id = permissionDeskSessionId(this.session.root, this.kernel.root);
|
|
853
|
-
if (id === "" || id === this.session.root.sessionId)
|
|
854
|
-
return null;
|
|
855
|
-
return this.sessionDaemon.kernel.sessionPlexus(id)?.root ?? null;
|
|
856
|
-
}
|
|
857
|
-
#deskPermissionSnap() {
|
|
858
|
-
const desk = this.#permissionDesk();
|
|
859
|
-
return desk == null ? null : deskPermissionSnap(desk);
|
|
860
|
-
}
|
|
861
|
-
ensure(connectionId) {
|
|
862
|
-
if (connectionId === NOUS_GATEWAY_CONNECTION_ID)
|
|
863
|
-
return this.#ensureBorrowedGateway();
|
|
864
|
-
return this.#loggedEnsure(connectionId, this.isEnsured(connectionId), super.ensure(connectionId));
|
|
865
|
-
}
|
|
866
|
-
/**
|
|
867
|
-
* `web-nous` is one intern id whose person can move. Memo by intern+source;
|
|
868
|
-
* {@link dropConnection} is the remint door so B is not A's lease.
|
|
869
|
-
*/
|
|
870
|
-
#ensureBorrowedGateway() {
|
|
871
|
-
const connectionId = NOUS_GATEWAY_CONNECTION_ID;
|
|
872
|
-
const source = nousGatewaySourceOf(this.kernel.root, connectionId);
|
|
873
|
-
const tracked = this.#gatewayEnsure.has(borrowedEnsureKey(connectionId, source));
|
|
874
|
-
return this.#loggedEnsure(connectionId, tracked, memoBorrowedEnsure(this.#gatewayEnsure, connectionId, source, (id) => this.ensure(id), () => super.ensure(connectionId), () => dropConnection(this.kernel.root, connectionId)));
|
|
875
|
-
}
|
|
876
|
-
#loggedEnsure(connectionId, tracked, pending) {
|
|
877
|
-
if (tracked)
|
|
878
|
-
return pending;
|
|
879
|
-
step(`ensure ${connectionId}`);
|
|
880
|
-
void pending.then(() => step(`ensure ${connectionId} ready`), (error) => fail(`ensure ${connectionId}`, error));
|
|
881
|
-
return pending;
|
|
882
|
-
}
|
|
883
|
-
attachConnection(connectionId, instance) {
|
|
884
|
-
const kernel = this.sessionDaemon.kernel;
|
|
885
|
-
instance.bindSettingsWriter((patch) => {
|
|
886
|
-
kernel.adjustConfig((config) => {
|
|
887
|
-
if (!Object.hasOwn(config.connections, connectionId))
|
|
888
|
-
return;
|
|
889
|
-
const row = config.connections[connectionId];
|
|
890
|
-
row.settings = { ...row.settings, ...patch };
|
|
891
|
-
});
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
connectionSecrets(connectionId) {
|
|
895
|
-
const root = this.sessionDaemon.kernel.root;
|
|
896
|
-
const ownerId = () => secretsConnectionId(this.kernel.root, connectionId);
|
|
897
|
-
return {
|
|
898
|
-
get: (field) => readConnectionSecrets(root, ownerId())[field],
|
|
899
|
-
all: () => readConnectionSecrets(root, ownerId()),
|
|
900
|
-
replace: (secrets) => {
|
|
901
|
-
writeConnectionSecrets(root, ownerId(), { ...secrets });
|
|
902
|
-
},
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Remainder / compact `.prompt` / turn port. Interns the row, then
|
|
907
|
-
* `connection.complete` → `handle(round)`. `KernelDaemon` has no `complete()`.
|
|
908
|
-
*/
|
|
909
|
-
complete(req) {
|
|
910
|
-
return this.#flights.complete(req);
|
|
911
|
-
}
|
|
912
|
-
/**
|
|
913
|
-
* Open completions honor will not start. `startedAtMs` means the attempt
|
|
914
|
-
* already began. Tape completions belong to the turn resolution strategy. Only isolated
|
|
915
|
-
* completions use this driver, so there is one dispatch owner per node.
|
|
916
|
-
*/
|
|
917
|
-
#openUnstartedCompletions() {
|
|
918
|
-
if (!this.activated || this.#closed)
|
|
919
|
-
return [];
|
|
920
|
-
const open = [];
|
|
921
|
-
for (const node of this.reachableJobs) {
|
|
922
|
-
if (!(node instanceof CompletionJob))
|
|
923
|
-
continue;
|
|
924
|
-
const pew = node.expectation;
|
|
925
|
-
if (pew.isDone || pew.startedAtMs != null)
|
|
926
|
-
continue;
|
|
927
|
-
const parent = node.parent;
|
|
928
|
-
if (parent instanceof SessionTurn && (parent.queue.includes(node) || parent.doneWork.includes(node)))
|
|
929
|
-
continue;
|
|
930
|
-
open.push(node);
|
|
1202
|
+
/** Honor still running after last-seat park: on-wire MCP or an unsealed eval bag. */
|
|
1203
|
+
get holding() {
|
|
1204
|
+
return this.#holdingDispatchedMcp || this.#holdingEval || this.#holdingCallbacks;
|
|
931
1205
|
}
|
|
932
|
-
return
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1206
|
+
get #callbackPending() { return _private_get_callbackPending_descriptor.get.call(this); }
|
|
1207
|
+
async #waitEvalAdmission(signal) {
|
|
1208
|
+
const held = this.sessionDaemon.evalAdmissionHeld;
|
|
1209
|
+
const ready = () => !held.get() && !this.sessionDaemon.sessionRuntime.admissionHeld;
|
|
1210
|
+
return ready() || (await waitUntil(ready, signal)) === "settled";
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Pause interpretation. Open Calls and agentCallbacks stay. Abort reason
|
|
1214
|
+
* `"park"` so Call / program / turn handlers do not seal.
|
|
1215
|
+
*
|
|
1216
|
+
* ONE SETTLEMENT. `tools/call` already on the wire is not aborted and not
|
|
1217
|
+
* started again — a second dispatch would hide whether the mutation landed.
|
|
1218
|
+
* Park waits for that settlement. Undispatched MCP is aborted without a
|
|
1219
|
+
* cancel receipt.
|
|
1220
|
+
*
|
|
1221
|
+
* Live eval bags are a wrap hold, not a mutation hold: last-seat drop must
|
|
1222
|
+
* not mint a second honor on the unsealed program. On-wire MCP does not
|
|
1223
|
+
* keep `interpreting` true.
|
|
1224
|
+
*
|
|
1225
|
+
* A held program keeps calling tools, and a tool is licensed and run by the
|
|
1226
|
+
* executors that a stop disposes. So the stop waits for the programs
|
|
1227
|
+
* instead of leaving them parked on a leaf nobody will run: until the last
|
|
1228
|
+
* live program settles this wrap keeps interpreting (and says so), then
|
|
1229
|
+
* parks as usual. The round a park interrupts is never ended: it is the
|
|
1230
|
+
* resumed turn that opens a fresh one.
|
|
1231
|
+
*/
|
|
1232
|
+
park() {
|
|
1233
|
+
if (this.#closed)
|
|
1234
|
+
return Promise.resolve();
|
|
1235
|
+
if (liveEvalWork(this.session.root).size === 0 && !this.#callbackPending)
|
|
1236
|
+
return this.#parkNow();
|
|
1237
|
+
this.#deferredPark ??= (async () => {
|
|
1238
|
+
this.#holdingEval = liveEvalWork(this.session.root).size > 0;
|
|
1239
|
+
this.#holdingCallbacks = this.#callbackPending;
|
|
1240
|
+
try {
|
|
1241
|
+
await waitUntil(() => this.#closed || (liveEvalWork(this.session.root).size === 0 && !this.#callbackPending), this.sessionDaemon.sessionRuntime.ownershipSignal);
|
|
1242
|
+
}
|
|
1243
|
+
finally {
|
|
1244
|
+
this.#holdingEval = false;
|
|
1245
|
+
this.#holdingCallbacks = false;
|
|
950
1246
|
}
|
|
951
|
-
|
|
1247
|
+
await this.#parkNow();
|
|
1248
|
+
})();
|
|
1249
|
+
return this.#deferredPark;
|
|
1250
|
+
}
|
|
1251
|
+
#deferredPark = null;
|
|
1252
|
+
#parkNow() {
|
|
1253
|
+
if (!this.#stop("park"))
|
|
1254
|
+
return Promise.resolve();
|
|
1255
|
+
if (!this.canApplyResults) {
|
|
1256
|
+
this.#release();
|
|
1257
|
+
return Promise.resolve();
|
|
1258
|
+
}
|
|
1259
|
+
const hold = this.#abortParkableWork();
|
|
1260
|
+
if (!hold.mcpPending && !hold.evalPending) {
|
|
1261
|
+
this.#abortParkableWork();
|
|
1262
|
+
this.#release();
|
|
1263
|
+
return Promise.resolve();
|
|
952
1264
|
}
|
|
1265
|
+
this.#holdingDispatchedMcp = hold.mcpPending;
|
|
1266
|
+
this.#holdingEval = hold.evalPending;
|
|
1267
|
+
return when(() => dispatchedMcpSettled(this.session.root) && liveEvalWork(this.session.root).size === 0).then(() => {
|
|
1268
|
+
this.#holdingDispatchedMcp = false;
|
|
1269
|
+
this.#holdingEval = false;
|
|
1270
|
+
this.#abortParkableWork();
|
|
1271
|
+
this.#release();
|
|
1272
|
+
return undefined;
|
|
1273
|
+
});
|
|
953
1274
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1275
|
+
/** Close the wrap to new work. Park and dispose share it; false once closed. */
|
|
1276
|
+
#stop(cause) {
|
|
1277
|
+
if (this.#closed)
|
|
1278
|
+
return false;
|
|
1279
|
+
this.#closed = true;
|
|
1280
|
+
this.#disposeLive();
|
|
1281
|
+
this.#disposeConnections();
|
|
1282
|
+
this.#disposeBoundWire();
|
|
1283
|
+
this.#disposePromptQueue();
|
|
1284
|
+
this.#disposeAutoAllow();
|
|
1285
|
+
this.#disposeDesk();
|
|
1286
|
+
// A park pauses the round it interrupts; only a dispose ends it.
|
|
1287
|
+
this.#stopCompletions(cause);
|
|
1288
|
+
this.stopAdmission();
|
|
1289
|
+
this.#behavior.stopAdmission();
|
|
1290
|
+
this.#closeScope();
|
|
1291
|
+
return true;
|
|
968
1292
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
throw error;
|
|
1293
|
+
/** Release what the wrap holds, once its last held work settled. */
|
|
1294
|
+
#release() {
|
|
1295
|
+
this.#behavior[Symbol.dispose]();
|
|
1296
|
+
super[Symbol.dispose]();
|
|
974
1297
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
return node.startedAtMs != null || node.dispatched;
|
|
994
|
-
if (node instanceof CompletionExpectation)
|
|
995
|
-
return node.startedAtMs != null;
|
|
996
|
-
return true;
|
|
997
|
-
}
|
|
998
|
-
async loadDriverModule(driver) {
|
|
999
|
-
return this.sessionDaemon.kernel.runtime.loadDriver(driver);
|
|
1000
|
-
}
|
|
1001
|
-
#holdingDispatchedMcp = false;
|
|
1002
|
-
#holdingEval = false;
|
|
1003
|
-
get holdingEval() {
|
|
1004
|
-
return this.#holdingEval;
|
|
1005
|
-
}
|
|
1006
|
-
/** Honor still running after last-seat park: on-wire MCP or an unsealed eval bag. */
|
|
1007
|
-
get holding() {
|
|
1008
|
-
return this.#holdingDispatchedMcp || this.#holdingEval;
|
|
1009
|
-
}
|
|
1010
|
-
async #waitEvalAdmission(signal) {
|
|
1011
|
-
const held = this.sessionDaemon.evalAdmissionHeld;
|
|
1012
|
-
return !held.get() || (await waitUntil(() => !held.get(), signal)) === "settled";
|
|
1013
|
-
}
|
|
1014
|
-
/**
|
|
1015
|
-
* Pause interpretation. Open Calls and agentCallbacks stay. Abort reason
|
|
1016
|
-
* `"park"` so Call / program / turn handlers do not seal.
|
|
1017
|
-
*
|
|
1018
|
-
* ONE SETTLEMENT. `tools/call` already on the wire is not aborted and not
|
|
1019
|
-
* started again — a second dispatch would hide whether the mutation landed.
|
|
1020
|
-
* Park waits for that settlement. Undispatched MCP is aborted without a
|
|
1021
|
-
* cancel receipt.
|
|
1022
|
-
*
|
|
1023
|
-
* Live eval bags are a wrap hold, not a mutation hold: last-seat drop must
|
|
1024
|
-
* not mint a second honor on the unsealed program. On-wire MCP does not
|
|
1025
|
-
* keep `interpreting` true.
|
|
1026
|
-
*
|
|
1027
|
-
* A held program keeps calling tools, and a tool is licensed and run by the
|
|
1028
|
-
* executors that a stop disposes. So the stop waits for the programs
|
|
1029
|
-
* instead of leaving them parked on a leaf nobody will run: until the last
|
|
1030
|
-
* live program settles this wrap keeps interpreting (and says so), then
|
|
1031
|
-
* parks as usual. The round a park interrupts is never ended: it is the
|
|
1032
|
-
* resumed turn that opens a fresh one.
|
|
1033
|
-
*/
|
|
1034
|
-
park() {
|
|
1035
|
-
if (this.#closed)
|
|
1036
|
-
return Promise.resolve();
|
|
1037
|
-
if (liveEvalWork(this.session.root).size === 0)
|
|
1038
|
-
return this.#parkNow();
|
|
1039
|
-
this.#deferredPark ??= (async () => {
|
|
1040
|
-
this.#holdingEval = true;
|
|
1041
|
-
try {
|
|
1042
|
-
await when(() => this.#closed || liveEvalWork(this.session.root).size === 0);
|
|
1043
|
-
}
|
|
1044
|
-
finally {
|
|
1045
|
-
this.#holdingEval = false;
|
|
1298
|
+
#abortParkableWork() {
|
|
1299
|
+
return abortParkableWork(this.session.root, {
|
|
1300
|
+
inFlightJobs: () => this.inFlightJobs(),
|
|
1301
|
+
abortJob: (job, reason) => this.abortJob(job, reason),
|
|
1302
|
+
abortExpectation: (expectation, reason) => {
|
|
1303
|
+
this.actorOf(expectation)?.abort(reason);
|
|
1304
|
+
},
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
[(_private_get_callbackPending_decorators = [computed], Symbol.dispose)]() {
|
|
1308
|
+
if (!this.#stop())
|
|
1309
|
+
return;
|
|
1310
|
+
if (this.canApplyResults) {
|
|
1311
|
+
for (const job of this.inFlightJobs()) {
|
|
1312
|
+
if (job instanceof SessionTurn) {
|
|
1313
|
+
failLeftoverApprovables(job, (leaf) => this.#abortLeaf(leaf), this.sessionDaemon.kernel.catalog);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1046
1316
|
}
|
|
1047
|
-
await this.#parkNow();
|
|
1048
|
-
})();
|
|
1049
|
-
return this.#deferredPark;
|
|
1050
|
-
}
|
|
1051
|
-
#deferredPark = null;
|
|
1052
|
-
#parkNow() {
|
|
1053
|
-
if (!this.#stop("park"))
|
|
1054
|
-
return Promise.resolve();
|
|
1055
|
-
const hold = this.#abortParkableWork();
|
|
1056
|
-
if (!hold.mcpPending && !hold.evalPending) {
|
|
1057
|
-
this.#abortParkableWork();
|
|
1058
1317
|
this.#release();
|
|
1059
|
-
return Promise.resolve();
|
|
1060
1318
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1319
|
+
#compactHost(turn, session, connectionId, model) {
|
|
1320
|
+
return {
|
|
1321
|
+
turn,
|
|
1322
|
+
session,
|
|
1323
|
+
compactKeepTurns: this.kernel.root.compactKeepTurns,
|
|
1324
|
+
compactStrategy: this.kernel.root.compactStrategy,
|
|
1325
|
+
kernelFsRoot: this.sessionDaemon.kernel.root,
|
|
1326
|
+
connectionId,
|
|
1327
|
+
model,
|
|
1328
|
+
complete: (req) => this.complete(req),
|
|
1329
|
+
loadProgram: (spec, root) => loadCompactProgram(spec, root),
|
|
1330
|
+
checkpoint: () => this.sessionDaemon.sessionRuntime.flush().then(() => undefined),
|
|
1331
|
+
log: (message) => step(message),
|
|
1332
|
+
fail: (label, error) => fail(label, error),
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
cancelOwned(turn) {
|
|
1336
|
+
const catalog = this.sessionDaemon.kernel.catalog;
|
|
1337
|
+
turn.cancelRequested = true;
|
|
1338
|
+
for (const node of walkWork(turn)) {
|
|
1339
|
+
if (node instanceof CallJob && !node.completed) {
|
|
1340
|
+
if (shouldSkipTurnCancel(node, catalog))
|
|
1341
|
+
continue;
|
|
1342
|
+
completeCallExceptionally(node, this.#agentHost(), "cancelled");
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
failLeftoverApprovables(turn, (leaf) => this.#abortLeaf(leaf), catalog);
|
|
1346
|
+
for (const node of walkWork(turn)) {
|
|
1347
|
+
if (node instanceof ProgrammaticJob && !node.completed) {
|
|
1348
|
+
if (node.orchestrated)
|
|
1349
|
+
this.abortJob(node);
|
|
1350
|
+
else {
|
|
1351
|
+
node.outcomeSnap ??= { ok: false, text: "cancelled" };
|
|
1352
|
+
node.cancelled = true;
|
|
1353
|
+
node.markComplete();
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (node instanceof WorkExpectation && !node.isDone)
|
|
1357
|
+
this.#terminatePew(node);
|
|
1358
|
+
}
|
|
1359
|
+
let inFlight = false;
|
|
1360
|
+
for (const job of this.inFlightJobs()) {
|
|
1361
|
+
if (job === turn) {
|
|
1362
|
+
inFlight = true;
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
this.abortJob(turn);
|
|
1367
|
+
if (!inFlight) {
|
|
1368
|
+
// The executor is not inside this turn, so its finally will not clear `inLoop`.
|
|
1369
|
+
closeTurn(turn, { kind: "cancel" }, this.#closeOptions());
|
|
1108
1370
|
}
|
|
1109
1371
|
}
|
|
1110
|
-
this
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1372
|
+
/** What every turn close needs from this wrap: the abort of an in-flight body, the actor-aware end, the inbox. */
|
|
1373
|
+
#closeOptions() {
|
|
1374
|
+
return {
|
|
1375
|
+
catalog: this.sessionDaemon.kernel.catalog,
|
|
1376
|
+
abort: (job) => (job instanceof ApprovableJob ? this.#abortLeaf(job) : this.abortJob(job)),
|
|
1377
|
+
terminate: (expectation) => this.#terminatePew(expectation),
|
|
1378
|
+
releaseCall: (call) => {
|
|
1379
|
+
if (!call.completed)
|
|
1380
|
+
completeCallExceptionally(call, this.#agentHost(), "cancelled");
|
|
1381
|
+
},
|
|
1382
|
+
session: this.session.root,
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Release the caller's open Call when a worker turn closes without answering:
|
|
1387
|
+
* on any non-cancelled completion failure, turn error, or one-shot worker loop exit.
|
|
1388
|
+
* The turn is already closed, so force-stop does not abort this executor.
|
|
1389
|
+
*/
|
|
1390
|
+
#releaseUnansweredCall(turn) {
|
|
1391
|
+
const requestId = turn.agentRequestId.trim();
|
|
1392
|
+
if (requestId === "")
|
|
1393
|
+
return;
|
|
1394
|
+
const call = loadOpenCall(this.sessionDaemon.kernel, this.session.root, requestId);
|
|
1395
|
+
if (call == null || call.completed)
|
|
1396
|
+
return;
|
|
1397
|
+
let detail = "";
|
|
1398
|
+
let hasCompletion = false;
|
|
1399
|
+
let hasOpenChildCall = false;
|
|
1400
|
+
let stopDetail = "";
|
|
1401
|
+
for (const node of walkWork(turn)) {
|
|
1402
|
+
if (node instanceof CallJob && !node.completed) {
|
|
1403
|
+
hasOpenChildCall = true;
|
|
1404
|
+
}
|
|
1405
|
+
if (node instanceof ReACTChunk && node.decision?.kind === "blocked") {
|
|
1406
|
+
const reason = node.decision.detail?.trim() || "";
|
|
1407
|
+
if (reason !== "")
|
|
1408
|
+
stopDetail = reason;
|
|
1409
|
+
}
|
|
1410
|
+
const pew = node instanceof CompletionExpectation ? node : tapeCompletion(node);
|
|
1411
|
+
if (pew == null || pew.startedAtMs == null)
|
|
1133
1412
|
continue;
|
|
1134
|
-
|
|
1413
|
+
hasCompletion = true;
|
|
1414
|
+
if (pew.outcome !== "failed" || pew.endCause === "cancel")
|
|
1415
|
+
continue;
|
|
1416
|
+
const text = pew.endDetail?.trim() || pew.failReason?.trim() || "model round failed";
|
|
1417
|
+
detail = text;
|
|
1418
|
+
break;
|
|
1135
1419
|
}
|
|
1420
|
+
if (detail === "" &&
|
|
1421
|
+
hasCompletion &&
|
|
1422
|
+
!hasOpenChildCall &&
|
|
1423
|
+
!this.session.root.agent.spec.keepAlive &&
|
|
1424
|
+
this.session.root.agent.spec instanceof AskAgentSpec) {
|
|
1425
|
+
detail = turn.broke.trim() || stopDetail || "worker turn ended without calling agent__respond";
|
|
1426
|
+
}
|
|
1427
|
+
if (detail === "")
|
|
1428
|
+
return;
|
|
1429
|
+
completeCallExceptionally(call, this.#agentHost(), "error", detail);
|
|
1136
1430
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1431
|
+
/**
|
|
1432
|
+
* In-flight BODY abort. `abortJob` does not stop a worker session — AgentCall
|
|
1433
|
+
* must complete exceptionally. MCP/web terminate PEW.
|
|
1434
|
+
*/
|
|
1435
|
+
#abortLeaf(leaf, reason) {
|
|
1436
|
+
this.abortJob(leaf, reason);
|
|
1437
|
+
if (leaf instanceof CallJob) {
|
|
1438
|
+
if (!leaf.completed) {
|
|
1439
|
+
completeCallExceptionally(leaf, this.#agentHost(), "cancelled", cancelOutcomeText(reason));
|
|
1146
1440
|
}
|
|
1441
|
+
return;
|
|
1147
1442
|
}
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
if (job === turn) {
|
|
1154
|
-
inFlight = true;
|
|
1155
|
-
break;
|
|
1443
|
+
if (leaf instanceof MCPToolJob || leaf instanceof WebSearchToolJob || leaf instanceof WebExtractToolJob) {
|
|
1444
|
+
leaf.outcomeSnap ??= { ok: false, text: cancelOutcomeText(reason) };
|
|
1445
|
+
const pew = leaf.expectation;
|
|
1446
|
+
if (!pew.isDone)
|
|
1447
|
+
this.#terminatePew(pew);
|
|
1156
1448
|
}
|
|
1157
1449
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1450
|
+
#abortWork(job) {
|
|
1451
|
+
if (job instanceof ApprovableJob)
|
|
1452
|
+
this.#abortLeaf(job);
|
|
1453
|
+
else
|
|
1454
|
+
this.abortJob(job);
|
|
1162
1455
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
terminate: (expectation) => this.#terminatePew(expectation),
|
|
1170
|
-
releaseCall: (call) => {
|
|
1171
|
-
if (!call.completed)
|
|
1172
|
-
completeCallExceptionally(call, this.#agentHost(), "cancelled");
|
|
1173
|
-
},
|
|
1174
|
-
session: this.session.root,
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
/**
|
|
1178
|
-
* Stream stall failed this turn's completion. A keepAlive worker can still
|
|
1179
|
-
* respond after any other close, and eval timeout cancels the call itself.
|
|
1180
|
-
* The turn is already closed, so force-stop does not abort this executor.
|
|
1181
|
-
*/
|
|
1182
|
-
#releaseUnansweredCall(turn) {
|
|
1183
|
-
const requestId = turn.agentRequestId.trim();
|
|
1184
|
-
if (requestId === "")
|
|
1185
|
-
return;
|
|
1186
|
-
const call = loadOpenCall(this.sessionDaemon.kernel, this.session.root, requestId);
|
|
1187
|
-
if (call == null || call.completed)
|
|
1188
|
-
return;
|
|
1189
|
-
let detail = "";
|
|
1190
|
-
for (const node of walkWork(turn)) {
|
|
1191
|
-
const pew = node instanceof CompletionExpectation ? node : tapeCompletion(node);
|
|
1192
|
-
if (pew == null)
|
|
1193
|
-
continue;
|
|
1194
|
-
if (pew.outcome !== "failed")
|
|
1195
|
-
continue;
|
|
1196
|
-
const text = pew.endDetail?.trim() || pew.failReason?.trim() || "";
|
|
1197
|
-
if (!text.startsWith("stream stalled after "))
|
|
1198
|
-
continue;
|
|
1199
|
-
detail = text;
|
|
1200
|
-
break;
|
|
1456
|
+
#terminatePew(expectation) {
|
|
1457
|
+
const actor = this.actorOf(expectation);
|
|
1458
|
+
if (actor !== undefined)
|
|
1459
|
+
actor.admitTermination();
|
|
1460
|
+
if (!expectation.isDone)
|
|
1461
|
+
expectation.fail("cancel", "terminate", "null");
|
|
1201
1462
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1463
|
+
#configuredModel(connectionId) {
|
|
1464
|
+
const spec = this.kernel.root.connections[connectionId];
|
|
1465
|
+
if (spec == null)
|
|
1466
|
+
return "";
|
|
1467
|
+
const configured = typeof spec.configuration.model === "string" ? spec.configuration.model.trim() : "";
|
|
1468
|
+
if (configured !== "")
|
|
1469
|
+
return configured;
|
|
1470
|
+
const activeConnection = (this.session.root.kvs.connection || this.kernel.root.kvs.connection || "").trim();
|
|
1471
|
+
if (connectionId === activeConnection) {
|
|
1472
|
+
const activeModel = (this.session.root.kvs.model || this.kernel.root.kvs.model || "").trim();
|
|
1473
|
+
if (activeModel !== "")
|
|
1474
|
+
return activeModel;
|
|
1475
|
+
}
|
|
1476
|
+
const liveModels = spec.live?.models;
|
|
1477
|
+
if (Array.isArray(liveModels)) {
|
|
1478
|
+
const first = liveModels.find((id) => typeof id === "string" && id.trim() !== "");
|
|
1479
|
+
if (typeof first === "string")
|
|
1480
|
+
return first.trim();
|
|
1215
1481
|
}
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
if (leaf instanceof MCPToolJob || leaf instanceof WebSearchToolJob || leaf instanceof WebExtractToolJob) {
|
|
1219
|
-
leaf.outcomeSnap ??= { ok: false, text: cancelOutcomeText(reason) };
|
|
1220
|
-
const pew = leaf.expectation;
|
|
1221
|
-
if (!pew.isDone)
|
|
1222
|
-
this.#terminatePew(pew);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
#abortWork(job) {
|
|
1226
|
-
if (job instanceof ApprovableJob)
|
|
1227
|
-
this.#abortLeaf(job);
|
|
1228
|
-
else
|
|
1229
|
-
this.abortJob(job);
|
|
1230
|
-
}
|
|
1231
|
-
#terminatePew(expectation) {
|
|
1232
|
-
const actor = this.actorOf(expectation);
|
|
1233
|
-
if (actor !== undefined)
|
|
1234
|
-
actor.admitTermination();
|
|
1235
|
-
if (!expectation.isDone)
|
|
1236
|
-
expectation.fail("cancel", "terminate", "null");
|
|
1237
|
-
}
|
|
1238
|
-
#configuredModel(connectionId) {
|
|
1239
|
-
const spec = this.kernel.root.connections[connectionId];
|
|
1240
|
-
if (spec == null)
|
|
1241
1482
|
return "";
|
|
1242
|
-
const configured = typeof spec.configuration.model === "string" ? spec.configuration.model.trim() : "";
|
|
1243
|
-
if (configured !== "")
|
|
1244
|
-
return configured;
|
|
1245
|
-
const activeConnection = (this.session.root.kvs.connection || this.kernel.root.kvs.connection || "").trim();
|
|
1246
|
-
if (connectionId === activeConnection) {
|
|
1247
|
-
const activeModel = (this.session.root.kvs.model || this.kernel.root.kvs.model || "").trim();
|
|
1248
|
-
if (activeModel !== "")
|
|
1249
|
-
return activeModel;
|
|
1250
1483
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1484
|
+
async #reActLoopIntentDetectedTrigger(turn, pew, signal) {
|
|
1485
|
+
if (!reActLoopIntentDetectedTriggerFor(this.kernel.root.reActLoopIntentDetectedTrigger, pew.connectionId, pew.model))
|
|
1486
|
+
return { verdict: null, status: "disabled", detail: "Continuation judge is disabled." };
|
|
1487
|
+
const wire = internJudgeWire(this.session.root.kvs, this.kernel.root.kvs, (id) => this.#configuredModel(id));
|
|
1488
|
+
if (wire == null)
|
|
1489
|
+
return { verdict: null, status: "unconfigured", detail: "No continuation judge is configured." };
|
|
1490
|
+
return assessTextOnlyContinueIntern({
|
|
1491
|
+
speech: pew.responseText,
|
|
1492
|
+
wire,
|
|
1493
|
+
haltKeys: this.#reActLoopIntentDetectedTriggerHaltKeys,
|
|
1494
|
+
home: turn,
|
|
1495
|
+
complete: (req) => this.complete(req),
|
|
1496
|
+
signal,
|
|
1497
|
+
});
|
|
1256
1498
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (internContinueStreak(turn) >= RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_STREAK_MAX)
|
|
1261
|
-
return "seal";
|
|
1262
|
-
if (!reActLoopIntentDetectedTriggerFor(this.kernel.root.reActLoopIntentDetectedTrigger, pew.connectionId, pew.model))
|
|
1263
|
-
return "seal";
|
|
1264
|
-
const wire = internJudgeWire(this.session.root.kvs, this.kernel.root.kvs, (id) => this.#configuredModel(id));
|
|
1265
|
-
if (wire == null)
|
|
1266
|
-
return "seal";
|
|
1267
|
-
return runTextOnlyContinueIntern({
|
|
1268
|
-
speech: pew.responseText,
|
|
1269
|
-
wire,
|
|
1270
|
-
haltKeys: this.#reActLoopIntentDetectedTriggerHaltKeys,
|
|
1271
|
-
home: turn,
|
|
1272
|
-
complete: (req) => this.complete(req),
|
|
1273
|
-
signal,
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1499
|
+
};
|
|
1500
|
+
})();
|
|
1501
|
+
export { SessionOrchestrator };
|
|
1277
1502
|
function completionRequest(pew) {
|
|
1278
1503
|
return {
|
|
1279
1504
|
connectionId: pew.connectionId,
|