visionclaw 0.1.219-dev.codex-issue-2805-worker-runtime.2 → 0.1.219-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 +59 -423
- package/dist/threads/foreman-host.js.map +1 -1
- package/dist/threads/foreman-protocol.d.ts +12 -109
- package/dist/threads/foreman-protocol.d.ts.map +1 -1
- package/dist/threads/foreman-protocol.js +15 -37
- package/dist/threads/foreman-protocol.js.map +1 -1
- package/dist/threads/foreman-store.d.ts +9 -36
- package/dist/threads/foreman-store.d.ts.map +1 -1
- package/dist/threads/foreman-store.js +19 -63
- 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 -71
- package/dist/tools/delegation.d.ts.map +1 -1
- package/dist/tools/delegation.js +28 -62
- 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") };
|
|
@@ -617,11 +317,8 @@ export class ForemanHost {
|
|
|
617
317
|
if (!parsed.success)
|
|
618
318
|
return invalid(parsed.error);
|
|
619
319
|
const { task_id: taskId, text } = parsed.data;
|
|
620
|
-
|
|
621
|
-
if (!dispatch)
|
|
320
|
+
if (!this.ownedDispatch(threadId, taskId))
|
|
622
321
|
return fail(`no background task ${taskId} in this conversation`);
|
|
623
|
-
if (dispatch.metadata.children?.length)
|
|
624
|
-
return fail(`Send the answer or requirement to a child Task: ${dispatch.metadata.children.join(", ")}. Each child owns its input cursor.`);
|
|
625
322
|
const record = this.latest(taskId);
|
|
626
323
|
if (!record)
|
|
627
324
|
return fail(`task ${taskId} has not started`);
|
|
@@ -656,25 +353,10 @@ export class ForemanHost {
|
|
|
656
353
|
if (!parsed.success)
|
|
657
354
|
return invalid(parsed.error);
|
|
658
355
|
const { task_id: taskId, requested_by_user: byUser, reason } = parsed.data;
|
|
659
|
-
|
|
660
|
-
if (!dispatch)
|
|
356
|
+
if (!this.ownedDispatch(threadId, taskId))
|
|
661
357
|
return fail(`no background task ${taskId} in this conversation`);
|
|
662
|
-
if (dispatch.metadata.children?.length) {
|
|
663
|
-
if (!this.isOpen(dispatch))
|
|
664
|
-
return { ok: true, text: `Task ${taskId} has already finished (${this.parentState(dispatch)}).` };
|
|
665
|
-
if (byUser)
|
|
666
|
-
this.deps.store.markUserCancelled(taskId);
|
|
667
|
-
this.deps.store.withdraw(taskId);
|
|
668
|
-
this.deps.store.requestGroupCancel(taskId, reason ?? "Parent task cancelled", byUser);
|
|
669
|
-
this.cancelChildren(dispatch, reason ?? "Parent task cancelled", byUser);
|
|
670
|
-
return { ok: true, text: `Cancel requested for all unfinished children of ${taskId}. Running children must confirm their stop before the parent is cancelled.` };
|
|
671
|
-
}
|
|
672
358
|
const record = this.latest(taskId);
|
|
673
359
|
if (!record || isTerminalExecutionState(record.state)) {
|
|
674
|
-
if (byUser) {
|
|
675
|
-
this.deps.store.markUserCancelled(taskId);
|
|
676
|
-
this.deps.store.withdraw(taskId);
|
|
677
|
-
}
|
|
678
360
|
return { ok: true, text: `Task ${taskId} is not running (${record ? record.state : "never started"}); nothing to cancel.` };
|
|
679
361
|
}
|
|
680
362
|
// Recorded first: the cancelled result that follows owes the user nothing.
|
|
@@ -695,13 +377,10 @@ export class ForemanHost {
|
|
|
695
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.`,
|
|
696
378
|
};
|
|
697
379
|
}
|
|
698
|
-
delivered(threadId, taskId, purpose
|
|
699
|
-
|
|
700
|
-
if (!dispatch)
|
|
380
|
+
delivered(threadId, taskId, purpose) {
|
|
381
|
+
if (!this.ownedDispatch(threadId, taskId))
|
|
701
382
|
return { ok: false, text: `no background task ${taskId} in this conversation` };
|
|
702
|
-
|
|
703
|
-
return fail("A parent Task delivery must identify its result event/version");
|
|
704
|
-
const satisfied = this.deps.store.markDelivered(taskId, purpose === "completion" ? "completion" : undefined, eventId);
|
|
383
|
+
const satisfied = this.deps.store.markDelivered(taskId, purpose === "completion" ? "completion" : undefined);
|
|
705
384
|
return { ok: true, text: String(satisfied) };
|
|
706
385
|
}
|
|
707
386
|
/** General's turn that handled these events ended: whatever it did not deliver is due now. */
|
|
@@ -721,13 +400,13 @@ export class ForemanHost {
|
|
|
721
400
|
}
|
|
722
401
|
/** Open tasks of this conversation, for General's wake context. Empty when none. */
|
|
723
402
|
summary(threadId) {
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
const recent = roots.filter((dispatch) => {
|
|
403
|
+
const open = this.deps.store.dispatchesForThread(threadId, 20).filter((dispatch) => {
|
|
404
|
+
const record = this.latest(dispatch.taskId);
|
|
727
405
|
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
728
|
-
return
|
|
729
|
-
|
|
730
|
-
|
|
406
|
+
return (record !== null && !isTerminalExecutionState(record.state))
|
|
407
|
+
|| delivery?.state === "pending" || delivery?.state === "delivery_failed";
|
|
408
|
+
});
|
|
409
|
+
if (open.length === 0)
|
|
731
410
|
return "";
|
|
732
411
|
let text = [
|
|
733
412
|
"## Background Tasks",
|
|
@@ -741,30 +420,15 @@ export class ForemanHost {
|
|
|
741
420
|
}
|
|
742
421
|
text += `\n${entry}`;
|
|
743
422
|
}
|
|
744
|
-
for (const dispatch of recent) {
|
|
745
|
-
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.`;
|
|
746
|
-
if (text.length + entry.length <= SUMMARY_CHAR_BUDGET)
|
|
747
|
-
text += entry;
|
|
748
|
-
}
|
|
749
423
|
return text;
|
|
750
424
|
}
|
|
751
425
|
describeTask(dispatch, detail) {
|
|
752
|
-
if (dispatch.metadata.children?.length) {
|
|
753
|
-
const children = dispatch.metadata.children.map((id) => this.deps.store.dispatch(id))
|
|
754
|
-
.filter((item) => item !== null);
|
|
755
|
-
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
756
|
-
return [
|
|
757
|
-
`- ${dispatch.taskId} "${dispatch.title}": ${this.parentState(dispatch)} (${children.length} child Tasks)`,
|
|
758
|
-
...children.map((child) => ` ${this.describeTask(child, detail)}`),
|
|
759
|
-
...(delivery ? [` parent delivery: ${delivery.state} (event ${delivery.eventId})`] : []),
|
|
760
|
-
].join("\n");
|
|
761
|
-
}
|
|
762
426
|
const record = this.latest(dispatch.taskId);
|
|
763
427
|
const lines = [`- ${dispatch.taskId} "${dispatch.title}": ${record ? describeState(record) : "not started"}`];
|
|
764
|
-
if (dispatch.
|
|
765
|
-
lines.push(`
|
|
428
|
+
if (dispatch.worker)
|
|
429
|
+
lines.push(` worker: ${dispatch.worker}`);
|
|
766
430
|
if (record) {
|
|
767
|
-
const events = detail || record.state === "waiting_input" ? this.
|
|
431
|
+
const events = detail || record.state === "waiting_input" ? this.deps.executions.eventsAfter(record.executionId, 0) : [];
|
|
768
432
|
const newestFirst = [...events].reverse();
|
|
769
433
|
if (record.state === "waiting_input" && record.openQuestionId) {
|
|
770
434
|
const question = newestFirst.find((event) => event.type === "question" && event.payload.questionId === record.openQuestionId);
|
|
@@ -784,9 +448,6 @@ export class ForemanHost {
|
|
|
784
448
|
const result = record.result;
|
|
785
449
|
if (result?.summary)
|
|
786
450
|
lines.push(` result: ${clip(result.summary, detail ? 4_000 : 400)}`);
|
|
787
|
-
const prepared = detail ? publicTaskText({ type: "result", payload: { data: result?.data } }) : null;
|
|
788
|
-
if (prepared)
|
|
789
|
-
lines.push(` prepared answer: ${prepared}`);
|
|
790
451
|
if (result?.error)
|
|
791
452
|
lines.push(` error: ${result.error.code}: ${clip(result.error.message, 1_000)}`);
|
|
792
453
|
if (result?.artifactRefs?.length)
|
|
@@ -794,42 +455,15 @@ export class ForemanHost {
|
|
|
794
455
|
}
|
|
795
456
|
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
796
457
|
if (delivery && delivery.state !== "delivered" && delivery.state !== "withdrawn") {
|
|
797
|
-
lines.push(` delivery to the user: ${delivery.state}${delivery.lastError ? ` (${delivery.lastError})` : ""}
|
|
798
|
-
if (delivery.deliveryMode === "review" && delivery.state === "fallback_sent")
|
|
799
|
-
lines.push(" Only a status notice was sent; the result still requires General to review and deliver it.");
|
|
458
|
+
lines.push(` delivery to the user: ${delivery.state}${delivery.lastError ? ` (${delivery.lastError})` : ""}`);
|
|
800
459
|
}
|
|
801
460
|
return lines.join("\n");
|
|
802
461
|
}
|
|
803
|
-
isOpen(dispatch) {
|
|
804
|
-
const record = this.latest(dispatch.taskId);
|
|
805
|
-
const workOpen = dispatch.metadata.children?.length
|
|
806
|
-
? this.childRecords(dispatch).some((child) => child.record !== null && !isTerminalExecutionState(child.record.state))
|
|
807
|
-
: record !== null && !isTerminalExecutionState(record.state);
|
|
808
|
-
const delivery = this.deps.store.latest(dispatch.taskId);
|
|
809
|
-
return workOpen || delivery?.state === "pending" || delivery?.state === "delivery_failed"
|
|
810
|
-
|| (delivery?.state === "fallback_sent" && delivery.deliveryMode === "review" && delivery.kind === "result");
|
|
811
|
-
}
|
|
812
|
-
allEvents(executionId) {
|
|
813
|
-
const events = [];
|
|
814
|
-
let after = 0;
|
|
815
|
-
for (;;) {
|
|
816
|
-
const page = this.deps.executions.eventsAfter(executionId, after);
|
|
817
|
-
const last = page.at(-1)?.seq ?? after;
|
|
818
|
-
if (last <= after)
|
|
819
|
-
break;
|
|
820
|
-
events.push(...page);
|
|
821
|
-
after = last;
|
|
822
|
-
if (page.length < 500)
|
|
823
|
-
break;
|
|
824
|
-
}
|
|
825
|
-
return events;
|
|
826
|
-
}
|
|
827
462
|
/** The system message that brings a question or result to the delegating thread. */
|
|
828
463
|
eventMessage(event, threadId, dispatch, obligationOpens) {
|
|
829
464
|
const payload = event.payload;
|
|
830
465
|
const title = dispatch?.title ?? event.taskId;
|
|
831
|
-
const
|
|
832
|
-
const quote = `${taskQuote}, event_id="${event.eventId}"`;
|
|
466
|
+
const quote = `task_id="${event.taskId}"`;
|
|
833
467
|
const lines = [`[Background task update] ${event.type === "question" ? "Question from" : "Result of"} background task ${event.taskId} "${title}"`];
|
|
834
468
|
if (dispatch?.sender)
|
|
835
469
|
lines.push(`Requested by: ${dispatch.sender}`);
|
|
@@ -838,16 +472,13 @@ export class ForemanHost {
|
|
|
838
472
|
if (Array.isArray(payload.choices) && payload.choices.length > 0) {
|
|
839
473
|
lines.push(`Choices: ${payload.choices.map(String).join(" / ")}`);
|
|
840
474
|
}
|
|
841
|
-
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.`);
|
|
842
476
|
}
|
|
843
477
|
else {
|
|
844
478
|
const status = String(payload.status);
|
|
845
479
|
lines.push(`Status: ${status}`);
|
|
846
480
|
if (typeof payload.summary === "string")
|
|
847
481
|
lines.push(`Summary: ${clip(payload.summary, 8_000)}`);
|
|
848
|
-
const preparedReply = publicTaskText(event);
|
|
849
|
-
if (preparedReply)
|
|
850
|
-
lines.push(`Prepared reply for the requester (review before sending): ${preparedReply}`);
|
|
851
482
|
const error = payload.error;
|
|
852
483
|
if (error && typeof error === "object") {
|
|
853
484
|
const { code, message } = error;
|
|
@@ -856,15 +487,12 @@ export class ForemanHost {
|
|
|
856
487
|
if (Array.isArray(payload.artifactRefs) && payload.artifactRefs.length > 0) {
|
|
857
488
|
lines.push(`Artifacts: ${payload.artifactRefs.slice(0, 30).map(String).join(", ")}`);
|
|
858
489
|
}
|
|
859
|
-
if (status === "
|
|
860
|
-
lines.push("The requester asked to stop this task; nothing more is needed unless they asked to hear back.");
|
|
861
|
-
}
|
|
862
|
-
else if (dispatch?.metadata.children?.length) {
|
|
863
|
-
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.`);
|
|
864
|
-
}
|
|
865
|
-
else if (status === "succeeded") {
|
|
490
|
+
if (status === "succeeded") {
|
|
866
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.`);
|
|
867
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
|
+
}
|
|
868
496
|
else {
|
|
869
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.`);
|
|
870
498
|
}
|
|
@@ -883,7 +511,15 @@ export class ForemanHost {
|
|
|
883
511
|
attachments: [],
|
|
884
512
|
timestamp: event.createdAt,
|
|
885
513
|
...routing,
|
|
886
|
-
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
|
+
},
|
|
887
523
|
...(threadId !== DEFAULT_THREAD_ID ? { threadId } : {}),
|
|
888
524
|
};
|
|
889
525
|
}
|