visionclaw 0.1.219-dev.codex-issue-2805-worker-runtime.3 → 0.1.220-dev.codex-fix-2941-activity-foreground.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/api-proxy.d.ts +16 -0
- package/dist/agent/api-proxy.d.ts.map +1 -1
- package/dist/agent/api-proxy.js +8 -9
- package/dist/agent/api-proxy.js.map +1 -1
- package/dist/agent/browser-launcher.d.ts +0 -3
- package/dist/agent/browser-launcher.d.ts.map +1 -1
- package/dist/agent/browser-launcher.js +43 -101
- package/dist/agent/browser-launcher.js.map +1 -1
- package/dist/agent/coding-conventions.d.ts +3 -2
- package/dist/agent/coding-conventions.d.ts.map +1 -1
- package/dist/agent/coding-conventions.js +8 -7
- package/dist/agent/coding-conventions.js.map +1 -1
- package/dist/agent/command-handlers.d.ts.map +1 -1
- package/dist/agent/command-handlers.js +16 -4
- package/dist/agent/command-handlers.js.map +1 -1
- package/dist/agent/context-action-restart-fallback.d.ts.map +1 -1
- package/dist/agent/context-action-restart-fallback.js +3 -9
- package/dist/agent/context-action-restart-fallback.js.map +1 -1
- package/dist/agent/context-refresh.d.ts.map +1 -1
- package/dist/agent/context-refresh.js +7 -0
- package/dist/agent/context-refresh.js.map +1 -1
- package/dist/agent/context.d.ts.map +1 -1
- package/dist/agent/context.js +8 -6
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/engine-fallback.d.ts +35 -0
- package/dist/agent/engine-fallback.d.ts.map +1 -1
- package/dist/agent/engine-fallback.js +3 -4
- package/dist/agent/engine-fallback.js.map +1 -1
- package/dist/agent/engines/browser-tool-policy.d.ts +1 -12
- package/dist/agent/engines/browser-tool-policy.d.ts.map +1 -1
- package/dist/agent/engines/browser-tool-policy.js +2 -17
- package/dist/agent/engines/browser-tool-policy.js.map +1 -1
- package/dist/agent/engines/claude/session.d.ts +0 -1
- package/dist/agent/engines/claude/session.d.ts.map +1 -1
- package/dist/agent/engines/claude/session.js +36 -21
- package/dist/agent/engines/claude/session.js.map +1 -1
- package/dist/agent/engines/pi/tools.d.ts.map +1 -1
- package/dist/agent/engines/pi/tools.js +10 -2
- package/dist/agent/engines/pi/tools.js.map +1 -1
- package/dist/agent/engines/pi-coding/session.d.ts +0 -3
- package/dist/agent/engines/pi-coding/session.d.ts.map +1 -1
- package/dist/agent/engines/pi-coding/session.js +22 -19
- package/dist/agent/engines/pi-coding/session.js.map +1 -1
- package/dist/agent/engines/session-types.d.ts +0 -6
- package/dist/agent/engines/session-types.d.ts.map +1 -1
- package/dist/agent/external-context/runtime.d.ts.map +1 -1
- package/dist/agent/external-context/runtime.js +14 -15
- package/dist/agent/external-context/runtime.js.map +1 -1
- package/dist/agent/fast-responder.d.ts +20 -4
- package/dist/agent/fast-responder.d.ts.map +1 -1
- package/dist/agent/fast-responder.js +39 -9
- package/dist/agent/fast-responder.js.map +1 -1
- package/dist/agent/interrupt-handler.d.ts +18 -4
- package/dist/agent/interrupt-handler.d.ts.map +1 -1
- package/dist/agent/interrupt-handler.js +284 -49
- package/dist/agent/interrupt-handler.js.map +1 -1
- package/dist/agent/lab-run-system-prompt.d.ts.map +1 -1
- package/dist/agent/lab-run-system-prompt.js +0 -3
- package/dist/agent/lab-run-system-prompt.js.map +1 -1
- package/dist/agent/lifecycle-trace.d.ts.map +1 -1
- package/dist/agent/lifecycle-trace.js +5 -6
- package/dist/agent/lifecycle-trace.js.map +1 -1
- package/dist/agent/loop.d.ts +1 -4
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +129 -105
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory-protocol.d.ts +2 -2
- package/dist/agent/memory-protocol.d.ts.map +1 -1
- package/dist/agent/memory-protocol.js +15 -3
- package/dist/agent/memory-protocol.js.map +1 -1
- package/dist/agent/memory-store.d.ts.map +1 -1
- package/dist/agent/memory-store.js +1 -11
- package/dist/agent/memory-store.js.map +1 -1
- package/dist/agent/model-provider.d.ts +0 -2
- package/dist/agent/model-provider.d.ts.map +1 -1
- package/dist/agent/model-provider.js +25 -31
- package/dist/agent/model-provider.js.map +1 -1
- package/dist/agent/product-identity.d.ts +4 -5
- package/dist/agent/product-identity.d.ts.map +1 -1
- package/dist/agent/product-identity.js +6 -7
- package/dist/agent/product-identity.js.map +1 -1
- package/dist/agent/public-activity.d.ts +1 -1
- package/dist/agent/public-activity.d.ts.map +1 -1
- package/dist/agent/public-activity.js.map +1 -1
- package/dist/agent/reply-watchdog.d.ts +3 -5
- package/dist/agent/reply-watchdog.d.ts.map +1 -1
- package/dist/agent/reply-watchdog.js +4 -6
- package/dist/agent/reply-watchdog.js.map +1 -1
- package/dist/agent/session-manager.d.ts +0 -5
- package/dist/agent/session-manager.d.ts.map +1 -1
- package/dist/agent/session-manager.js +0 -7
- package/dist/agent/session-manager.js.map +1 -1
- package/dist/agent/state.d.ts +0 -7
- package/dist/agent/state.d.ts.map +1 -1
- package/dist/agent/state.js +11 -21
- package/dist/agent/state.js.map +1 -1
- package/dist/agent/stream-handler.d.ts.map +1 -1
- package/dist/agent/stream-handler.js +17 -0
- package/dist/agent/stream-handler.js.map +1 -1
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +6 -10
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/transcript/transcript-indexer.d.ts +28 -0
- package/dist/agent/transcript/transcript-indexer.d.ts.map +1 -1
- package/dist/agent/transcript/transcript-indexer.js +57 -58
- package/dist/agent/transcript/transcript-indexer.js.map +1 -1
- package/dist/agent/usage/usage-report-queue.d.ts.map +1 -1
- package/dist/agent/usage/usage-report-queue.js +52 -53
- package/dist/agent/usage/usage-report-queue.js.map +1 -1
- package/dist/agent/wake-cycle-runner.d.ts +8 -7
- package/dist/agent/wake-cycle-runner.d.ts.map +1 -1
- package/dist/agent/wake-cycle-runner.js +66 -105
- package/dist/agent/wake-cycle-runner.js.map +1 -1
- package/dist/builtin-skills/owner-manual/SKILL.md +2 -2
- package/dist/calendar/occurrence-ownership.d.ts.map +1 -1
- package/dist/calendar/occurrence-ownership.js +5 -6
- package/dist/calendar/occurrence-ownership.js.map +1 -1
- package/dist/calendar/scheduler.d.ts.map +1 -1
- package/dist/calendar/scheduler.js +3 -4
- package/dist/calendar/scheduler.js.map +1 -1
- package/dist/channels/interface.d.ts +2 -8
- package/dist/channels/interface.d.ts.map +1 -1
- package/dist/channels/interface.js +1 -8
- package/dist/channels/interface.js.map +1 -1
- package/dist/channels/manager.d.ts.map +1 -1
- package/dist/channels/manager.js +0 -2
- package/dist/channels/manager.js.map +1 -1
- package/dist/channels/qq-status.d.ts.map +1 -1
- package/dist/channels/qq-status.js +1 -0
- package/dist/channels/qq-status.js.map +1 -1
- package/dist/channels/queue.d.ts.map +1 -1
- package/dist/channels/queue.js +0 -8
- package/dist/channels/queue.js.map +1 -1
- package/dist/channels/telegram-status.d.ts.map +1 -1
- package/dist/channels/telegram-status.js +2 -0
- package/dist/channels/telegram-status.js.map +1 -1
- package/dist/channels/tui.d.ts +2 -2
- package/dist/channels/tui.d.ts.map +1 -1
- package/dist/channels/tui.js +2 -7
- package/dist/channels/tui.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +15 -16
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +8 -16
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +24 -13
- package/dist/config/types.js.map +1 -1
- package/dist/email/email-route-store.d.ts.map +1 -1
- package/dist/email/email-route-store.js +5 -6
- package/dist/email/email-route-store.js.map +1 -1
- package/dist/i18n/messages.d.ts +0 -12
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/messages.js +9 -21
- package/dist/i18n/messages.js.map +1 -1
- package/dist/logger.d.ts +0 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -19
- package/dist/logger.js.map +1 -1
- package/dist/threads/active-threads-store.d.ts.map +1 -1
- package/dist/threads/active-threads-store.js +27 -8
- package/dist/threads/active-threads-store.js.map +1 -1
- package/dist/threads/execution-host.d.ts.map +1 -1
- package/dist/threads/execution-host.js +0 -1
- package/dist/threads/execution-host.js.map +1 -1
- package/dist/threads/execution-scheduler.d.ts +0 -4
- package/dist/threads/execution-scheduler.d.ts.map +1 -1
- package/dist/threads/execution-scheduler.js +2 -38
- package/dist/threads/execution-scheduler.js.map +1 -1
- package/dist/threads/execution-store.d.ts +5 -20
- package/dist/threads/execution-store.d.ts.map +1 -1
- package/dist/threads/execution-store.js +8 -45
- package/dist/threads/execution-store.js.map +1 -1
- package/dist/threads/foreground-task.d.ts +6 -10
- package/dist/threads/foreground-task.d.ts.map +1 -1
- package/dist/threads/foreground-task.js +26 -34
- package/dist/threads/foreground-task.js.map +1 -1
- package/dist/threads/foreman-client.d.ts.map +1 -1
- package/dist/threads/foreman-client.js +1 -3
- package/dist/threads/foreman-client.js.map +1 -1
- package/dist/threads/foreman-execution.d.ts +12 -12
- package/dist/threads/foreman-execution.d.ts.map +1 -1
- package/dist/threads/foreman-execution.js +19 -30
- package/dist/threads/foreman-execution.js.map +1 -1
- package/dist/threads/foreman-host.d.ts +2 -17
- package/dist/threads/foreman-host.d.ts.map +1 -1
- package/dist/threads/foreman-host.js +63 -452
- package/dist/threads/foreman-host.js.map +1 -1
- package/dist/threads/foreman-protocol.d.ts +12 -113
- package/dist/threads/foreman-protocol.d.ts.map +1 -1
- package/dist/threads/foreman-protocol.js +15 -41
- package/dist/threads/foreman-protocol.js.map +1 -1
- package/dist/threads/foreman-store.d.ts +10 -44
- package/dist/threads/foreman-store.d.ts.map +1 -1
- package/dist/threads/foreman-store.js +19 -78
- package/dist/threads/foreman-store.js.map +1 -1
- package/dist/threads/generic-execution.d.ts +2 -2
- package/dist/threads/generic-execution.d.ts.map +1 -1
- package/dist/threads/generic-execution.js +6 -9
- package/dist/threads/generic-execution.js.map +1 -1
- package/dist/threads/ipc.d.ts +0 -3
- package/dist/threads/ipc.d.ts.map +1 -1
- package/dist/threads/ipc.js.map +1 -1
- package/dist/threads/relay-adapter.d.ts +2 -0
- package/dist/threads/relay-adapter.d.ts.map +1 -1
- package/dist/threads/relay-adapter.js +7 -1
- package/dist/threads/relay-adapter.js.map +1 -1
- package/dist/threads/supervisor.d.ts +51 -21
- package/dist/threads/supervisor.d.ts.map +1 -1
- package/dist/threads/supervisor.js +449 -160
- package/dist/threads/supervisor.js.map +1 -1
- package/dist/threads/task-contracts.d.ts +194 -0
- package/dist/threads/task-contracts.d.ts.map +1 -0
- package/dist/threads/task-contracts.js +82 -0
- package/dist/threads/task-contracts.js.map +1 -0
- package/dist/threads/task-sidecar.d.ts +145 -0
- package/dist/threads/task-sidecar.d.ts.map +1 -0
- package/dist/threads/task-sidecar.js +311 -0
- package/dist/threads/task-sidecar.js.map +1 -0
- package/dist/threads/task-store.d.ts +172 -0
- package/dist/threads/task-store.d.ts.map +1 -0
- package/dist/threads/task-store.js +599 -0
- package/dist/threads/task-store.js.map +1 -0
- package/dist/threads/thread-activity-controller.d.ts +7 -0
- package/dist/threads/thread-activity-controller.d.ts.map +1 -1
- package/dist/threads/thread-activity-controller.js +52 -2
- package/dist/threads/thread-activity-controller.js.map +1 -1
- package/dist/threads/thread-task-context.d.ts +40 -0
- package/dist/threads/thread-task-context.d.ts.map +1 -0
- package/dist/threads/thread-task-context.js +160 -0
- package/dist/threads/thread-task-context.js.map +1 -0
- package/dist/threads/thread-task-controller.d.ts +122 -0
- package/dist/threads/thread-task-controller.d.ts.map +1 -0
- package/dist/threads/thread-task-controller.js +276 -0
- package/dist/threads/thread-task-controller.js.map +1 -0
- package/dist/threads/thread-task-runtime.d.ts +350 -0
- package/dist/threads/thread-task-runtime.d.ts.map +1 -0
- package/dist/threads/thread-task-runtime.js +896 -0
- package/dist/threads/thread-task-runtime.js.map +1 -0
- package/dist/threads/worker-entry.d.ts.map +1 -1
- package/dist/threads/worker-entry.js +28 -29
- package/dist/threads/worker-entry.js.map +1 -1
- package/dist/threads/worker-execution-runtime.d.ts +2 -3
- package/dist/threads/worker-execution-runtime.d.ts.map +1 -1
- package/dist/threads/worker-execution-runtime.js +2 -5
- package/dist/threads/worker-execution-runtime.js.map +1 -1
- package/dist/tools/delegation.d.ts +2 -75
- package/dist/tools/delegation.d.ts.map +1 -1
- package/dist/tools/delegation.js +29 -63
- package/dist/tools/delegation.js.map +1 -1
- package/dist/tools/email.d.ts +1 -1
- package/dist/tools/execution-control.d.ts +0 -5
- package/dist/tools/execution-control.d.ts.map +1 -1
- package/dist/tools/execution-control.js +2 -9
- package/dist/tools/execution-control.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp-manager.d.ts +1 -1
- package/dist/tools/memory.d.ts +1 -1
- package/dist/tools/notify.d.ts +0 -2
- package/dist/tools/notify.d.ts.map +1 -1
- package/dist/tools/notify.js +5 -21
- package/dist/tools/notify.js.map +1 -1
- package/dist/tools/skill.d.ts +1 -1
- package/dist/tools/thread-tasks.d.ts +23 -0
- package/dist/tools/thread-tasks.d.ts.map +1 -0
- package/dist/tools/thread-tasks.js +122 -0
- package/dist/tools/thread-tasks.js.map +1 -0
- package/dist/tools/totp.d.ts +1 -1
- package/dist/tui/request-store.d.ts +1 -1
- package/dist/tui/request-store.d.ts.map +1 -1
- package/dist/tui/request-store.js +2 -2
- package/dist/tui/request-store.js.map +1 -1
- package/dist/tui/server.d.ts.map +1 -1
- package/dist/tui/server.js +2 -17
- package/dist/tui/server.js.map +1 -1
- package/dist/utils/restart.d.ts.map +1 -1
- package/dist/utils/restart.js +0 -6
- package/dist/utils/restart.js.map +1 -1
- package/dist-agent/bundle.cjs +2 -2
- package/package.json +1 -1
- package/dist/agent/runtime-context.d.ts +0 -27
- package/dist/agent/runtime-context.d.ts.map +0 -1
- package/dist/agent/runtime-context.js +0 -60
- package/dist/agent/runtime-context.js.map +0 -1
- package/dist/agent/runtime-disclosure-policy.d.ts +0 -6
- package/dist/agent/runtime-disclosure-policy.d.ts.map +0 -1
- package/dist/agent/runtime-disclosure-policy.js +0 -16
- package/dist/agent/runtime-disclosure-policy.js.map +0 -1
- package/dist/conversations/lazy-runtime.d.ts +0 -5
- package/dist/conversations/lazy-runtime.d.ts.map +0 -1
- package/dist/conversations/lazy-runtime.js +0 -12
- package/dist/conversations/lazy-runtime.js.map +0 -1
- package/dist/conversations/runtime.d.ts +0 -17
- package/dist/conversations/runtime.d.ts.map +0 -1
- package/dist/conversations/runtime.js +0 -92
- package/dist/conversations/runtime.js.map +0 -1
- package/dist/conversations/scheduler.d.ts +0 -57
- package/dist/conversations/scheduler.d.ts.map +0 -1
- package/dist/conversations/scheduler.js +0 -175
- package/dist/conversations/scheduler.js.map +0 -1
- package/dist/threads/foreman-business.d.ts +0 -9
- package/dist/threads/foreman-business.d.ts.map +0 -1
- package/dist/threads/foreman-business.js +0 -117
- package/dist/threads/foreman-business.js.map +0 -1
- package/dist/threads/foreman-delivery.d.ts +0 -5
- package/dist/threads/foreman-delivery.d.ts.map +0 -1
- package/dist/threads/foreman-delivery.js +0 -21
- package/dist/threads/foreman-delivery.js.map +0 -1
- package/dist/threads/task-workspace.d.ts +0 -8
- package/dist/threads/task-workspace.d.ts.map +0 -1
- package/dist/threads/task-workspace.js +0 -52
- package/dist/threads/task-workspace.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { DEFAULT_THREAD_ID } from "../channels/interface.js";
|
|
3
3
|
import { isDeliveryTarget } from "../channels/delivery-target.js";
|
|
4
4
|
import { taskContinuationRouting } from "../agent/reply-target.js";
|
|
@@ -6,10 +6,7 @@ import { resolveWorkspaceCwd } from "../agent/workspace-cwd.js";
|
|
|
6
6
|
import { t } from "../i18n/index.js";
|
|
7
7
|
import { logger } from "../logger.js";
|
|
8
8
|
import { ExecutionCommandConflictError, isTerminalExecutionState, } from "./execution-store.js";
|
|
9
|
-
import {
|
|
10
|
-
import { prepareTaskWorkspace } from "./task-workspace.js";
|
|
11
|
-
import { publicTaskText, taskDeliveryMode } from "./foreman-delivery.js";
|
|
12
|
-
import { CancelArgsSchema, DelegateArgsSchema, DelegateBatchArgsSchema, FOREMAN_EVENT_META_KEY, FOREMAN_SCOPE, InputArgsSchema, StatusArgsSchema, } from "./foreman-protocol.js";
|
|
9
|
+
import { CancelArgsSchema, DelegateArgsSchema, FOREMAN_EVENT_META_KEY, FOREMAN_SCOPE, InputArgsSchema, StatusArgsSchema, } from "./foreman-protocol.js";
|
|
13
10
|
/**
|
|
14
11
|
* The Foreman host (#2805 stage 3), in the main process.
|
|
15
12
|
*
|
|
@@ -42,23 +39,6 @@ function textOf(value, fallback = "") {
|
|
|
42
39
|
function clip(text, max) {
|
|
43
40
|
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
44
41
|
}
|
|
45
|
-
function identity(...parts) {
|
|
46
|
-
return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
|
|
47
|
-
}
|
|
48
|
-
function taskIdFor(conversation, dispatch, key) {
|
|
49
|
-
return `task-${identity(conversation, dispatch, key)}`;
|
|
50
|
-
}
|
|
51
|
-
function remainingBudget(limits, attempts) {
|
|
52
|
-
const used = (field) => attempts.reduce((sum, attempt) => {
|
|
53
|
-
const value = (attempt.usage ?? attempt.result?.usage)?.[field];
|
|
54
|
-
return sum + (typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0);
|
|
55
|
-
}, 0);
|
|
56
|
-
return {
|
|
57
|
-
maxModelAttempts: limits.maxModelAttempts - used("modelAttempts"),
|
|
58
|
-
maxToolCalls: limits.maxToolCalls - used("toolCalls"),
|
|
59
|
-
maxDurationMs: limits.maxDurationMs - used("elapsedMs"),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
42
|
function describeState(record) {
|
|
63
43
|
switch (record.state) {
|
|
64
44
|
case "queued": return "queued, waiting for a worker slot";
|
|
@@ -77,13 +57,10 @@ function fallbackKey(obligation) {
|
|
|
77
57
|
return "foreman.fallback.succeeded";
|
|
78
58
|
if (obligation.status === "cancelled")
|
|
79
59
|
return "foreman.fallback.cancelled";
|
|
80
|
-
if (obligation.status === "partial")
|
|
81
|
-
return "foreman.fallback.partial";
|
|
82
60
|
return "foreman.fallback.failed";
|
|
83
61
|
}
|
|
84
62
|
export class ForemanHost {
|
|
85
63
|
deps;
|
|
86
|
-
pendingBatches = new Map();
|
|
87
64
|
timer = null;
|
|
88
65
|
sweeping = false;
|
|
89
66
|
now;
|
|
@@ -104,33 +81,13 @@ export class ForemanHost {
|
|
|
104
81
|
clearInterval(this.timer);
|
|
105
82
|
this.timer = null;
|
|
106
83
|
}
|
|
107
|
-
|
|
108
|
-
async readBusiness(executionId, tool, args, workspace) {
|
|
109
|
-
const record = this.deps.executions.get(executionId);
|
|
110
|
-
const dispatch = record ? this.ownedDispatch(record.caller.ref, record.taskId) : null;
|
|
111
|
-
if (record?.caller.kind !== "general" || record.state !== "running" || !record.payload.businessRead || !dispatch?.metadata.businessRead
|
|
112
|
-
|| !this.deps.canReadBusiness?.(dispatch.threadId, dispatch.replyTarget) || !this.deps.readBusiness) {
|
|
113
|
-
return fail("This execution is not authorized to read the owner's workspace");
|
|
114
|
-
}
|
|
115
|
-
if (record.payload.workspaceMode === "read_only" && tool === "manage_drive"
|
|
116
|
-
&& typeof args === "object" && args !== null && "action" in args && args.action === "download_file") {
|
|
117
|
-
return fail("A read-only Task cannot download files into its workspace");
|
|
118
|
-
}
|
|
119
|
-
const stillAuthorized = () => {
|
|
120
|
-
const current = this.deps.executions.get(executionId);
|
|
121
|
-
return current?.state === "running" && current.workerInstanceId === record.workerInstanceId
|
|
122
|
-
&& this.deps.canReadBusiness?.(dispatch.threadId, dispatch.replyTarget) === true;
|
|
123
|
-
};
|
|
124
|
-
const result = await this.deps.readBusiness(tool, args, workspace, stillAuthorized);
|
|
125
|
-
return stillAuthorized() ? result : fail("The execution stopped or its business-read permission was revoked");
|
|
126
|
-
}
|
|
127
|
-
async handle(threadId, command) {
|
|
84
|
+
handle(threadId, command) {
|
|
128
85
|
try {
|
|
129
|
-
return
|
|
86
|
+
return Promise.resolve(this.run(threadId, command));
|
|
130
87
|
}
|
|
131
88
|
catch (error) {
|
|
132
89
|
logger.warn(`[foreman] ${command.type} from thread=${threadId} failed: ${errorMessage(error)}`);
|
|
133
|
-
return fail(errorMessage(error));
|
|
90
|
+
return Promise.resolve(fail(errorMessage(error)));
|
|
134
91
|
}
|
|
135
92
|
}
|
|
136
93
|
// ----------------------------------------------------------- delivery
|
|
@@ -147,25 +104,11 @@ export class ForemanHost {
|
|
|
147
104
|
return Promise.resolve(true);
|
|
148
105
|
const threadId = caller.ref;
|
|
149
106
|
const dispatch = this.ownedDispatch(threadId, event.taskId);
|
|
150
|
-
if (dispatch?.metadata.parentTaskId && event.type === "result") {
|
|
151
|
-
return this.deliverParentResult(threadId, dispatch.metadata.parentTaskId);
|
|
152
|
-
}
|
|
153
|
-
// A retried Task may still have an older result/question in the outbox.
|
|
154
|
-
// A transport replay must not revive that superseded attempt's delivery.
|
|
155
|
-
const latest = dispatch && !dispatch.metadata.children?.length ? this.latest(dispatch.taskId) : null;
|
|
156
|
-
if (latest && latest.executionId !== event.executionId)
|
|
157
|
-
return Promise.resolve(true);
|
|
158
|
-
if (event.type === "result" && this.deps.store.isUserCancelled(event.taskId))
|
|
159
|
-
return Promise.resolve(true);
|
|
160
107
|
const userCancelled = event.type === "result" && event.payload.status === "cancelled"
|
|
161
108
|
&& (event.payload.userInitiated === true || this.deps.store.isUserCancelled(event.taskId));
|
|
162
|
-
const publicText = publicTaskText(event);
|
|
163
|
-
const direct = Boolean(dispatch?.replyTarget && !userCancelled && publicText !== null && taskDeliveryMode(event) === "direct"
|
|
164
|
-
&& !dispatch.metadata.children?.length);
|
|
165
109
|
// Queue first, then open the obligation: both are idempotent per event, so
|
|
166
110
|
// a crash in between is repaired by the outbox's redelivery.
|
|
167
|
-
|
|
168
|
-
this.deps.enqueueToGeneral(threadId, this.eventMessage(event, threadId, dispatch, dispatch !== null && !userCancelled));
|
|
111
|
+
this.deps.enqueueToGeneral(threadId, this.eventMessage(event, threadId, dispatch, dispatch !== null && !userCancelled));
|
|
169
112
|
if (dispatch) {
|
|
170
113
|
this.deps.store.open({
|
|
171
114
|
taskId: event.taskId,
|
|
@@ -174,36 +117,23 @@ export class ForemanHost {
|
|
|
174
117
|
kind: event.type,
|
|
175
118
|
status: event.type === "question" ? "question" : String(event.payload.status),
|
|
176
119
|
userInitiated: userCancelled,
|
|
177
|
-
publicText,
|
|
178
|
-
deliveryMode: direct ? "direct" : "review",
|
|
179
120
|
});
|
|
180
121
|
}
|
|
181
|
-
return
|
|
122
|
+
return Promise.resolve(true);
|
|
182
123
|
}
|
|
183
|
-
/**
|
|
124
|
+
/** Send the fixed notice for every obligation that is due. Safe to call at any time. */
|
|
184
125
|
async sweep() {
|
|
185
126
|
if (this.sweeping)
|
|
186
127
|
return;
|
|
187
128
|
this.sweeping = true;
|
|
188
129
|
try {
|
|
189
|
-
for (const cancel of this.deps.store.groupCancels()) {
|
|
190
|
-
const parent = this.deps.store.dispatch(cancel.taskId);
|
|
191
|
-
if (parent)
|
|
192
|
-
this.cancelChildren(parent, cancel.reason, cancel.byUser);
|
|
193
|
-
}
|
|
194
130
|
for (const obligation of this.deps.store.due()) {
|
|
195
131
|
const dispatch = this.deps.store.dispatch(obligation.taskId);
|
|
196
132
|
if (!dispatch?.replyTarget) {
|
|
197
133
|
this.deps.store.fallbackFailed(obligation.taskId, obligation.eventId, "No reply target was recorded for this task");
|
|
198
134
|
continue;
|
|
199
135
|
}
|
|
200
|
-
const
|
|
201
|
-
? this.allEvents(obligation.executionId).find((event) => event.eventId === obligation.eventId) : undefined;
|
|
202
|
-
const publicText = obligation.publicText ?? (legacyEvent ? publicTaskText(legacyEvent) : null);
|
|
203
|
-
const text = obligation.deliveryMode === "direct" && publicText ? `${dispatch.title}\n\n${publicText}`
|
|
204
|
-
: obligation.deliveryMode === "review" && obligation.kind === "result"
|
|
205
|
-
? t("foreman.fallback.review", this.deps.locale(), dispatch.title)
|
|
206
|
-
: [t(fallbackKey(obligation), this.deps.locale(), dispatch.title), publicText].filter(Boolean).join("\n\n");
|
|
136
|
+
const text = t(fallbackKey(obligation), this.deps.locale(), dispatch.title);
|
|
207
137
|
let result;
|
|
208
138
|
try {
|
|
209
139
|
result = await this.deps.deliver(dispatch.replyTarget, text, dispatch.threadId);
|
|
@@ -212,11 +142,8 @@ export class ForemanHost {
|
|
|
212
142
|
result = { ok: false, errorMessage: errorMessage(error) };
|
|
213
143
|
}
|
|
214
144
|
if (result.ok) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
else
|
|
218
|
-
this.deps.store.fallbackSent(obligation.taskId, obligation.eventId);
|
|
219
|
-
logger.system(`[foreman] Sent ${obligation.deliveryMode === "direct" ? "direct delivery" : "fallback notice"} for task=${obligation.taskId} event=${obligation.eventId} (${obligation.kind})`);
|
|
145
|
+
this.deps.store.fallbackSent(obligation.taskId, obligation.eventId);
|
|
146
|
+
logger.system(`[foreman] Sent the delivery fallback for task=${obligation.taskId} event=${obligation.eventId} (${obligation.kind})`);
|
|
220
147
|
continue;
|
|
221
148
|
}
|
|
222
149
|
const after = this.deps.store.fallbackFailed(obligation.taskId, obligation.eventId, result.errorMessage);
|
|
@@ -237,25 +164,11 @@ export class ForemanHost {
|
|
|
237
164
|
// ----------------------------------------------------------- commands
|
|
238
165
|
run(threadId, command) {
|
|
239
166
|
switch (command.type) {
|
|
240
|
-
case "
|
|
241
|
-
case "delegate": return this.deps.allowDelegation?.() === false ? fail("New Task delegation is disabled") : this.delegate(threadId, command);
|
|
242
|
-
case "delegate_batch": return this.deps.allowDelegation?.() === false ? fail("New Task delegation is disabled") : this.delegateBatch(threadId, command);
|
|
167
|
+
case "delegate": return this.delegate(threadId, command);
|
|
243
168
|
case "status": return this.status(threadId, command.args);
|
|
244
169
|
case "input": return this.input(threadId, command.args);
|
|
245
170
|
case "cancel": return this.cancel(threadId, command.args);
|
|
246
|
-
case "
|
|
247
|
-
const roots = this.deps.store.allForThread(threadId).filter((task) => !task.metadata.parentTaskId && this.isOpen(task));
|
|
248
|
-
const results = roots.map((task) => this.cancel(threadId, { task_id: task.taskId, requested_by_user: true, reason: "User stopped this conversation's tasks" }));
|
|
249
|
-
return {
|
|
250
|
-
ok: results.every((result) => result.ok),
|
|
251
|
-
text: roots.length > 0 ? [
|
|
252
|
-
t("foreman.cancel_all", this.deps.locale(), String(roots.length)),
|
|
253
|
-
...roots.slice(0, 10).map((task) => `- ${task.title}`),
|
|
254
|
-
...results.filter((result) => !result.ok).map((result) => result.text),
|
|
255
|
-
].join("\n") : "",
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
case "delivered": return this.delivered(threadId, command.taskId, command.purpose, command.eventId);
|
|
171
|
+
case "delivered": return this.delivered(threadId, command.taskId, command.purpose);
|
|
259
172
|
case "turn_ended": return this.turnEnded(threadId, command.events);
|
|
260
173
|
case "summary": return { ok: true, text: this.summary(threadId) };
|
|
261
174
|
}
|
|
@@ -271,177 +184,15 @@ export class ForemanHost {
|
|
|
271
184
|
latest(taskId) {
|
|
272
185
|
return this.deps.executions.latestForTask(taskId, FOREMAN_SCOPE);
|
|
273
186
|
}
|
|
274
|
-
childRecords(parent) {
|
|
275
|
-
return (parent.metadata.children ?? []).map((taskId) => ({ taskId, record: this.latest(taskId) }));
|
|
276
|
-
}
|
|
277
|
-
parentState(parent) {
|
|
278
|
-
const children = this.childRecords(parent);
|
|
279
|
-
if (children.every((child) => child.record === null))
|
|
280
|
-
return "not_started";
|
|
281
|
-
if (children.some((child) => child.record === null || !isTerminalExecutionState(child.record.state))) {
|
|
282
|
-
if (children.some((child) => child.record?.state === "running"))
|
|
283
|
-
return "running";
|
|
284
|
-
if (children.some((child) => child.record?.state === "cancel_requested"))
|
|
285
|
-
return "cancel_requested";
|
|
286
|
-
if (children.some((child) => child.record?.state === "waiting_input"))
|
|
287
|
-
return "waiting_input";
|
|
288
|
-
return "queued";
|
|
289
|
-
}
|
|
290
|
-
if (children.every((child) => child.record?.state === "succeeded"))
|
|
291
|
-
return "succeeded";
|
|
292
|
-
if (children.every((child) => child.record?.state === "cancelled") || this.deps.store.groupCancels().some((item) => item.taskId === parent.taskId))
|
|
293
|
-
return "cancelled";
|
|
294
|
-
if (children.some((child) => child.record?.state === "succeeded"))
|
|
295
|
-
return "partial";
|
|
296
|
-
return "failed";
|
|
297
|
-
}
|
|
298
|
-
deliverParentResult(threadId, taskId) {
|
|
299
|
-
const parent = this.ownedDispatch(threadId, taskId);
|
|
300
|
-
if (!parent?.metadata.children?.length)
|
|
301
|
-
return Promise.resolve(false);
|
|
302
|
-
const children = this.childRecords(parent);
|
|
303
|
-
if (children.some((child) => !child.record || !isTerminalExecutionState(child.record.state)))
|
|
304
|
-
return Promise.resolve(true);
|
|
305
|
-
const records = children.map((child) => child.record).filter((record) => record !== null);
|
|
306
|
-
// The set of attempts is the result version. A late event from an old
|
|
307
|
-
// attempt cannot create a second delivery for the current result version.
|
|
308
|
-
const eventId = `evt-task-${identity(taskId, ...records.map((record) => record.executionId))}`;
|
|
309
|
-
const status = this.parentState(parent);
|
|
310
|
-
const summary = records.map((record) => {
|
|
311
|
-
const childId = record.taskId;
|
|
312
|
-
const title = this.deps.store.dispatch(childId)?.title ?? childId;
|
|
313
|
-
return `${childId} "${title}": ${record.state}. ${clip(record.result?.summary ?? record.result?.error?.message ?? "", 400)}`;
|
|
314
|
-
}).join("\n");
|
|
315
|
-
const event = {
|
|
316
|
-
protocolVersion: 1, eventId, seq: 0, taskId, executionId: taskId, type: "result",
|
|
317
|
-
createdAt: records.map((record) => record.updatedAt).sort().at(-1) ?? this.now().toISOString(),
|
|
318
|
-
payload: {
|
|
319
|
-
status, summary,
|
|
320
|
-
data: { userMessage: children.map(({ taskId: childId, record }) => {
|
|
321
|
-
const message = publicTaskText({ type: "result", payload: { data: record?.result?.data } });
|
|
322
|
-
const title = this.deps.store.dispatch(childId)?.title ?? childId;
|
|
323
|
-
return message ? `${title}\n${message}` : null;
|
|
324
|
-
}).filter(Boolean).join("\n\n") },
|
|
325
|
-
artifactRefs: records.flatMap((record) => record.result?.artifactRefs ?? []).slice(0, 30),
|
|
326
|
-
userInitiated: status === "cancelled" && this.deps.store.isUserCancelled(taskId),
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
return this.deliverEvent(event, { kind: "general", ref: threadId });
|
|
330
|
-
}
|
|
331
|
-
cancelChildren(parent, reason, byUser) {
|
|
332
|
-
for (const { taskId, record } of this.childRecords(parent)) {
|
|
333
|
-
if (!record || isTerminalExecutionState(record.state))
|
|
334
|
-
continue;
|
|
335
|
-
if (byUser)
|
|
336
|
-
this.deps.store.markUserCancelled(taskId);
|
|
337
|
-
this.deps.store.withdraw(taskId);
|
|
338
|
-
this.deps.executions.cancel(this.envelope(parent.threadId, `cancel-${identity(parent.taskId, record.executionId, reason, String(byUser))}`), {
|
|
339
|
-
executionId: record.executionId, reason, userInitiated: byUser,
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
delegateBatch(threadId, command) {
|
|
344
|
-
const parsed = DelegateBatchArgsSchema.safeParse(command.args);
|
|
345
|
-
if (!parsed.success)
|
|
346
|
-
return Promise.resolve(invalid(parsed.error));
|
|
347
|
-
const key = identity(threadId, command.dispatchKey, parsed.data.title);
|
|
348
|
-
const specification = JSON.stringify(parsed.data);
|
|
349
|
-
const pending = this.pendingBatches.get(key);
|
|
350
|
-
if (pending)
|
|
351
|
-
return pending.specification === specification ? pending.result : Promise.resolve(fail("Batch identity is already being prepared with different content"));
|
|
352
|
-
const result = this.prepareBatch(threadId, command);
|
|
353
|
-
this.pendingBatches.set(key, { specification, result });
|
|
354
|
-
void result.finally(() => { this.pendingBatches.delete(key); }).catch(() => { });
|
|
355
|
-
return result;
|
|
356
|
-
}
|
|
357
|
-
async prepareBatch(threadId, command) {
|
|
358
|
-
const parsed = DelegateBatchArgsSchema.safeParse(command.args);
|
|
359
|
-
if (!parsed.success)
|
|
360
|
-
return invalid(parsed.error);
|
|
361
|
-
const args = parsed.data;
|
|
362
|
-
if (new Set(args.tasks.map((task) => task.key)).size !== args.tasks.length)
|
|
363
|
-
return fail("Task keys must be unique within a batch");
|
|
364
|
-
const atomic = this.deps.executions.atomic;
|
|
365
|
-
if (!atomic)
|
|
366
|
-
return fail("This execution host does not support atomic task batches");
|
|
367
|
-
const rootId = taskIdFor(threadId, command.dispatchKey, `batch:${args.title}`);
|
|
368
|
-
const specification = identity(JSON.stringify(args));
|
|
369
|
-
const previous = this.ownedDispatch(threadId, rootId);
|
|
370
|
-
if (previous && previous.metadata.specification !== specification)
|
|
371
|
-
return fail("This batch key was already used with different tasks; use a distinct title for new work");
|
|
372
|
-
const ids = args.tasks.map((task) => taskIdFor(threadId, command.dispatchKey, `${rootId}:${task.key}`));
|
|
373
|
-
const commits = new Map();
|
|
374
|
-
// Filesystem preparation happens before the synchronous acceptance transaction.
|
|
375
|
-
// No worker starts until every Task and command has been committed.
|
|
376
|
-
const workspaces = [];
|
|
377
|
-
const contexts = [];
|
|
378
|
-
for (const [index, task] of args.tasks.entries()) {
|
|
379
|
-
const existing = this.ownedDispatch(threadId, ids[index]);
|
|
380
|
-
if (existing?.metadata.specification && existing.metadata.specification !== identity(specification, task.key))
|
|
381
|
-
throw new Error(`Task key ${task.key} was already used with different content`);
|
|
382
|
-
const source = task.context_task_id ? this.ownedDispatch(threadId, task.context_task_id) : null;
|
|
383
|
-
const sourceRecord = source ? (existing?.metadata.contextExecutionId
|
|
384
|
-
? this.deps.executions.get(existing.metadata.contextExecutionId) : this.latest(source.taskId)) : null;
|
|
385
|
-
if (task.context_task_id && (!sourceRecord || sourceRecord.taskId !== source?.taskId || !isTerminalExecutionState(sourceRecord.state))) {
|
|
386
|
-
return fail("context_task_id must name a completed execution Task from this conversation");
|
|
387
|
-
}
|
|
388
|
-
contexts.push(sourceRecord?.executionId);
|
|
389
|
-
const directory = task.project_dir ?? sourceRecord?.payload.workspaceRef ?? source?.workspace;
|
|
390
|
-
workspaces.push(existing?.workspace ?? (directory
|
|
391
|
-
? await prepareTaskWorkspace(directory, ids[index], task.workspace_mode, commits)
|
|
392
|
-
: undefined));
|
|
393
|
-
}
|
|
394
|
-
const parent = args.result_mode === "combined" && args.tasks.length > 1;
|
|
395
|
-
const foreground = this.ownedDispatch(threadId, ids[0])?.metadata.foregroundCalls
|
|
396
|
-
?? Math.max(0, Math.trunc(command.foreground.toolCallsUsed));
|
|
397
|
-
const budget = DEFAULT_LAB_RUN_BUDGET;
|
|
398
|
-
return atomic(() => this.deps.store.atomic(() => {
|
|
399
|
-
if (parent)
|
|
400
|
-
this.deps.store.recordDispatch({
|
|
401
|
-
taskId: rootId, threadId, title: args.title, replyTarget: isDeliveryTarget(command.replyTarget)
|
|
402
|
-
? command.replyTarget : threadId === DEFAULT_THREAD_ID ? this.deps.ownerTarget() : null,
|
|
403
|
-
sender: command.sender ?? "", metadata: { children: ids, specification, sourceKey: command.dispatchKey },
|
|
404
|
-
});
|
|
405
|
-
for (const [index, task] of args.tasks.entries()) {
|
|
406
|
-
const result = this.delegate(threadId, {
|
|
407
|
-
type: "delegate", args: { ...task, project_dir: workspaces[index], workspace_mode: task.workspace_mode === "isolated" ? "shared" : task.workspace_mode },
|
|
408
|
-
foreground: { taskId: ids[index], toolCallsUsed: index === 0 ? foreground : 0 },
|
|
409
|
-
...(parent ? { parentTaskId: rootId } : {}),
|
|
410
|
-
preparedWorkspace: Boolean(workspaces[index] && task.workspace_mode === "isolated"),
|
|
411
|
-
contextExecutionId: contexts[index],
|
|
412
|
-
specification: identity(specification, task.key),
|
|
413
|
-
sourceKey: command.dispatchKey,
|
|
414
|
-
budget, replyTarget: command.replyTarget, sender: command.sender,
|
|
415
|
-
});
|
|
416
|
-
if (!result.ok)
|
|
417
|
-
throw new Error(result.text);
|
|
418
|
-
}
|
|
419
|
-
return {
|
|
420
|
-
ok: true, acceptedTaskId: parent ? rootId : ids[0], acceptedTaskIds: ids,
|
|
421
|
-
text: `Accepted ${ids.length} Task(s)${parent ? ` under parent ${rootId} for one combined delivery` : " with independent delivery"}: ${args.tasks.map((task, i) => `${task.key}=${ids[i]}`).join(", ")}. Each Task has its own budget and runs when slots are available. You may delegate more independent work, coordinate existing Tasks, or reply. Do not repeat work assigned to these Tasks.`,
|
|
422
|
-
};
|
|
423
|
-
}));
|
|
424
|
-
}
|
|
425
187
|
delegate(threadId, command) {
|
|
426
|
-
if (command.args && typeof command.args === "object" && "worker" in command.args && command.args.worker !== null && command.args.worker !== undefined) {
|
|
427
|
-
return fail("Worker aliases are no longer used. Select a completed context_task_id, or send input to the existing Task.");
|
|
428
|
-
}
|
|
429
188
|
const parsed = DelegateArgsSchema.safeParse(command.args);
|
|
430
189
|
if (!parsed.success)
|
|
431
190
|
return invalid(parsed.error);
|
|
432
191
|
const args = parsed.data;
|
|
433
192
|
let retryOf = null;
|
|
434
|
-
const retryDispatchId = args.retry_task_id && command.dispatchKey ? identity(threadId, command.dispatchKey) : undefined;
|
|
435
|
-
const retrySpecification = retryDispatchId ? identity(JSON.stringify(args)) : undefined;
|
|
436
193
|
if (args.retry_task_id) {
|
|
437
194
|
if (!this.ownedDispatch(threadId, args.retry_task_id))
|
|
438
195
|
return fail(`task ${args.retry_task_id} was not delegated from this conversation`);
|
|
439
|
-
const replay = retryDispatchId ? this.deps.executions.attemptsForTask?.(args.retry_task_id, FOREMAN_SCOPE)
|
|
440
|
-
.find((attempt) => attempt.payload.retryDispatchId === retryDispatchId) : undefined;
|
|
441
|
-
if (replay)
|
|
442
|
-
return replay.payload.retrySpecification === retrySpecification
|
|
443
|
-
? { ok: true, acceptedTaskId: replay.taskId, text: `Retry already accepted for task ${replay.taskId}, execution ${replay.executionId}: ${describeState(replay)}.` }
|
|
444
|
-
: fail("This retry was already accepted with different content; use background_task_input or a new user instruction");
|
|
445
196
|
retryOf = this.latest(args.retry_task_id);
|
|
446
197
|
if (!retryOf)
|
|
447
198
|
return fail(`task ${args.retry_task_id} has no earlier run to retry`);
|
|
@@ -449,34 +200,13 @@ export class ForemanHost {
|
|
|
449
200
|
return fail(`task ${args.retry_task_id} is still ${describeState(retryOf)}; send changes with background_task_input instead`);
|
|
450
201
|
}
|
|
451
202
|
}
|
|
452
|
-
const taskId = args.retry_task_id ??
|
|
453
|
-
|
|
454
|
-
: command.foreground.taskId);
|
|
455
|
-
const oldDispatch = this.ownedDispatch(threadId, taskId);
|
|
456
|
-
if (command.parentTaskId && !this.ownedDispatch(threadId, command.parentTaskId)?.metadata.children?.includes(taskId)) {
|
|
457
|
-
return fail("The parent Task did not admit this child");
|
|
458
|
-
}
|
|
459
|
-
const previous = args.context_task_id ? this.ownedDispatch(threadId, args.context_task_id) : null;
|
|
460
|
-
const contextExecutionId = command.contextExecutionId ?? oldDispatch?.metadata.contextExecutionId;
|
|
461
|
-
const previousRecord = previous ? (contextExecutionId
|
|
462
|
-
? this.deps.executions.get(contextExecutionId) : this.latest(previous.taskId)) : null;
|
|
463
|
-
if (args.context_task_id) {
|
|
464
|
-
if (args.retry_task_id)
|
|
465
|
-
return fail("Use either context_task_id for new work or retry_task_id for another attempt, not both");
|
|
466
|
-
if (!previousRecord || previousRecord.taskId !== previous?.taskId)
|
|
467
|
-
return fail("context_task_id must name an execution Task from this conversation");
|
|
468
|
-
if (!isTerminalExecutionState(previousRecord.state))
|
|
469
|
-
return fail("That Task is unfinished; use background_task_input to continue it");
|
|
470
|
-
}
|
|
203
|
+
const taskId = args.retry_task_id ?? command.foreground.taskId;
|
|
204
|
+
const previous = args.worker ? this.deps.store.lastForWorker(threadId, args.worker) : null;
|
|
471
205
|
let workspace;
|
|
472
|
-
|
|
473
|
-
return fail("A retry keeps its workspace access. Delegate a new Task to change read-only versus writable access");
|
|
474
|
-
}
|
|
475
|
-
const workspaceMode = retryOf?.payload.workspaceMode ?? (command.preparedWorkspace ? "isolated" : args.workspace_mode ?? "shared");
|
|
476
|
-
const requestedDir = args.project_dir ?? retryOf?.payload.workspaceRef ?? oldDispatch?.workspace ?? previousRecord?.payload.workspaceRef ?? previous?.workspace ?? undefined;
|
|
206
|
+
const requestedDir = args.project_dir ?? previous?.workspace ?? undefined;
|
|
477
207
|
if (requestedDir) {
|
|
478
208
|
try {
|
|
479
|
-
workspace = resolveWorkspaceCwd(requestedDir, { writable:
|
|
209
|
+
workspace = resolveWorkspaceCwd(requestedDir, { writable: true });
|
|
480
210
|
}
|
|
481
211
|
catch (error) {
|
|
482
212
|
return fail(`project_dir: ${errorMessage(error)}. Nothing was delegated.`);
|
|
@@ -493,65 +223,41 @@ export class ForemanHost {
|
|
|
493
223
|
title: args.title,
|
|
494
224
|
replyTarget,
|
|
495
225
|
sender: typeof command.sender === "string" ? command.sender : "",
|
|
226
|
+
worker: args.worker ?? null,
|
|
496
227
|
workspace: workspace ?? null,
|
|
497
|
-
metadata: {
|
|
498
|
-
sourceKey: command.sourceKey ?? command.dispatchKey ?? (command.parentTaskId ? this.deps.store.dispatch(command.parentTaskId)?.metadata.sourceKey : undefined),
|
|
499
|
-
contextTaskId: previous?.taskId,
|
|
500
|
-
contextExecutionId: previousRecord?.executionId,
|
|
501
|
-
businessRead: this.deps.canReadBusiness?.(threadId, replyTarget) ?? false,
|
|
502
|
-
...(command.parentTaskId ? { parentTaskId: command.parentTaskId } : {}),
|
|
503
|
-
budget: command.budget ?? DEFAULT_LAB_RUN_BUDGET,
|
|
504
|
-
foregroundCalls: command.foreground.toolCallsUsed,
|
|
505
|
-
preparedWorkspace: command.preparedWorkspace,
|
|
506
|
-
specification: command.specification,
|
|
507
|
-
},
|
|
508
228
|
});
|
|
509
229
|
const context = [
|
|
510
230
|
args.context,
|
|
511
|
-
retryOf?.result?.summary ? `Previous attempt result: ${clip(retryOf.result.summary, 4_000)}` : undefined,
|
|
512
|
-
retryOf?.payload.contextSummary,
|
|
513
231
|
dispatch.sender ? `Requested by: ${dispatch.sender}` : undefined,
|
|
514
|
-
previous && previous.taskId !== taskId ? this.previousWork(previous
|
|
232
|
+
previous && previous.taskId !== taskId ? this.previousWork(previous) : undefined,
|
|
515
233
|
].filter((part) => typeof part === "string" && part.length > 0);
|
|
516
|
-
const toolCallsUsed =
|
|
234
|
+
const toolCallsUsed = command.foreground.toolCallsUsed;
|
|
517
235
|
const payload = {
|
|
518
236
|
goal: args.goal,
|
|
519
|
-
requirements:
|
|
520
|
-
...(
|
|
237
|
+
requirements: args.requirements,
|
|
238
|
+
...(args.expected_output ? { expectedOutput: args.expected_output } : {}),
|
|
521
239
|
...(context.length > 0 ? { contextSummary: clip(context.join("\n\n"), 20_000) } : {}),
|
|
522
240
|
checkpoint: {
|
|
523
241
|
done: args.done,
|
|
524
242
|
remaining: args.remaining,
|
|
525
243
|
decisions: [],
|
|
526
244
|
blockers: [],
|
|
527
|
-
artifactRefs:
|
|
245
|
+
artifactRefs: args.artifacts,
|
|
528
246
|
uncertainOperations: args.uncertain_operations,
|
|
529
247
|
},
|
|
530
248
|
title: dispatch.title,
|
|
531
|
-
businessRead: dispatch.metadata.businessRead === true,
|
|
532
249
|
foregroundToolCallsUsed: Number.isInteger(toolCallsUsed) && toolCallsUsed >= 0 ? toolCallsUsed : 0,
|
|
533
250
|
...(workspace ? { workspaceRef: workspace } : {}),
|
|
534
|
-
workspaceMode,
|
|
535
|
-
...(dispatch.metadata.parentTaskId ? { parentTaskId: dispatch.metadata.parentTaskId } : {}),
|
|
536
|
-
...((command.preparedWorkspace || (dispatch.metadata.preparedWorkspace && workspace === dispatch.workspace)
|
|
537
|
-
|| (previousRecord?.payload.preparedWorkspace && workspace === previousRecord.payload.workspaceRef)
|
|
538
|
-
|| (retryOf?.payload.preparedWorkspace && workspace === retryOf.payload.workspaceRef)) ? { preparedWorkspace: true } : {}),
|
|
539
|
-
...(retryOf ? { priorExecutionId: retryOf.executionId } : {}),
|
|
540
|
-
...(retryDispatchId ? { retryDispatchId, retrySpecification } : {}),
|
|
541
251
|
};
|
|
542
252
|
// One command per handoff (or per retry of one run): a repeated call
|
|
543
253
|
// returns the original acceptance instead of starting a second worker.
|
|
544
254
|
const commandId = retryOf ? `retry-${taskId}-${retryOf.executionId}` : `delegate-${taskId}`;
|
|
545
|
-
const budget = remainingBudget(dispatch.metadata.budget ?? DEFAULT_LAB_RUN_BUDGET, retryOf ? this.deps.executions.attemptsForTask?.(taskId, FOREMAN_SCOPE) ?? [retryOf] : []);
|
|
546
|
-
if (budget.maxModelAttempts < 1 || budget.maxToolCalls <= toolCallsUsed || budget.maxDurationMs < 1)
|
|
547
|
-
return fail(`Task ${taskId} has exhausted its cumulative budget`);
|
|
548
255
|
let outcome;
|
|
549
256
|
try {
|
|
550
257
|
outcome = this.deps.executions.submit(this.envelope(threadId, commandId), {
|
|
551
258
|
taskId,
|
|
552
259
|
...(retryOf ? { retryOf: retryOf.executionId } : {}),
|
|
553
260
|
payload,
|
|
554
|
-
budget,
|
|
555
261
|
});
|
|
556
262
|
}
|
|
557
263
|
catch (error) {
|
|
@@ -569,11 +275,6 @@ export class ForemanHost {
|
|
|
569
275
|
// The previous result is superseded: whatever it still owed the user moves to the new run.
|
|
570
276
|
this.deps.store.withdraw(taskId);
|
|
571
277
|
this.deps.store.clearUserCancelled(taskId);
|
|
572
|
-
if (dispatch.metadata.parentTaskId) {
|
|
573
|
-
this.deps.store.withdraw(dispatch.metadata.parentTaskId);
|
|
574
|
-
this.deps.store.clearUserCancelled(dispatch.metadata.parentTaskId);
|
|
575
|
-
this.deps.store.clearGroupCancel(dispatch.metadata.parentTaskId);
|
|
576
|
-
}
|
|
577
278
|
}
|
|
578
279
|
logger.system(`[foreman] Delegated task=${taskId} execution=${outcome.value.executionId} from thread=${threadId}${retryOf ? ` (retry of ${retryOf.executionId})` : ""}`);
|
|
579
280
|
return {
|
|
@@ -581,14 +282,15 @@ export class ForemanHost {
|
|
|
581
282
|
acceptedTaskId: taskId,
|
|
582
283
|
text: [
|
|
583
284
|
`Delegated as background task ${taskId} ("${dispatch.title}"), execution ${outcome.value.executionId}, ${outcome.value.state === "queued" ? "queued: it starts when a worker slot is free" : outcome.value.state}.`,
|
|
584
|
-
"
|
|
285
|
+
"Stop working on this task here. Tell the user it is running in the background (unless you already did), then call finish.",
|
|
585
286
|
"Its question or result arrives later as a [Background task update] message.",
|
|
586
287
|
].join(" "),
|
|
587
288
|
};
|
|
588
289
|
}
|
|
589
|
-
/**
|
|
590
|
-
previousWork(previous
|
|
591
|
-
const
|
|
290
|
+
/** What the same logical worker did last time, so follow-up work starts informed. */
|
|
291
|
+
previousWork(previous) {
|
|
292
|
+
const record = this.latest(previous.taskId);
|
|
293
|
+
const lines = [`Previous task of this worker: ${previous.taskId} "${previous.title}" (${record ? describeState(record) : "not started"})`];
|
|
592
294
|
if (record?.result?.summary)
|
|
593
295
|
lines.push(`Its result: ${clip(record.result.summary, 4_000)}`);
|
|
594
296
|
if (record?.result?.artifactRefs?.length)
|
|
@@ -605,9 +307,7 @@ export class ForemanHost {
|
|
|
605
307
|
? { ok: true, text: this.describeTask(dispatch, true) }
|
|
606
308
|
: fail(`no background task ${parsed.data.task_id} in this conversation`);
|
|
607
309
|
}
|
|
608
|
-
const
|
|
609
|
-
const active = roots.filter((dispatch) => this.isOpen(dispatch));
|
|
610
|
-
const dispatches = [...active, ...roots.filter((dispatch) => !this.isOpen(dispatch)).slice(0, Math.max(0, 20 - active.length))];
|
|
310
|
+
const dispatches = this.deps.store.dispatchesForThread(threadId, 20);
|
|
611
311
|
if (dispatches.length === 0)
|
|
612
312
|
return { ok: true, text: "No background tasks in this conversation." };
|
|
613
313
|
return { ok: true, text: dispatches.map((dispatch) => this.describeTask(dispatch, false)).join("\n") };
|
|
@@ -616,15 +316,9 @@ export class ForemanHost {
|
|
|
616
316
|
const parsed = InputArgsSchema.safeParse(args);
|
|
617
317
|
if (!parsed.success)
|
|
618
318
|
return invalid(parsed.error);
|
|
619
|
-
const { task_id: taskId, text
|
|
620
|
-
if (
|
|
621
|
-
return fail("source_task_id and source_event_id must be provided together when forwarding a completed result");
|
|
622
|
-
}
|
|
623
|
-
const dispatch = this.ownedDispatch(threadId, taskId);
|
|
624
|
-
if (!dispatch)
|
|
319
|
+
const { task_id: taskId, text } = parsed.data;
|
|
320
|
+
if (!this.ownedDispatch(threadId, taskId))
|
|
625
321
|
return fail(`no background task ${taskId} in this conversation`);
|
|
626
|
-
if (dispatch.metadata.children?.length)
|
|
627
|
-
return fail(`Send the answer or requirement to a child Task: ${dispatch.metadata.children.join(", ")}. Each child owns its input cursor.`);
|
|
628
322
|
const record = this.latest(taskId);
|
|
629
323
|
if (!record)
|
|
630
324
|
return fail(`task ${taskId} has not started`);
|
|
@@ -633,15 +327,6 @@ export class ForemanHost {
|
|
|
633
327
|
}
|
|
634
328
|
if (record.state === "cancel_requested")
|
|
635
329
|
return fail(`task ${taskId} is stopping and takes no more input`);
|
|
636
|
-
const sourceObligation = sourceTaskId && sourceEventId ? this.deps.store.get(sourceTaskId, sourceEventId) : null;
|
|
637
|
-
if (sourceTaskId && sourceEventId) {
|
|
638
|
-
if (sourceTaskId === taskId)
|
|
639
|
-
return fail("source_task_id must name a different Task when forwarding a result");
|
|
640
|
-
if (!this.ownedDispatch(threadId, sourceTaskId))
|
|
641
|
-
return fail(`no source Task ${sourceTaskId} in this conversation`);
|
|
642
|
-
if (sourceObligation?.kind !== "result")
|
|
643
|
-
return fail(`no result event ${sourceEventId} found for source Task ${sourceTaskId}`);
|
|
644
|
-
}
|
|
645
330
|
const questionId = parsed.data.question_id
|
|
646
331
|
?? (record.state === "waiting_input" ? record.openQuestionId ?? undefined : undefined);
|
|
647
332
|
const outcome = this.deps.executions.sendInput(this.envelope(threadId, `input-${randomUUID()}`), {
|
|
@@ -654,23 +339,13 @@ export class ForemanHost {
|
|
|
654
339
|
? `question ${questionId ?? ""} of task ${taskId} is no longer open`
|
|
655
340
|
: `the input was not accepted (${outcome.reason})`);
|
|
656
341
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// final synthesis task owns the user-facing delivery; turn_ended cannot
|
|
660
|
-
// make this intermediate result due again.
|
|
661
|
-
this.deps.store.transfer(sourceTaskId, sourceEventId, taskId, outcome.value.inputSeq);
|
|
662
|
-
}
|
|
663
|
-
else {
|
|
664
|
-
// General handled what the task asked; the next result carries the obligation.
|
|
665
|
-
this.deps.store.withdraw(taskId);
|
|
666
|
-
}
|
|
342
|
+
// General handled what the task asked; the next result carries the obligation.
|
|
343
|
+
this.deps.store.withdraw(taskId);
|
|
667
344
|
return {
|
|
668
345
|
ok: true,
|
|
669
346
|
text: questionId
|
|
670
347
|
? `Answer stored for task ${taskId} (input ${outcome.value.inputSeq}); the task resumes with it.`
|
|
671
|
-
:
|
|
672
|
-
? `Input stored for task ${taskId} (input ${outcome.value.inputSeq}); result ${sourceTaskId}/${sourceEventId} was transferred to this Task and will not trigger a separate user fallback.`
|
|
673
|
-
: `Input stored for task ${taskId} (input ${outcome.value.inputSeq}); the worker reads it at its next step. It is not applied yet.`,
|
|
348
|
+
: `Input stored for task ${taskId} (input ${outcome.value.inputSeq}); the worker reads it at its next step. It is not applied yet.`,
|
|
674
349
|
};
|
|
675
350
|
}
|
|
676
351
|
cancel(threadId, args) {
|
|
@@ -678,25 +353,10 @@ export class ForemanHost {
|
|
|
678
353
|
if (!parsed.success)
|
|
679
354
|
return invalid(parsed.error);
|
|
680
355
|
const { task_id: taskId, requested_by_user: byUser, reason } = parsed.data;
|
|
681
|
-
|
|
682
|
-
if (!dispatch)
|
|
356
|
+
if (!this.ownedDispatch(threadId, taskId))
|
|
683
357
|
return fail(`no background task ${taskId} in this conversation`);
|
|
684
|
-
if (dispatch.metadata.children?.length) {
|
|
685
|
-
if (!this.isOpen(dispatch))
|
|
686
|
-
return { ok: true, text: `Task ${taskId} has already finished (${this.parentState(dispatch)}).` };
|
|
687
|
-
if (byUser)
|
|
688
|
-
this.deps.store.markUserCancelled(taskId);
|
|
689
|
-
this.deps.store.withdraw(taskId);
|
|
690
|
-
this.deps.store.requestGroupCancel(taskId, reason ?? "Parent task cancelled", byUser);
|
|
691
|
-
this.cancelChildren(dispatch, reason ?? "Parent task cancelled", byUser);
|
|
692
|
-
return { ok: true, text: `Cancel requested for all unfinished children of ${taskId}. Running children must confirm their stop before the parent is cancelled.` };
|
|
693
|
-
}
|
|
694
358
|
const record = this.latest(taskId);
|
|
695
359
|
if (!record || isTerminalExecutionState(record.state)) {
|
|
696
|
-
if (byUser) {
|
|
697
|
-
this.deps.store.markUserCancelled(taskId);
|
|
698
|
-
this.deps.store.withdraw(taskId);
|
|
699
|
-
}
|
|
700
360
|
return { ok: true, text: `Task ${taskId} is not running (${record ? record.state : "never started"}); nothing to cancel.` };
|
|
701
361
|
}
|
|
702
362
|
// Recorded first: the cancelled result that follows owes the user nothing.
|
|
@@ -717,13 +377,10 @@ export class ForemanHost {
|
|
|
717
377
|
: `Cancel requested for task ${taskId}. The worker has not confirmed it stopped yet; do not tell the user it has stopped. The task is reported cancelled only after it does.`,
|
|
718
378
|
};
|
|
719
379
|
}
|
|
720
|
-
delivered(threadId, taskId, purpose
|
|
721
|
-
|
|
722
|
-
if (!dispatch)
|
|
380
|
+
delivered(threadId, taskId, purpose) {
|
|
381
|
+
if (!this.ownedDispatch(threadId, taskId))
|
|
723
382
|
return { ok: false, text: `no background task ${taskId} in this conversation` };
|
|
724
|
-
|
|
725
|
-
return fail("A parent Task delivery must identify its result event/version");
|
|
726
|
-
const satisfied = this.deps.store.markDelivered(taskId, purpose === "completion" ? "completion" : undefined, eventId);
|
|
383
|
+
const satisfied = this.deps.store.markDelivered(taskId, purpose === "completion" ? "completion" : undefined);
|
|
727
384
|
return { ok: true, text: String(satisfied) };
|
|
728
385
|
}
|
|
729
386
|
/** General's turn that handled these events ended: whatever it did not deliver is due now. */
|
|
@@ -743,13 +400,13 @@ export class ForemanHost {
|
|
|
743
400
|
}
|
|
744
401
|
/** Open tasks of this conversation, for General's wake context. Empty when none. */
|
|
745
402
|
summary(threadId) {
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
const recent = roots.filter((dispatch) => {
|
|
403
|
+
const open = this.deps.store.dispatchesForThread(threadId, 20).filter((dispatch) => {
|
|
404
|
+
const record = this.latest(dispatch.taskId);
|
|
749
405
|
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
750
|
-
return
|
|
751
|
-
|
|
752
|
-
|
|
406
|
+
return (record !== null && !isTerminalExecutionState(record.state))
|
|
407
|
+
|| delivery?.state === "pending" || delivery?.state === "delivery_failed";
|
|
408
|
+
});
|
|
409
|
+
if (open.length === 0)
|
|
753
410
|
return "";
|
|
754
411
|
let text = [
|
|
755
412
|
"## Background Tasks",
|
|
@@ -763,30 +420,15 @@ export class ForemanHost {
|
|
|
763
420
|
}
|
|
764
421
|
text += `\n${entry}`;
|
|
765
422
|
}
|
|
766
|
-
for (const dispatch of recent) {
|
|
767
|
-
const entry = `\n- Already delivered directly: ${dispatch.taskId} "${dispatch.title}": ${clip(this.deps.store.latest(dispatch.taskId)?.publicText ?? "", 1_000)}. Do not send it again.`;
|
|
768
|
-
if (text.length + entry.length <= SUMMARY_CHAR_BUDGET)
|
|
769
|
-
text += entry;
|
|
770
|
-
}
|
|
771
423
|
return text;
|
|
772
424
|
}
|
|
773
425
|
describeTask(dispatch, detail) {
|
|
774
|
-
if (dispatch.metadata.children?.length) {
|
|
775
|
-
const children = dispatch.metadata.children.map((id) => this.deps.store.dispatch(id))
|
|
776
|
-
.filter((item) => item !== null);
|
|
777
|
-
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
778
|
-
return [
|
|
779
|
-
`- ${dispatch.taskId} "${dispatch.title}": ${this.parentState(dispatch)} (${children.length} child Tasks)`,
|
|
780
|
-
...children.map((child) => ` ${this.describeTask(child, detail)}`),
|
|
781
|
-
...(delivery ? [` parent delivery: ${delivery.state} (event ${delivery.eventId})`] : []),
|
|
782
|
-
].join("\n");
|
|
783
|
-
}
|
|
784
426
|
const record = this.latest(dispatch.taskId);
|
|
785
427
|
const lines = [`- ${dispatch.taskId} "${dispatch.title}": ${record ? describeState(record) : "not started"}`];
|
|
786
|
-
if (dispatch.
|
|
787
|
-
lines.push(`
|
|
428
|
+
if (dispatch.worker)
|
|
429
|
+
lines.push(` worker: ${dispatch.worker}`);
|
|
788
430
|
if (record) {
|
|
789
|
-
const events = detail || record.state === "waiting_input" ? this.
|
|
431
|
+
const events = detail || record.state === "waiting_input" ? this.deps.executions.eventsAfter(record.executionId, 0) : [];
|
|
790
432
|
const newestFirst = [...events].reverse();
|
|
791
433
|
if (record.state === "waiting_input" && record.openQuestionId) {
|
|
792
434
|
const question = newestFirst.find((event) => event.type === "question" && event.payload.questionId === record.openQuestionId);
|
|
@@ -806,9 +448,6 @@ export class ForemanHost {
|
|
|
806
448
|
const result = record.result;
|
|
807
449
|
if (result?.summary)
|
|
808
450
|
lines.push(` result: ${clip(result.summary, detail ? 4_000 : 400)}`);
|
|
809
|
-
const prepared = detail ? publicTaskText({ type: "result", payload: { data: result?.data } }) : null;
|
|
810
|
-
if (prepared)
|
|
811
|
-
lines.push(` prepared answer: ${prepared}`);
|
|
812
451
|
if (result?.error)
|
|
813
452
|
lines.push(` error: ${result.error.code}: ${clip(result.error.message, 1_000)}`);
|
|
814
453
|
if (result?.artifactRefs?.length)
|
|
@@ -816,45 +455,15 @@ export class ForemanHost {
|
|
|
816
455
|
}
|
|
817
456
|
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
818
457
|
if (delivery && delivery.state !== "delivered" && delivery.state !== "withdrawn") {
|
|
819
|
-
lines.push(` delivery to the user: ${delivery.state}${delivery.lastError ? ` (${delivery.lastError})` : ""}
|
|
820
|
-
if (delivery.deliveryMode === "review" && delivery.state === "fallback_sent")
|
|
821
|
-
lines.push(" Only a status notice was sent; the result still requires General to review and deliver it.");
|
|
822
|
-
if (delivery.state === "transferred" && delivery.transferredToTaskId) {
|
|
823
|
-
lines.push(` result forwarded to Task ${delivery.transferredToTaskId} (input ${delivery.transferredToInputSeq ?? "?"}); the target owns final delivery.`);
|
|
824
|
-
}
|
|
458
|
+
lines.push(` delivery to the user: ${delivery.state}${delivery.lastError ? ` (${delivery.lastError})` : ""}`);
|
|
825
459
|
}
|
|
826
460
|
return lines.join("\n");
|
|
827
461
|
}
|
|
828
|
-
isOpen(dispatch) {
|
|
829
|
-
const record = this.latest(dispatch.taskId);
|
|
830
|
-
const workOpen = dispatch.metadata.children?.length
|
|
831
|
-
? this.childRecords(dispatch).some((child) => child.record !== null && !isTerminalExecutionState(child.record.state))
|
|
832
|
-
: record !== null && !isTerminalExecutionState(record.state);
|
|
833
|
-
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
834
|
-
return workOpen || delivery?.state === "pending" || delivery?.state === "delivery_failed"
|
|
835
|
-
|| (delivery?.state === "fallback_sent" && delivery.deliveryMode === "review" && delivery.kind === "result");
|
|
836
|
-
}
|
|
837
|
-
allEvents(executionId) {
|
|
838
|
-
const events = [];
|
|
839
|
-
let after = 0;
|
|
840
|
-
for (;;) {
|
|
841
|
-
const page = this.deps.executions.eventsAfter(executionId, after);
|
|
842
|
-
const last = page.at(-1)?.seq ?? after;
|
|
843
|
-
if (last <= after)
|
|
844
|
-
break;
|
|
845
|
-
events.push(...page);
|
|
846
|
-
after = last;
|
|
847
|
-
if (page.length < 500)
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
return events;
|
|
851
|
-
}
|
|
852
462
|
/** The system message that brings a question or result to the delegating thread. */
|
|
853
463
|
eventMessage(event, threadId, dispatch, obligationOpens) {
|
|
854
464
|
const payload = event.payload;
|
|
855
465
|
const title = dispatch?.title ?? event.taskId;
|
|
856
|
-
const
|
|
857
|
-
const quote = `${taskQuote}, event_id="${event.eventId}"`;
|
|
466
|
+
const quote = `task_id="${event.taskId}"`;
|
|
858
467
|
const lines = [`[Background task update] ${event.type === "question" ? "Question from" : "Result of"} background task ${event.taskId} "${title}"`];
|
|
859
468
|
if (dispatch?.sender)
|
|
860
469
|
lines.push(`Requested by: ${dispatch.sender}`);
|
|
@@ -863,16 +472,13 @@ export class ForemanHost {
|
|
|
863
472
|
if (Array.isArray(payload.choices) && payload.choices.length > 0) {
|
|
864
473
|
lines.push(`Choices: ${payload.choices.map(String).join(" / ")}`);
|
|
865
474
|
}
|
|
866
|
-
lines.push(`If you can answer from what you know, answer with background_task_input(${
|
|
475
|
+
lines.push(`If you can answer from what you know, answer with background_task_input(${quote}). Otherwise ask the requester with notify_user(${quote}) and pass their answer on. Do not do the task's work here.`);
|
|
867
476
|
}
|
|
868
477
|
else {
|
|
869
478
|
const status = String(payload.status);
|
|
870
479
|
lines.push(`Status: ${status}`);
|
|
871
480
|
if (typeof payload.summary === "string")
|
|
872
481
|
lines.push(`Summary: ${clip(payload.summary, 8_000)}`);
|
|
873
|
-
const preparedReply = publicTaskText(event);
|
|
874
|
-
if (preparedReply)
|
|
875
|
-
lines.push(`Prepared reply for the requester (review before sending): ${preparedReply}`);
|
|
876
482
|
const error = payload.error;
|
|
877
483
|
if (error && typeof error === "object") {
|
|
878
484
|
const { code, message } = error;
|
|
@@ -881,15 +487,12 @@ export class ForemanHost {
|
|
|
881
487
|
if (Array.isArray(payload.artifactRefs) && payload.artifactRefs.length > 0) {
|
|
882
488
|
lines.push(`Artifacts: ${payload.artifactRefs.slice(0, 30).map(String).join(", ")}`);
|
|
883
489
|
}
|
|
884
|
-
if (status === "
|
|
885
|
-
lines.push("The requester asked to stop this task; nothing more is needed unless they asked to hear back.");
|
|
886
|
-
}
|
|
887
|
-
else if (dispatch?.metadata.children?.length) {
|
|
888
|
-
lines.push(`Review the child results together. Deliver the overall result with notify_user(${quote}, purpose="completion"). Retry only children that need further work using their child task_id; do not repeat successful children. The parent does not occupy a worker.`);
|
|
889
|
-
}
|
|
890
|
-
else if (status === "succeeded") {
|
|
490
|
+
if (status === "succeeded") {
|
|
891
491
|
lines.push(`Check the result against the request before you deliver it. If it holds up, tell the requester with notify_user(${quote}, purpose="completion") in your own words, and send any drafts it contains with the right tool. If it falls short, run it again with delegate_task(retry_task_id="${event.taskId}") and say what must change.`);
|
|
892
492
|
}
|
|
493
|
+
else if (status === "cancelled" && !obligationOpens) {
|
|
494
|
+
lines.push("The requester asked to stop this task; nothing more is needed unless they asked to hear back.");
|
|
495
|
+
}
|
|
893
496
|
else {
|
|
894
497
|
lines.push(`Tell the requester what happened with notify_user(${quote}, purpose="completion"), or run it again with delegate_task(retry_task_id="${event.taskId}") if a retry can succeed.`);
|
|
895
498
|
}
|
|
@@ -908,7 +511,15 @@ export class ForemanHost {
|
|
|
908
511
|
attachments: [],
|
|
909
512
|
timestamp: event.createdAt,
|
|
910
513
|
...routing,
|
|
911
|
-
meta: {
|
|
514
|
+
meta: {
|
|
515
|
+
...routing.meta,
|
|
516
|
+
[FOREMAN_EVENT_META_KEY]: {
|
|
517
|
+
taskId: event.taskId,
|
|
518
|
+
eventId: event.eventId,
|
|
519
|
+
kind: event.type,
|
|
520
|
+
...(event.type === "result" ? { status: String(payload.status) } : {}),
|
|
521
|
+
},
|
|
522
|
+
},
|
|
912
523
|
...(threadId !== DEFAULT_THREAD_ID ? { threadId } : {}),
|
|
913
524
|
};
|
|
914
525
|
}
|