@iowarp/clio-coder 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +407 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +224 -0
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +798 -0
- package/SECURITY.md +72 -0
- package/assets/clio-coder-logo-128.webp +0 -0
- package/damage-control-rules.yaml +419 -0
- package/dist/acp-UMLFVA3F.js +92 -0
- package/dist/agents-Q4MYPMUW.js +91 -0
- package/dist/auth-O6HYIJ6J.js +521 -0
- package/dist/chunk-262G75JS.js +35 -0
- package/dist/chunk-26BZQOAD.js +1281 -0
- package/dist/chunk-2J63S4SF.js +508 -0
- package/dist/chunk-3DANZDGR.js +717 -0
- package/dist/chunk-4UQA7NCT.js +29 -0
- package/dist/chunk-527KG6XR.js +497 -0
- package/dist/chunk-5LDRNKX2.js +1063 -0
- package/dist/chunk-5N2FG33Q.js +25 -0
- package/dist/chunk-67MTHP2E.js +135 -0
- package/dist/chunk-6CWDTGUC.js +20 -0
- package/dist/chunk-7BHLZB3A.js +2115 -0
- package/dist/chunk-7RBKDI66.js +348 -0
- package/dist/chunk-AMFR5YA3.js +541 -0
- package/dist/chunk-BBUH4VAA.js +1224 -0
- package/dist/chunk-BYEU76JP.js +899 -0
- package/dist/chunk-CLJ5HLUD.js +458 -0
- package/dist/chunk-D5YD55AR.js +116 -0
- package/dist/chunk-DXQNI4PC.js +61 -0
- package/dist/chunk-E3NYWENM.js +1004 -0
- package/dist/chunk-GNGDQYDU.js +34688 -0
- package/dist/chunk-GOTUR54M.js +9 -0
- package/dist/chunk-HBU5MTAM.js +41 -0
- package/dist/chunk-HMYNFFY4.js +28 -0
- package/dist/chunk-JPOWPFCU.js +1010 -0
- package/dist/chunk-JWHCJDCI.js +1215 -0
- package/dist/chunk-KBR4MZZR.js +41 -0
- package/dist/chunk-KKKPTZLM.js +93 -0
- package/dist/chunk-ME6DNWIU.js +66 -0
- package/dist/chunk-NI4DEJMC.js +88 -0
- package/dist/chunk-O4EJEDHO.js +659 -0
- package/dist/chunk-PIDUD6M2.js +31 -0
- package/dist/chunk-PS4PFJQP.js +29459 -0
- package/dist/chunk-QV47YRF4.js +48 -0
- package/dist/chunk-RQDWMVRB.js +279 -0
- package/dist/chunk-TFSSEXL6.js +136 -0
- package/dist/chunk-TKHQ4DGZ.js +8290 -0
- package/dist/chunk-TPOCL34A.js +2876 -0
- package/dist/chunk-UGYAX5YI.js +565 -0
- package/dist/chunk-UHTSULZS.js +461 -0
- package/dist/chunk-UU3R62TT.js +128 -0
- package/dist/chunk-UWIJNAOB.js +3906 -0
- package/dist/chunk-VOO7NYPP.js +914 -0
- package/dist/chunk-VPAWTYLY.js +117 -0
- package/dist/chunk-WD6AJM35.js +1216 -0
- package/dist/chunk-X3BR7HWV.js +115 -0
- package/dist/chunk-X3NE4WVW.js +120 -0
- package/dist/chunk-XNISANGE.js +1395 -0
- package/dist/chunk-XV4ZJ6ZM.js +3177 -0
- package/dist/cli/index.js +236 -0
- package/dist/clio-KIQ5SNDS.js +53 -0
- package/dist/components-JVHMUBEB.js +653 -0
- package/dist/config-ZFCDBMDC.js +372 -0
- package/dist/configure-G4E3A2PG.js +27 -0
- package/dist/context-CDXTP2MP.js +293 -0
- package/dist/context-E3KIFVXI.js +185 -0
- package/dist/context-clear-3F4PLXOS.js +102 -0
- package/dist/context-index-Q7YSYTR3.js +106 -0
- package/dist/docs-YIETIWZI.js +280 -0
- package/dist/doctor-M5HJJZOL.js +61 -0
- package/dist/domains/agents/builtins/architect.md +33 -0
- package/dist/domains/agents/builtins/coder.md +31 -0
- package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
- package/dist/domains/agents/builtins/debugger.md +30 -0
- package/dist/domains/agents/builtins/documenter.md +31 -0
- package/dist/domains/agents/builtins/git-master.md +30 -0
- package/dist/domains/agents/builtins/provenance.md +30 -0
- package/dist/domains/agents/builtins/researcher.md +71 -0
- package/dist/domains/agents/builtins/scout.md +42 -0
- package/dist/domains/agents/builtins/tester.md +31 -0
- package/dist/domains/agents/builtins/verifier.md +30 -0
- package/dist/domains/agents/builtins/wiki-writer.md +41 -0
- package/dist/eval-B3KZZESM.js +2674 -0
- package/dist/evidence-V67CHM35.js +233 -0
- package/dist/evolve-YDZSUQYA.js +518 -0
- package/dist/extensions-SRG7XCAH.js +207 -0
- package/dist/fleet-CA2CRTVG.js +760 -0
- package/dist/fleet-preflight-CLIAX7YR.js +21 -0
- package/dist/init-2OZDJE2D.js +227 -0
- package/dist/memory-3PIQQAKX.js +207 -0
- package/dist/models-DY35XI7Y.js +237 -0
- package/dist/paths-5OMXW7Z4.js +57 -0
- package/dist/preload-KZVHET2B.js +11 -0
- package/dist/reset-PIFYNOS3.js +216 -0
- package/dist/run-3VSPP24F.js +735 -0
- package/dist/share-D36RQCXM.js +241 -0
- package/dist/skills-F2MRLELY.js +445 -0
- package/dist/skills-eval-E2ZTW4PL.js +932 -0
- package/dist/targets-DZMEZAH4.js +977 -0
- package/dist/trace-7NYCUI2J.js +250 -0
- package/dist/uninstall-AD3JWHBB.js +322 -0
- package/dist/upgrade-WYYBKGDY.js +301 -0
- package/dist/usage-ULIDAGFF.js +755 -0
- package/dist/version-ROZ6CZKH.js +16 -0
- package/dist/wiki-generate-PKFIX6OB.js +377 -0
- package/dist/worker/entry.js +1739 -0
- package/docs/README.md +93 -0
- package/docs/acp.md +120 -0
- package/docs/alcf-provider.md +72 -0
- package/docs/architecture.md +172 -0
- package/docs/artifact-versions.md +54 -0
- package/docs/built-in-agents.md +265 -0
- package/docs/capacity-and-scheduling.md +97 -0
- package/docs/commands-and-modes.md +554 -0
- package/docs/config-knobs-audit.md +115 -0
- package/docs/configuration-and-targets.md +812 -0
- package/docs/context-engine.md +236 -0
- package/docs/dispatch-architecture-rationale.md +126 -0
- package/docs/documentation-coverage.md +46 -0
- package/docs/documentation-guide.md +166 -0
- package/docs/environment-variables.md +105 -0
- package/docs/eval-runner.md +205 -0
- package/docs/evals-internal.md +298 -0
- package/docs/evidence-and-memory.md +243 -0
- package/docs/evolution.md +143 -0
- package/docs/exit-codes-and-output.md +74 -0
- package/docs/extensions-and-sharing.md +306 -0
- package/docs/fleet-demo-runbook.md +179 -0
- package/docs/fleet-dispatch.md +591 -0
- package/docs/glossary.md +75 -0
- package/docs/html/agents_blueprint.html +936 -0
- package/docs/html/alcf_blueprint.html +324 -0
- package/docs/html/architecture_blueprint.html +850 -0
- package/docs/html/commands_blueprint.html +794 -0
- package/docs/html/config_knobs_audit_blueprint.html +178 -0
- package/docs/html/configuration_blueprint.html +1080 -0
- package/docs/html/context_blueprint.html +603 -0
- package/docs/html/documentation_blueprint.html +832 -0
- package/docs/html/environment_blueprint.html +404 -0
- package/docs/html/eval_blueprint.html +743 -0
- package/docs/html/evals_internal_blueprint.html +190 -0
- package/docs/html/evolution_blueprint.html +674 -0
- package/docs/html/extensions_blueprint.html +2065 -0
- package/docs/html/fleet_dispatch_blueprint.html +286 -0
- package/docs/html/index.html +919 -0
- package/docs/html/lifecycle_blueprint.html +723 -0
- package/docs/html/memory_blueprint.html +699 -0
- package/docs/html/middleware_blueprint.html +664 -0
- package/docs/html/models_blueprint.html +2366 -0
- package/docs/html/observability_blueprint.html +683 -0
- package/docs/html/provider_adapter_blueprint.html +245 -0
- package/docs/html/safety_blueprint.html +1386 -0
- package/docs/html/shared.css +571 -0
- package/docs/html/shared.js +143 -0
- package/docs/html/skills_blueprint.html +671 -0
- package/docs/html/soak_blueprint.html +182 -0
- package/docs/html/tool_usage_blueprint.html +350 -0
- package/docs/html/tools_blueprint.html +2249 -0
- package/docs/html/trace_blueprint.html +235 -0
- package/docs/html/tui_design_blueprint.html +314 -0
- package/docs/html/validation_blueprint.html +961 -0
- package/docs/html/worker_dispatch_blueprint.html +231 -0
- package/docs/installation-and-lifecycle.md +308 -0
- package/docs/middleware-and-components.md +148 -0
- package/docs/model-catalog.md +189 -0
- package/docs/observability.md +233 -0
- package/docs/proactive-memory.md +452 -0
- package/docs/prompt-envelope-and-tools.md +142 -0
- package/docs/provider-adapter-cookbook.md +148 -0
- package/docs/release-cut-checklist.md +138 -0
- package/docs/safety-model.md +357 -0
- package/docs/scientific-validation.md +105 -0
- package/docs/session-lifecycle.md +156 -0
- package/docs/skills-marketplace.md +46 -0
- package/docs/tool-usage.md +527 -0
- package/docs/trace-store.md +132 -0
- package/docs/troubleshooting.md +33 -0
- package/docs/tui-design.md +239 -0
- package/docs/worker-dispatch-mechanics.md +242 -0
- package/package.json +132 -0
- package/skills/README.md +408 -0
- package/skills/git/commit-crafting/SKILL.md +79 -0
- package/skills/git/commit-crafting/evals.md +92 -0
- package/skills/git/create-pr/SKILL.md +116 -0
- package/skills/git/create-pr/evals.md +114 -0
- package/skills/git/investigate-issue/SKILL.md +139 -0
- package/skills/git/investigate-issue/evals.md +94 -0
- package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
- package/skills/git/resolve-merge-conflicts/evals.md +58 -0
- package/skills/git/review-changes/SKILL.md +103 -0
- package/skills/git/review-changes/evals.md +85 -0
- package/skills/git/worktree-create/SKILL.md +92 -0
- package/skills/git/worktree-create/evals.md +97 -0
- package/skills/git/worktree-create/references/worktree-setup.md +66 -0
- package/skills/git/worktree-merge/SKILL.md +95 -0
- package/skills/git/worktree-merge/evals.md +114 -0
- package/skills/skill-marketplace.json +261 -0
- package/skills/workflow/cut-it/SKILL.md +86 -0
- package/skills/workflow/cut-it/evals.md +42 -0
- package/src/domains/agents/builtins/architect.md +33 -0
- package/src/domains/agents/builtins/coder.md +31 -0
- package/src/domains/agents/builtins/context-bootstrap.md +38 -0
- package/src/domains/agents/builtins/debugger.md +30 -0
- package/src/domains/agents/builtins/documenter.md +31 -0
- package/src/domains/agents/builtins/git-master.md +30 -0
- package/src/domains/agents/builtins/provenance.md +30 -0
- package/src/domains/agents/builtins/researcher.md +71 -0
- package/src/domains/agents/builtins/scout.md +42 -0
- package/src/domains/agents/builtins/tester.md +31 -0
- package/src/domains/agents/builtins/verifier.md +30 -0
- package/src/domains/agents/builtins/wiki-writer.md +41 -0
- package/src/domains/agents/fleets/build-review.md +34 -0
- package/src/domains/agents/fleets/build-test.md +35 -0
- package/src/domains/agents/fleets/sdlc.md +86 -0
- package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
- package/src/domains/prompts/fragments/identity/clio.md +26 -0
- package/src/domains/prompts/fragments/operating/contract.md +64 -0
- package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
- package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
- package/src/domains/prompts/fragments/safety/read-only.md +13 -0
- package/src/domains/prompts/fragments/safety/suggest.md +13 -0
- package/src/domains/prompts/fragments/wiki/page.md +75 -0
- package/src/domains/prompts/fragments/wiki/plan.md +48 -0
- package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
|
@@ -0,0 +1,1739 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createEngineAgent,
|
|
4
|
+
createLoopGuardRegistration,
|
|
5
|
+
createProtectedArtifactsRegistration,
|
|
6
|
+
describeCallTarget,
|
|
7
|
+
isLoopGuardSynthesisBackstopReason,
|
|
8
|
+
isReserveAdmittedTool,
|
|
9
|
+
patchWorkerRequestPayload,
|
|
10
|
+
resolveDeliveryTools,
|
|
11
|
+
sanitizeLockedSynthesisMessage,
|
|
12
|
+
workerLoopBlockBudget
|
|
13
|
+
} from "../chunk-BYEU76JP.js";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_ESCALATION_FALLBACK,
|
|
16
|
+
DEFAULT_ESCALATION_TIMEOUT_MS,
|
|
17
|
+
WORKER_EXIT_PERMISSION_REQUIRED,
|
|
18
|
+
agentSkillToolPolicy,
|
|
19
|
+
attestedToolSignature,
|
|
20
|
+
claudeToolsOutsideProfile,
|
|
21
|
+
coerceToolInput,
|
|
22
|
+
createMiddlewareToolChoiceControl,
|
|
23
|
+
createRunEffectsRecorder,
|
|
24
|
+
createWorkerSafety,
|
|
25
|
+
createWorkerToolRegistry,
|
|
26
|
+
emitClaudeToolPermissionDecision,
|
|
27
|
+
isClaudeCodeSessionId,
|
|
28
|
+
parseWorkerSpec,
|
|
29
|
+
resolveAgentTools,
|
|
30
|
+
runtimeSpeaksResponseSchemaDialect,
|
|
31
|
+
shouldRequestStalledTurnContinuation,
|
|
32
|
+
startAntigravityWorkerRun,
|
|
33
|
+
startClaudeCodeWorkerRun,
|
|
34
|
+
validateRehydratedWorkerRuntime
|
|
35
|
+
} from "../chunk-GNGDQYDU.js";
|
|
36
|
+
import "../chunk-2J63S4SF.js";
|
|
37
|
+
import "../chunk-RQDWMVRB.js";
|
|
38
|
+
import "../chunk-7BHLZB3A.js";
|
|
39
|
+
import "../chunk-TPOCL34A.js";
|
|
40
|
+
import "../chunk-E3NYWENM.js";
|
|
41
|
+
import {
|
|
42
|
+
RESULT_CONTRACT_REPAIR_LIMIT,
|
|
43
|
+
parseResultContract,
|
|
44
|
+
resultContractRepairMessage,
|
|
45
|
+
validateResultContract
|
|
46
|
+
} from "../chunk-XV4ZJ6ZM.js";
|
|
47
|
+
import "../chunk-26BZQOAD.js";
|
|
48
|
+
import "../chunk-WD6AJM35.js";
|
|
49
|
+
import {
|
|
50
|
+
ToolNames
|
|
51
|
+
} from "../chunk-QV47YRF4.js";
|
|
52
|
+
import "../chunk-CLJ5HLUD.js";
|
|
53
|
+
import {
|
|
54
|
+
UNKNOWN_RESOURCE,
|
|
55
|
+
WORKER_PROTOCOL_VERSION,
|
|
56
|
+
WORKER_RESOURCE_LABEL_MAX,
|
|
57
|
+
WORKER_STDIN_FRAME_MAX_BYTES,
|
|
58
|
+
encodeControlFrame,
|
|
59
|
+
endpointIdentityHash,
|
|
60
|
+
knownResource,
|
|
61
|
+
parseAgentLedgerBody,
|
|
62
|
+
parseAgentLedgerEntry,
|
|
63
|
+
withinFrameBudget,
|
|
64
|
+
workerSpecDigest
|
|
65
|
+
} from "../chunk-BBUH4VAA.js";
|
|
66
|
+
import "../chunk-GOTUR54M.js";
|
|
67
|
+
import {
|
|
68
|
+
FileKnowledgeBase,
|
|
69
|
+
applyModelCapabilityPatch,
|
|
70
|
+
disposeLmStudioClients,
|
|
71
|
+
getRuntimeRegistry,
|
|
72
|
+
loadPluginRuntimes,
|
|
73
|
+
registerBuiltinRuntimes,
|
|
74
|
+
registerClioApiProviders,
|
|
75
|
+
registerFauxFromEnv,
|
|
76
|
+
resolveModelRuntimeCapabilitiesForModel,
|
|
77
|
+
resolveProviderKnowledgeBaseRoots,
|
|
78
|
+
setGlobalDefaultMaxOutputTokens,
|
|
79
|
+
setProtectedModelsProvider,
|
|
80
|
+
setResidencyNoticeSink
|
|
81
|
+
} from "../chunk-TKHQ4DGZ.js";
|
|
82
|
+
import "../chunk-D5YD55AR.js";
|
|
83
|
+
import {
|
|
84
|
+
readSettings,
|
|
85
|
+
validateSettingsFile
|
|
86
|
+
} from "../chunk-JWHCJDCI.js";
|
|
87
|
+
import {
|
|
88
|
+
configureGuardrails,
|
|
89
|
+
isWorkerToolCallCapExceededReason,
|
|
90
|
+
isWorkerToolCallCapSynthesisReason
|
|
91
|
+
} from "../chunk-UHTSULZS.js";
|
|
92
|
+
import "../chunk-5LDRNKX2.js";
|
|
93
|
+
import "../chunk-UGYAX5YI.js";
|
|
94
|
+
import "../chunk-5N2FG33Q.js";
|
|
95
|
+
import "../chunk-VPAWTYLY.js";
|
|
96
|
+
import "../chunk-X3NE4WVW.js";
|
|
97
|
+
import {
|
|
98
|
+
readClioVersion
|
|
99
|
+
} from "../chunk-DXQNI4PC.js";
|
|
100
|
+
|
|
101
|
+
// src/engine/worker-runtime.ts
|
|
102
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
103
|
+
import path from "node:path";
|
|
104
|
+
|
|
105
|
+
// src/engine/claude/sdk-runtime.ts
|
|
106
|
+
import {
|
|
107
|
+
query
|
|
108
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
109
|
+
var DEFAULT_CLAUDE_TOOLS = { type: "preset", preset: "claude_code" };
|
|
110
|
+
function sdkUserTextMessage(text, shouldQuery) {
|
|
111
|
+
return {
|
|
112
|
+
type: "user",
|
|
113
|
+
message: { role: "user", content: [{ type: "text", text }] },
|
|
114
|
+
parent_tool_use_id: null,
|
|
115
|
+
shouldQuery,
|
|
116
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function buildClaudeSdkPrompt(input) {
|
|
120
|
+
const parts = (input.dynamicPromptMessages ?? []).map((message) => message.body.trim()).filter(Boolean);
|
|
121
|
+
parts.push(input.task);
|
|
122
|
+
return parts.join("\n\n");
|
|
123
|
+
}
|
|
124
|
+
async function* oneSdkUserMessage(message) {
|
|
125
|
+
yield message;
|
|
126
|
+
}
|
|
127
|
+
function claudeSdkPermissionModeForAutonomy(_level) {
|
|
128
|
+
return "default";
|
|
129
|
+
}
|
|
130
|
+
function claudeSdkToolsForAutonomy(_level) {
|
|
131
|
+
return DEFAULT_CLAUDE_TOOLS;
|
|
132
|
+
}
|
|
133
|
+
function effortForThinking(level) {
|
|
134
|
+
switch (level) {
|
|
135
|
+
case "minimal":
|
|
136
|
+
case "low":
|
|
137
|
+
return "low";
|
|
138
|
+
case "medium":
|
|
139
|
+
return "medium";
|
|
140
|
+
case "high":
|
|
141
|
+
return "high";
|
|
142
|
+
case "xhigh":
|
|
143
|
+
return "xhigh";
|
|
144
|
+
default:
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function thinkingForInput(input) {
|
|
149
|
+
const level = input.runtimeResolution?.effectiveThinkingLevel ?? input.thinkingLevel ?? "off";
|
|
150
|
+
if (level === "off") return { type: "disabled" };
|
|
151
|
+
const budgetTokens = input.runtimeResolution?.request.budgetTokens;
|
|
152
|
+
if (typeof budgetTokens === "number" && Number.isFinite(budgetTokens) && budgetTokens > 0) {
|
|
153
|
+
return { type: "enabled", budgetTokens, display: "summarized" };
|
|
154
|
+
}
|
|
155
|
+
return { type: "adaptive", display: "summarized" };
|
|
156
|
+
}
|
|
157
|
+
function systemPromptForInput(input) {
|
|
158
|
+
const append = input.systemPrompt.trim();
|
|
159
|
+
if (append.length === 0) return { type: "preset", preset: "claude_code" };
|
|
160
|
+
return { type: "preset", preset: "claude_code", append };
|
|
161
|
+
}
|
|
162
|
+
function finite(value) {
|
|
163
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
164
|
+
}
|
|
165
|
+
function nestedRecord(record, key) {
|
|
166
|
+
const value = record[key];
|
|
167
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
168
|
+
}
|
|
169
|
+
function normalizeUsage(raw, totalCostUsd = 0) {
|
|
170
|
+
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
171
|
+
const cacheCreation = finite(record.cache_creation_input_tokens) + finite(record.cacheCreationInputTokens) + finite(record.cacheWrite) + finite(record.cacheWriteTokens);
|
|
172
|
+
const cacheRead = finite(record.cache_read_input_tokens) + finite(record.cacheReadInputTokens) + finite(record.cacheRead) + finite(record.cacheReadTokens);
|
|
173
|
+
const input = finite(record.input_tokens) + finite(record.inputTokens) + finite(record.input) + finite(record.prompt_tokens) + finite(record.promptTokens);
|
|
174
|
+
const output = finite(record.output_tokens) + finite(record.outputTokens) + finite(record.output) + finite(record.completion_tokens) + finite(record.completionTokens);
|
|
175
|
+
const totalTokens = input + output + cacheRead + cacheCreation;
|
|
176
|
+
const cost = finite(totalCostUsd) + finite(record.costUSD) + finite(record.cost_usd);
|
|
177
|
+
return {
|
|
178
|
+
input,
|
|
179
|
+
output,
|
|
180
|
+
cacheRead,
|
|
181
|
+
cacheWrite: cacheCreation,
|
|
182
|
+
totalTokens,
|
|
183
|
+
cost: {
|
|
184
|
+
input: 0,
|
|
185
|
+
output: 0,
|
|
186
|
+
cacheRead: 0,
|
|
187
|
+
cacheWrite: 0,
|
|
188
|
+
total: cost
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function textFromUnknownBlock(block) {
|
|
193
|
+
if (typeof block !== "object" || block === null) return "";
|
|
194
|
+
const record = block;
|
|
195
|
+
if (record.type === "text" && typeof record.text === "string") return record.text;
|
|
196
|
+
if (record.type === "thinking" && typeof record.thinking === "string") return "";
|
|
197
|
+
return "";
|
|
198
|
+
}
|
|
199
|
+
function thinkingFromUnknownBlock(block) {
|
|
200
|
+
if (typeof block !== "object" || block === null) return "";
|
|
201
|
+
const record = block;
|
|
202
|
+
if (record.type === "thinking" && typeof record.thinking === "string") return record.thinking;
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
function contentFromSdkMessage(message) {
|
|
206
|
+
const content = [];
|
|
207
|
+
const record = typeof message === "object" && message !== null ? message : {};
|
|
208
|
+
const blocks = Array.isArray(record.content) ? record.content : [];
|
|
209
|
+
for (const block of blocks) {
|
|
210
|
+
const text = textFromUnknownBlock(block);
|
|
211
|
+
if (text.length > 0) {
|
|
212
|
+
content.push({ type: "text", text });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const thinking = thinkingFromUnknownBlock(block);
|
|
216
|
+
if (thinking.length > 0) {
|
|
217
|
+
content.push({ type: "thinking", thinking });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (typeof block === "object" && block !== null) {
|
|
221
|
+
const tool = block;
|
|
222
|
+
if (tool.type === "tool_use" && typeof tool.name === "string" && typeof tool.id === "string") {
|
|
223
|
+
content.push({
|
|
224
|
+
type: "toolCall",
|
|
225
|
+
id: tool.id,
|
|
226
|
+
name: tool.name,
|
|
227
|
+
arguments: nestedRecord(tool, "input") ?? {}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (content.length === 0) content.push({ type: "text", text: "" });
|
|
233
|
+
return {
|
|
234
|
+
role: "assistant",
|
|
235
|
+
content,
|
|
236
|
+
api: "claude-agent-sdk",
|
|
237
|
+
provider: "anthropic",
|
|
238
|
+
model: "",
|
|
239
|
+
usage: normalizeUsage(null),
|
|
240
|
+
stopReason: "stop",
|
|
241
|
+
timestamp: Date.now()
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function extractAssistantText(message) {
|
|
245
|
+
if (message.type !== "assistant") return "";
|
|
246
|
+
const record = message;
|
|
247
|
+
const assistant = contentFromSdkMessage(record.message);
|
|
248
|
+
return assistant.content.map((block) => block.type === "text" ? block.text : "").filter(Boolean).join("");
|
|
249
|
+
}
|
|
250
|
+
function extractStreamDelta(message) {
|
|
251
|
+
if (message.type !== "stream_event") return "";
|
|
252
|
+
const event = message.event;
|
|
253
|
+
if (typeof event !== "object" || event === null) return "";
|
|
254
|
+
const record = event;
|
|
255
|
+
const delta = nestedRecord(record, "delta");
|
|
256
|
+
if (delta && typeof delta.text === "string") return delta.text;
|
|
257
|
+
if (delta && typeof delta.thinking === "string") return "";
|
|
258
|
+
const contentBlock = nestedRecord(record, "content_block");
|
|
259
|
+
if (contentBlock && contentBlock.type === "text" && typeof contentBlock.text === "string") return contentBlock.text;
|
|
260
|
+
return "";
|
|
261
|
+
}
|
|
262
|
+
function resultText(message) {
|
|
263
|
+
if (message?.subtype !== "success") return "";
|
|
264
|
+
return typeof message.result === "string" ? message.result : "";
|
|
265
|
+
}
|
|
266
|
+
function resultError(message) {
|
|
267
|
+
if (!message || message.subtype === "success") return "";
|
|
268
|
+
return Array.isArray(message.errors) ? message.errors.join("; ") : "Claude Agent SDK run failed";
|
|
269
|
+
}
|
|
270
|
+
function stopReasonFor(result, aborted) {
|
|
271
|
+
if (aborted) return "aborted";
|
|
272
|
+
if (!result) return "stop";
|
|
273
|
+
if (result.subtype !== "success") return "error";
|
|
274
|
+
if (result.stop_reason === "max_tokens") return "length";
|
|
275
|
+
return "stop";
|
|
276
|
+
}
|
|
277
|
+
function buildAssistantMessage(input) {
|
|
278
|
+
const usage = normalizeUsage(input.result?.usage, input.result?.total_cost_usd ?? 0);
|
|
279
|
+
const message = {
|
|
280
|
+
role: "assistant",
|
|
281
|
+
content: [{ type: "text", text: input.text }],
|
|
282
|
+
api: "claude-agent-sdk",
|
|
283
|
+
provider: "anthropic",
|
|
284
|
+
model: input.model,
|
|
285
|
+
usage,
|
|
286
|
+
stopReason: stopReasonFor(input.result, input.aborted),
|
|
287
|
+
timestamp: Date.now()
|
|
288
|
+
};
|
|
289
|
+
if (input.responseModel !== void 0) message.responseModel = input.responseModel;
|
|
290
|
+
if (input.responseId !== void 0) message.responseId = input.responseId;
|
|
291
|
+
if (input.errorMessage !== void 0 && input.errorMessage.length > 0) message.errorMessage = input.errorMessage;
|
|
292
|
+
return message;
|
|
293
|
+
}
|
|
294
|
+
function emitTextDelta(emit, state, delta) {
|
|
295
|
+
if (delta.length === 0) return;
|
|
296
|
+
state.text += delta;
|
|
297
|
+
const message = buildAssistantMessage({ model: state.model, text: state.text, result: null, aborted: false });
|
|
298
|
+
if (!state.started) {
|
|
299
|
+
state.started = true;
|
|
300
|
+
emit({ type: "message_start", message });
|
|
301
|
+
}
|
|
302
|
+
emit({
|
|
303
|
+
type: "message_update",
|
|
304
|
+
message,
|
|
305
|
+
assistantMessageEvent: {
|
|
306
|
+
type: "text_delta",
|
|
307
|
+
contentIndex: 0,
|
|
308
|
+
delta,
|
|
309
|
+
partial: message
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function createClaudeWorkerBudgetGate(budget, onBoundary, onHardCap, deliveryTools = []) {
|
|
314
|
+
let attempts = 0;
|
|
315
|
+
let admitted = 0;
|
|
316
|
+
let locked = false;
|
|
317
|
+
const phaseDecision = (canonicalToolName) => {
|
|
318
|
+
const reserveAdmits = isReserveAdmittedTool(canonicalToolName, deliveryTools);
|
|
319
|
+
if (locked || admitted >= budget.toolCalls) {
|
|
320
|
+
if (deliveryTools.length > 0 && !locked) {
|
|
321
|
+
if (reserveAdmits) return { kind: "allow" };
|
|
322
|
+
return {
|
|
323
|
+
kind: "deny",
|
|
324
|
+
reason: `worker discovery budget reached (${budget.toolCalls}); only read and ${deliveryTools.join("/")} are admitted for the rest of this run`
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
kind: "deny",
|
|
329
|
+
reason: budget.synthesis ? `worker agent budget reached (${budget.toolCalls}); tool calls are disabled for final synthesis` : `worker agent budget reached (${budget.toolCalls}); synthesis is disabled`
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const reserveStart = budget.toolCalls - budget.readReserve;
|
|
333
|
+
if (budget.readReserve > 0 && admitted >= reserveStart && !reserveAdmits) {
|
|
334
|
+
const admittedNames = deliveryTools.length > 0 ? `read and ${deliveryTools.join("/")} are` : "only read is";
|
|
335
|
+
return {
|
|
336
|
+
kind: "deny",
|
|
337
|
+
reason: `worker read reserve: ${budget.toolCalls - admitted} admitted calls remain and ${admittedNames} allowed`
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return { kind: "allow" };
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
attempt(canonicalToolName) {
|
|
344
|
+
attempts += 1;
|
|
345
|
+
if (attempts > budget.hardCap) {
|
|
346
|
+
onHardCap();
|
|
347
|
+
return { kind: "deny", reason: `workerToolCallCap reached (${budget.hardCap}); abort run` };
|
|
348
|
+
}
|
|
349
|
+
return phaseDecision(canonicalToolName);
|
|
350
|
+
},
|
|
351
|
+
admit(canonicalToolName) {
|
|
352
|
+
const phase = phaseDecision(canonicalToolName);
|
|
353
|
+
if (phase.kind === "deny") return phase;
|
|
354
|
+
admitted += 1;
|
|
355
|
+
if (admitted >= budget.toolCalls && deliveryTools.length === 0) {
|
|
356
|
+
locked = true;
|
|
357
|
+
onBoundary();
|
|
358
|
+
}
|
|
359
|
+
return { kind: "allow" };
|
|
360
|
+
},
|
|
361
|
+
phaseReached: () => locked
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function permissionResultForDecision(decision, toolUseID, input) {
|
|
365
|
+
if (decision.kind === "allow") {
|
|
366
|
+
const result2 = {
|
|
367
|
+
behavior: "allow",
|
|
368
|
+
decisionClassification: "user_temporary"
|
|
369
|
+
};
|
|
370
|
+
if (toolUseID !== void 0) result2.toolUseID = toolUseID;
|
|
371
|
+
return result2;
|
|
372
|
+
}
|
|
373
|
+
if (decision.permissionRequired && input.onPermission === "fail") {
|
|
374
|
+
input.onPermissionFailure();
|
|
375
|
+
}
|
|
376
|
+
const result = {
|
|
377
|
+
behavior: "deny",
|
|
378
|
+
message: decision.reason,
|
|
379
|
+
interrupt: decision.permissionRequired && input.onPermission === "fail",
|
|
380
|
+
decisionClassification: "user_reject"
|
|
381
|
+
};
|
|
382
|
+
if (toolUseID !== void 0) result.toolUseID = toolUseID;
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
function decideToolUse(input, toolName, toolInput) {
|
|
386
|
+
return emitClaudeToolPermissionDecision({
|
|
387
|
+
toolName,
|
|
388
|
+
input: coerceToolInput(toolInput),
|
|
389
|
+
safety: input.safety,
|
|
390
|
+
cwd: input.cwd,
|
|
391
|
+
...input.autonomy !== void 0 ? { autonomy: input.autonomy } : {},
|
|
392
|
+
onPermission: input.onPermission,
|
|
393
|
+
emit: input.emit,
|
|
394
|
+
allowedTools: input.allowedTools,
|
|
395
|
+
...input.budgetGate ? { budgetGate: input.budgetGate } : {}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function decideClaudeSdkToolUseOnce(handled, toolUseID, decide) {
|
|
399
|
+
const cached = toolUseID ? handled.get(toolUseID) : void 0;
|
|
400
|
+
if (cached) return cached;
|
|
401
|
+
const decision = decide();
|
|
402
|
+
if (toolUseID) handled.set(toolUseID, decision);
|
|
403
|
+
return decision;
|
|
404
|
+
}
|
|
405
|
+
function decideToolUseOnce(input, toolUseID, toolName, toolInput) {
|
|
406
|
+
return decideClaudeSdkToolUseOnce(
|
|
407
|
+
input.handledToolDecisions,
|
|
408
|
+
toolUseID,
|
|
409
|
+
() => decideToolUse(input, toolName, toolInput)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
function buildCanUseTool(input) {
|
|
413
|
+
return async (toolName, toolInput, options) => {
|
|
414
|
+
const decision = decideToolUseOnce(input, options.toolUseID, toolName, toolInput);
|
|
415
|
+
return permissionResultForDecision(decision, options.toolUseID, input);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function buildPreToolUseHook(input) {
|
|
419
|
+
return async (hookInput, toolUseID) => {
|
|
420
|
+
if (hookInput.hook_event_name !== "PreToolUse") return { continue: true };
|
|
421
|
+
const id = hookInput.tool_use_id || toolUseID;
|
|
422
|
+
const decision = decideToolUseOnce(input, id, hookInput.tool_name, hookInput.tool_input);
|
|
423
|
+
if (decision.kind === "allow") {
|
|
424
|
+
return {
|
|
425
|
+
continue: true,
|
|
426
|
+
hookSpecificOutput: {
|
|
427
|
+
hookEventName: "PreToolUse",
|
|
428
|
+
permissionDecision: "allow",
|
|
429
|
+
permissionDecisionReason: decision.reason
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (decision.permissionRequired && input.onPermission === "fail") input.onPermissionFailure();
|
|
434
|
+
return {
|
|
435
|
+
continue: true,
|
|
436
|
+
hookSpecificOutput: {
|
|
437
|
+
hookEventName: "PreToolUse",
|
|
438
|
+
permissionDecision: "deny",
|
|
439
|
+
permissionDecisionReason: decision.reason
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function startClaudeSdkWorkerRun(input, emit) {
|
|
445
|
+
const abortController = new AbortController();
|
|
446
|
+
let queryHandle = null;
|
|
447
|
+
let aborted = false;
|
|
448
|
+
let permissionFailure = false;
|
|
449
|
+
let budgetFailure = false;
|
|
450
|
+
const abort = () => {
|
|
451
|
+
aborted = true;
|
|
452
|
+
abortController.abort();
|
|
453
|
+
void queryHandle?.interrupt().catch(() => {
|
|
454
|
+
});
|
|
455
|
+
queryHandle?.close();
|
|
456
|
+
};
|
|
457
|
+
if (input.signal) {
|
|
458
|
+
if (input.signal.aborted) abort();
|
|
459
|
+
else input.signal.addEventListener("abort", abort, { once: true });
|
|
460
|
+
}
|
|
461
|
+
const safety = createWorkerSafety({
|
|
462
|
+
cwd: process.cwd(),
|
|
463
|
+
...input.writeRoots !== void 0 ? { writeRoots: input.writeRoots } : {},
|
|
464
|
+
...input.protectedArtifactState !== void 0 ? { protectedArtifactState: { artifacts: [...input.protectedArtifactState.artifacts] } } : {}
|
|
465
|
+
});
|
|
466
|
+
const onPermission = input.onPermission === "fail" ? "fail" : "deny";
|
|
467
|
+
const allowedToolSet = new Set(input.allowedTools);
|
|
468
|
+
const budgetGate = input.budget ? createClaudeWorkerBudgetGate(
|
|
469
|
+
input.budget,
|
|
470
|
+
() => {
|
|
471
|
+
},
|
|
472
|
+
() => {
|
|
473
|
+
budgetFailure = true;
|
|
474
|
+
emit({ type: "clio_run_outcome", payload: { outcomeCode: "worker_tool_call_cap_exhausted" } });
|
|
475
|
+
abort();
|
|
476
|
+
},
|
|
477
|
+
resolveDeliveryTools(allowedToolSet, input.product)
|
|
478
|
+
) : void 0;
|
|
479
|
+
const permissionGate = {
|
|
480
|
+
safety,
|
|
481
|
+
cwd: process.cwd(),
|
|
482
|
+
...input.autonomy !== void 0 ? { autonomy: input.autonomy } : {},
|
|
483
|
+
onPermission,
|
|
484
|
+
emit,
|
|
485
|
+
handledToolDecisions: /* @__PURE__ */ new Map(),
|
|
486
|
+
onPermissionFailure() {
|
|
487
|
+
permissionFailure = true;
|
|
488
|
+
abort();
|
|
489
|
+
},
|
|
490
|
+
allowedTools: allowedToolSet,
|
|
491
|
+
...budgetGate ? { budgetGate } : {}
|
|
492
|
+
};
|
|
493
|
+
const canUseTool = buildCanUseTool(permissionGate);
|
|
494
|
+
const preToolUseHook = buildPreToolUseHook(permissionGate);
|
|
495
|
+
const postToolBatchHook = async (hookInput) => {
|
|
496
|
+
if (hookInput.hook_event_name !== "PostToolBatch" || !budgetGate?.phaseReached()) return { continue: true };
|
|
497
|
+
if (input.budget?.synthesis === false) {
|
|
498
|
+
budgetFailure = true;
|
|
499
|
+
emit({ type: "clio_run_outcome", payload: { outcomeCode: "worker_tool_call_cap_exhausted" } });
|
|
500
|
+
return { continue: false, stopReason: `worker agent budget reached (${input.budget.toolCalls})` };
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
continue: true,
|
|
504
|
+
hookSpecificOutput: {
|
|
505
|
+
hookEventName: "PostToolBatch",
|
|
506
|
+
additionalContext: "The admitted tool-call phase is complete. Tools are now disabled. Return the final answer in plain text from evidence already gathered."
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
};
|
|
510
|
+
const options = {
|
|
511
|
+
abortController,
|
|
512
|
+
cwd: process.cwd(),
|
|
513
|
+
model: input.wireModelId,
|
|
514
|
+
systemPrompt: systemPromptForInput(input),
|
|
515
|
+
tools: claudeSdkToolsForAutonomy(input.autonomy),
|
|
516
|
+
permissionMode: claudeSdkPermissionModeForAutonomy(input.autonomy),
|
|
517
|
+
canUseTool,
|
|
518
|
+
hooks: {
|
|
519
|
+
PreToolUse: [{ hooks: [preToolUseHook] }],
|
|
520
|
+
...budgetGate ? { PostToolBatch: [{ hooks: [postToolBatchHook] }] } : {}
|
|
521
|
+
},
|
|
522
|
+
includePartialMessages: true,
|
|
523
|
+
persistSession: false,
|
|
524
|
+
settingSources: [],
|
|
525
|
+
env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: `@iowarp/clio-coder/${readClioVersion()}` }
|
|
526
|
+
};
|
|
527
|
+
const disallowedTools = claudeToolsOutsideProfile(allowedToolSet);
|
|
528
|
+
if (disallowedTools.length > 0) options.disallowedTools = disallowedTools;
|
|
529
|
+
const thinking = thinkingForInput(input);
|
|
530
|
+
if (thinking !== void 0) options.thinking = thinking;
|
|
531
|
+
const effort = input.runtimeResolution?.request.reasoningEffort ?? effortForThinking(input.runtimeResolution?.effectiveThinkingLevel ?? input.thinkingLevel);
|
|
532
|
+
if (effort !== void 0) options.effort = effort;
|
|
533
|
+
if (isClaudeCodeSessionId(input.sessionId)) options.sessionId = input.sessionId.trim();
|
|
534
|
+
const promise = (async () => {
|
|
535
|
+
const messages = [];
|
|
536
|
+
const streamState = { started: false, text: "", model: input.wireModelId };
|
|
537
|
+
let result = null;
|
|
538
|
+
let responseModel;
|
|
539
|
+
let responseId;
|
|
540
|
+
emit({ type: "agent_start" });
|
|
541
|
+
try {
|
|
542
|
+
queryHandle = query({ prompt: buildClaudeSdkPrompt(input), options });
|
|
543
|
+
for await (const sdkMessage of queryHandle) {
|
|
544
|
+
if (sdkMessage.type === "system" && sdkMessage.subtype === "init") {
|
|
545
|
+
const init = sdkMessage;
|
|
546
|
+
if (typeof init.model === "string" && init.model.length > 0) streamState.model = init.model;
|
|
547
|
+
if (typeof init.uuid === "string") responseId = init.uuid;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (sdkMessage.type === "stream_event") {
|
|
551
|
+
emitTextDelta(emit, streamState, extractStreamDelta(sdkMessage));
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (sdkMessage.type === "assistant") {
|
|
555
|
+
const text = extractAssistantText(sdkMessage);
|
|
556
|
+
if (text.length > 0 && text.length >= streamState.text.length) {
|
|
557
|
+
streamState.text = text;
|
|
558
|
+
}
|
|
559
|
+
const assistant = sdkMessage;
|
|
560
|
+
if (typeof assistant.message?.model === "string") responseModel = assistant.message.model;
|
|
561
|
+
if (typeof assistant.request_id === "string") responseId = assistant.request_id;
|
|
562
|
+
else if (typeof assistant.uuid === "string") responseId = assistant.uuid;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (sdkMessage.type === "result") {
|
|
566
|
+
result = sdkMessage;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const error = resultError(result);
|
|
570
|
+
const finalText = streamState.text || resultText(result) || error;
|
|
571
|
+
const finalMessage = buildAssistantMessage({
|
|
572
|
+
model: streamState.model,
|
|
573
|
+
responseModel,
|
|
574
|
+
responseId,
|
|
575
|
+
text: finalText,
|
|
576
|
+
result,
|
|
577
|
+
aborted,
|
|
578
|
+
errorMessage: error
|
|
579
|
+
});
|
|
580
|
+
if (!streamState.started) emit({ type: "message_start", message: finalMessage });
|
|
581
|
+
emit({ type: "message_end", message: finalMessage });
|
|
582
|
+
messages.push(finalMessage);
|
|
583
|
+
emit({ type: "agent_end", messages });
|
|
584
|
+
if (permissionFailure) return { messages, exitCode: WORKER_EXIT_PERMISSION_REQUIRED };
|
|
585
|
+
if (budgetFailure) return { messages, exitCode: 1 };
|
|
586
|
+
return { messages, exitCode: finalMessage.stopReason === "error" ? 1 : 0 };
|
|
587
|
+
} catch (error) {
|
|
588
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
589
|
+
const finalMessage = buildAssistantMessage({
|
|
590
|
+
model: streamState.model,
|
|
591
|
+
text: messageText,
|
|
592
|
+
result,
|
|
593
|
+
aborted,
|
|
594
|
+
errorMessage: messageText
|
|
595
|
+
});
|
|
596
|
+
emit({ type: "message_end", message: finalMessage });
|
|
597
|
+
messages.push(finalMessage);
|
|
598
|
+
emit({ type: "agent_end", messages });
|
|
599
|
+
if (permissionFailure) return { messages, exitCode: WORKER_EXIT_PERMISSION_REQUIRED };
|
|
600
|
+
if (budgetFailure) return { messages, exitCode: 1 };
|
|
601
|
+
if (!aborted) process.stderr.write(`[worker:claude-sdk] ${messageText}
|
|
602
|
+
`);
|
|
603
|
+
return { messages, exitCode: 1 };
|
|
604
|
+
} finally {
|
|
605
|
+
queryHandle?.close();
|
|
606
|
+
}
|
|
607
|
+
})();
|
|
608
|
+
return {
|
|
609
|
+
promise,
|
|
610
|
+
abort,
|
|
611
|
+
async steer(text) {
|
|
612
|
+
const trimmed = text.trim();
|
|
613
|
+
const activeQuery = queryHandle;
|
|
614
|
+
if (trimmed.length === 0 || activeQuery === null) return false;
|
|
615
|
+
try {
|
|
616
|
+
await activeQuery.streamInput(oneSdkUserMessage(sdkUserTextMessage(trimmed, true)));
|
|
617
|
+
return true;
|
|
618
|
+
} catch {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/engine/worker-runtime.ts
|
|
626
|
+
function isAssistantMessage(message) {
|
|
627
|
+
if (typeof message !== "object" || message === null) return false;
|
|
628
|
+
return "role" in message && message.role === "assistant";
|
|
629
|
+
}
|
|
630
|
+
function getTerminalAgentError(messages) {
|
|
631
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
632
|
+
const message = messages[i];
|
|
633
|
+
if (!isAssistantMessage(message)) continue;
|
|
634
|
+
if (message.stopReason !== "error") return null;
|
|
635
|
+
return typeof message.errorMessage === "string" ? message.errorMessage : "";
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
function assistantMessageText(message) {
|
|
640
|
+
if (!isAssistantMessage(message) || !Array.isArray(message.content)) return null;
|
|
641
|
+
return message.content.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("").trim();
|
|
642
|
+
}
|
|
643
|
+
function isTerminalAssistantMessage(message) {
|
|
644
|
+
if (!isAssistantMessage(message)) return false;
|
|
645
|
+
return message.stopReason !== "toolUse" && message.stopReason !== "error";
|
|
646
|
+
}
|
|
647
|
+
function terminalContractViolation(contract, message, cwd, observedReadRanges, observedRunEffects) {
|
|
648
|
+
if (!isAssistantMessage(message)) return null;
|
|
649
|
+
const text = assistantMessageText(message);
|
|
650
|
+
if (text === null || text.length === 0) return "missing final result";
|
|
651
|
+
const stopReason = message.stopReason;
|
|
652
|
+
if (shouldRequestStalledTurnContinuation({
|
|
653
|
+
hook: "turn_end",
|
|
654
|
+
text,
|
|
655
|
+
metadata: { turnToolCalls: 0, ...typeof stopReason === "string" ? { stopReason } : {} }
|
|
656
|
+
})) {
|
|
657
|
+
return "the response announced further work instead of stating the final result";
|
|
658
|
+
}
|
|
659
|
+
const validation = validateResultContract({
|
|
660
|
+
contract,
|
|
661
|
+
output: text,
|
|
662
|
+
cwd,
|
|
663
|
+
observedReadRanges,
|
|
664
|
+
observedRunEffects,
|
|
665
|
+
// Repair rounds judge shape and grounding only. Network posture belongs to
|
|
666
|
+
// the orchestrator's sealed validation, which runs again on the receipt.
|
|
667
|
+
networkAllowed: true,
|
|
668
|
+
filesystem: {
|
|
669
|
+
readFile(filePath) {
|
|
670
|
+
try {
|
|
671
|
+
return readFileSync(filePath, "utf8");
|
|
672
|
+
} catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
pathExists(filePath) {
|
|
677
|
+
return existsSync(filePath);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
return validation.conformance === "pass" ? null : validation.reason ?? "invalid result";
|
|
682
|
+
}
|
|
683
|
+
var NullKnowledgeBase = class {
|
|
684
|
+
lookup(_modelId) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
entries() {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
var kbSingleton = null;
|
|
692
|
+
function getKnowledgeBase() {
|
|
693
|
+
if (kbSingleton) return kbSingleton;
|
|
694
|
+
try {
|
|
695
|
+
const roots = resolveProviderKnowledgeBaseRoots(import.meta.url);
|
|
696
|
+
kbSingleton = roots.length > 0 ? new FileKnowledgeBase(roots) : new NullKnowledgeBase();
|
|
697
|
+
} catch {
|
|
698
|
+
kbSingleton = new NullKnowledgeBase();
|
|
699
|
+
}
|
|
700
|
+
return kbSingleton;
|
|
701
|
+
}
|
|
702
|
+
function clampThinkingLevelForModel(model, requested) {
|
|
703
|
+
const level = requested ?? "off";
|
|
704
|
+
return resolveModelRuntimeCapabilitiesForModel(model, level).thinking.effectiveLevel;
|
|
705
|
+
}
|
|
706
|
+
function promptMessage(fragment) {
|
|
707
|
+
return {
|
|
708
|
+
role: "user",
|
|
709
|
+
content: [{ type: "text", text: fragment.body }],
|
|
710
|
+
timestamp: Date.now()
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function taskMessage(task) {
|
|
714
|
+
return {
|
|
715
|
+
role: "user",
|
|
716
|
+
content: [{ type: "text", text: task }],
|
|
717
|
+
timestamp: Date.now()
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function promptMessagesForWorker(input) {
|
|
721
|
+
const messages = (input.dynamicPromptMessages ?? []).map(promptMessage);
|
|
722
|
+
messages.push(taskMessage(input.task));
|
|
723
|
+
return messages;
|
|
724
|
+
}
|
|
725
|
+
function workerProviderSupportsTools(input) {
|
|
726
|
+
const runtimeDecision = input.runtimeResolution?.capabilities.tools;
|
|
727
|
+
if (runtimeDecision !== void 0) return runtimeDecision === true;
|
|
728
|
+
if (typeof input.modelCapabilities?.tools === "boolean") return input.modelCapabilities.tools;
|
|
729
|
+
return input.runtime.defaultCapabilities.tools === true;
|
|
730
|
+
}
|
|
731
|
+
function assertResponseSchemaRuntime(input) {
|
|
732
|
+
if (input.responseSchema === void 0) return;
|
|
733
|
+
if (runtimeSpeaksResponseSchemaDialect(input.runtime) && input.modelCapabilities?.structuredOutputs === "json-schema") {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
throw new Error(
|
|
737
|
+
`responseSchema requires a native llamacpp runtime with resolved JSON-schema support; received '${input.runtime.id}'`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
function resolveWorkerRuntimeBudget(input) {
|
|
741
|
+
return input.budget;
|
|
742
|
+
}
|
|
743
|
+
function startWorkerRun(input, emit) {
|
|
744
|
+
assertResponseSchemaRuntime(input);
|
|
745
|
+
if (input.resultContract !== void 0) parseResultContract(input.resultContract, "WorkerSpec.resultContract");
|
|
746
|
+
if (input.runtime.id === "claude-sdk") {
|
|
747
|
+
return startClaudeSdkWorkerRun(input, emit);
|
|
748
|
+
}
|
|
749
|
+
if (input.runtime.id === "claude-code") {
|
|
750
|
+
return startClaudeCodeWorkerRun(input, emit);
|
|
751
|
+
}
|
|
752
|
+
if (input.runtime.id === "antigravity-code") {
|
|
753
|
+
return startAntigravityWorkerRun(input, emit);
|
|
754
|
+
}
|
|
755
|
+
registerClioApiProviders();
|
|
756
|
+
const workerSettings = validateSettingsFile().settings;
|
|
757
|
+
setGlobalDefaultMaxOutputTokens(workerSettings.defaults.maxTokens);
|
|
758
|
+
configureGuardrails(workerSettings.guardrails);
|
|
759
|
+
const fauxModel = registerFauxFromEnv();
|
|
760
|
+
const agentSkillPolicy = input.allowedTools.includes(ToolNames.Context) && input.noSkills !== true ? agentSkillToolPolicy(input.agentSkills ?? []) : void 0;
|
|
761
|
+
const activeWorkerTools = workerProviderSupportsTools(input) ? input.allowedTools : [];
|
|
762
|
+
const kb = getKnowledgeBase();
|
|
763
|
+
const kbHit = kb.lookup(input.wireModelId);
|
|
764
|
+
const synthesized = input.runtime.synthesizeModel(input.target, input.wireModelId, kbHit);
|
|
765
|
+
const model = applyModelCapabilityPatch(
|
|
766
|
+
input.target.runtime === "faux" && fauxModel ? fauxModel : synthesized,
|
|
767
|
+
input.modelCapabilities
|
|
768
|
+
);
|
|
769
|
+
const safety = createWorkerSafety({
|
|
770
|
+
cwd: process.cwd(),
|
|
771
|
+
...input.writeRoots !== void 0 ? { writeRoots: input.writeRoots } : {},
|
|
772
|
+
...input.protectedArtifactState !== void 0 ? { protectedArtifactState: { artifacts: [...input.protectedArtifactState.artifacts] } } : {}
|
|
773
|
+
});
|
|
774
|
+
let synthesisToolLock = false;
|
|
775
|
+
let workerBoundFailure = null;
|
|
776
|
+
let workerBoundAborted = false;
|
|
777
|
+
let abortWorkerForBound = null;
|
|
778
|
+
let stopAfterToolResultCallId = null;
|
|
779
|
+
const workerBudget = resolveWorkerRuntimeBudget(input);
|
|
780
|
+
const readReserve = input.allowedTools.includes(ToolNames.Read) ? workerBudget.readReserve : 0;
|
|
781
|
+
const deliveryTools = resolveDeliveryTools(input.allowedTools, input.product);
|
|
782
|
+
const middlewareToolChoice = createMiddlewareToolChoiceControl();
|
|
783
|
+
let workerModelRound = 0;
|
|
784
|
+
const registry = createWorkerToolRegistry(
|
|
785
|
+
input.middlewareSnapshot,
|
|
786
|
+
safety,
|
|
787
|
+
{
|
|
788
|
+
...input.noSkills !== void 0 ? { noSkills: input.noSkills } : {},
|
|
789
|
+
...input.skillPaths !== void 0 ? { skillPaths: [...input.skillPaths] } : {},
|
|
790
|
+
...input.trustProjectCompatRoots !== void 0 ? { trustProjectCompatRoots: input.trustProjectCompatRoots } : {}
|
|
791
|
+
},
|
|
792
|
+
// Workers run unattended, so the loop guard carries the hard tool-call
|
|
793
|
+
// cap in addition to repetition blocking, plus the synthesis lockout:
|
|
794
|
+
// after the loop-block budget the worker is told to report from what it
|
|
795
|
+
// gathered instead of burning the lifetime cap on a retry spiral, and
|
|
796
|
+
// the bounded backstop reason is watched in telemetry.onFinish below to
|
|
797
|
+
// abort a worker that keeps calling tools anyway.
|
|
798
|
+
// The protected-artifacts guard starts from the parent-session snapshot
|
|
799
|
+
// and has no persistence sink. It can still absorb worker-local
|
|
800
|
+
// protect_path effects from snapshot rules for the rest of this run.
|
|
801
|
+
[
|
|
802
|
+
createLoopGuardRegistration({
|
|
803
|
+
safety,
|
|
804
|
+
toolCallCap: workerBudget.hardCap,
|
|
805
|
+
toolCallSoftLimit: workerBudget.toolCalls,
|
|
806
|
+
// A worker's blocks all land in one run-long bucket, so the bound on
|
|
807
|
+
// them is a statement about this run's length, not about a turn.
|
|
808
|
+
turnBlockBudget: workerLoopBlockBudget(workerBudget.toolCalls),
|
|
809
|
+
toolCallSoftReadReserve: readReserve,
|
|
810
|
+
...deliveryTools.length > 0 ? { deliveryTools } : {},
|
|
811
|
+
turnSynthesisLockout: workerBudget.synthesis,
|
|
812
|
+
// Once locked, the next model round is forced text-only at the
|
|
813
|
+
// request level (tool_choice none in onPayload below): the lockout
|
|
814
|
+
// directive alone relies on model compliance, and measured local
|
|
815
|
+
// models kept calling tools until the backstop aborted the run.
|
|
816
|
+
onSynthesisLockout: () => {
|
|
817
|
+
if (workerBudget.synthesis) {
|
|
818
|
+
synthesisToolLock = true;
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
...!workerBudget.synthesis ? {
|
|
822
|
+
onSoftLimitFinalCallAdmitted: (toolCallId) => {
|
|
823
|
+
if (workerBoundFailure === null) {
|
|
824
|
+
workerBoundFailure = `worker agent budget reached (${workerBudget.toolCalls}); synthesis is disabled`;
|
|
825
|
+
}
|
|
826
|
+
stopAfterToolResultCallId = toolCallId ?? "";
|
|
827
|
+
}
|
|
828
|
+
} : {},
|
|
829
|
+
// Forcing the next round to `read` is only correct when reading is
|
|
830
|
+
// the whole reserve. An agent with delivery tools must be able to
|
|
831
|
+
// write in its own reserve window, so it gets the steering directive
|
|
832
|
+
// without the request-level lock.
|
|
833
|
+
...readReserve > 0 && deliveryTools.length === 0 ? {
|
|
834
|
+
onSoftReadReserve: () => {
|
|
835
|
+
middlewareToolChoice.apply([{ kind: "require_tool", toolName: ToolNames.Read }]);
|
|
836
|
+
}
|
|
837
|
+
} : {}
|
|
838
|
+
}),
|
|
839
|
+
createProtectedArtifactsRegistration({
|
|
840
|
+
...input.protectedArtifactState !== void 0 ? { initialState: { artifacts: [...input.protectedArtifactState.artifacts] } } : {}
|
|
841
|
+
})
|
|
842
|
+
],
|
|
843
|
+
input.autonomy,
|
|
844
|
+
(effects) => middlewareToolChoice.apply(effects),
|
|
845
|
+
input.agentLedger
|
|
846
|
+
);
|
|
847
|
+
const contractCwd = input.cwd ?? process.cwd();
|
|
848
|
+
let resultContractRepairsQueued = 0;
|
|
849
|
+
const pendingReadCitations = /* @__PURE__ */ new Map();
|
|
850
|
+
const observedReadRanges = /* @__PURE__ */ new Map();
|
|
851
|
+
const runEffects = createRunEffectsRecorder(contractCwd);
|
|
852
|
+
const recordObservedRead = (request, result) => {
|
|
853
|
+
const observation = result?.details?.observation;
|
|
854
|
+
if (!observation) return;
|
|
855
|
+
const shown = observation.shownCount;
|
|
856
|
+
const total = observation.totalCount;
|
|
857
|
+
if (typeof shown !== "number" || !Number.isFinite(shown) || shown <= 0) return;
|
|
858
|
+
let start;
|
|
859
|
+
if (request.tail) {
|
|
860
|
+
if (typeof total !== "number" || !Number.isFinite(total)) return;
|
|
861
|
+
start = Math.max(1, Math.floor(total) - shown + 1);
|
|
862
|
+
} else {
|
|
863
|
+
start = request.offset ?? 1;
|
|
864
|
+
}
|
|
865
|
+
const key = path.resolve(contractCwd, request.path);
|
|
866
|
+
const spans = observedReadRanges.get(key) ?? [];
|
|
867
|
+
spans.push([start, start + shown - 1]);
|
|
868
|
+
observedReadRanges.set(key, spans);
|
|
869
|
+
};
|
|
870
|
+
const observedReadAnchors = () => [...observedReadRanges.entries()].flatMap(
|
|
871
|
+
([key, spans]) => spans.map(([start, end]) => `${path.relative(contractCwd, key) || key}:${start}-${end}`)
|
|
872
|
+
);
|
|
873
|
+
const telemetry = {
|
|
874
|
+
onStart(event) {
|
|
875
|
+
emit({ type: "clio_tool_start", payload: event });
|
|
876
|
+
},
|
|
877
|
+
onFinish(event) {
|
|
878
|
+
emit({ type: "clio_tool_finish", payload: event });
|
|
879
|
+
if (event.outcome !== "blocked" || typeof event.reason !== "string") return;
|
|
880
|
+
if (isWorkerToolCallCapSynthesisReason(event.reason)) {
|
|
881
|
+
emit({ type: "clio_run_outcome", payload: { outcomeCode: "worker_tool_call_cap_exhausted" } });
|
|
882
|
+
if (workerBoundFailure === null) {
|
|
883
|
+
workerBoundFailure = event.reason;
|
|
884
|
+
process.stderr.write(`[worker] ${event.reason}
|
|
885
|
+
`);
|
|
886
|
+
}
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (isWorkerToolCallCapExceededReason(event.reason) || isLoopGuardSynthesisBackstopReason(event.reason)) {
|
|
890
|
+
emit({
|
|
891
|
+
type: "clio_run_outcome",
|
|
892
|
+
payload: {
|
|
893
|
+
outcomeCode: isLoopGuardSynthesisBackstopReason(event.reason) ? "loop_guard_tools_disabled_exhausted" : "worker_tool_call_cap_exhausted"
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
if (workerBoundAborted) return;
|
|
897
|
+
workerBoundAborted = true;
|
|
898
|
+
if (workerBoundFailure === null) workerBoundFailure = event.reason;
|
|
899
|
+
process.stderr.write(`[worker] ${event.reason}
|
|
900
|
+
`);
|
|
901
|
+
abortWorkerForBound?.();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
const tools = resolveAgentTools({
|
|
906
|
+
registry,
|
|
907
|
+
telemetry,
|
|
908
|
+
allowedTools: activeWorkerTools,
|
|
909
|
+
agentId: input.agentId,
|
|
910
|
+
task: input.task,
|
|
911
|
+
includeInteractiveTools: false,
|
|
912
|
+
invokeOptions: () => ({
|
|
913
|
+
correlationId: `worker-model-round-${workerModelRound}`,
|
|
914
|
+
...agentSkillPolicy ? { pendingSkillPolicy: agentSkillPolicy } : {}
|
|
915
|
+
})
|
|
916
|
+
});
|
|
917
|
+
if (tools.length === 0 && activeWorkerTools.length > 0) {
|
|
918
|
+
process.stderr.write(`[worker] warning: no tools resolved for allowed=[${activeWorkerTools.join(",")}]
|
|
919
|
+
`);
|
|
920
|
+
}
|
|
921
|
+
const effectiveThinkingLevel = clampThinkingLevelForModel(
|
|
922
|
+
model,
|
|
923
|
+
input.runtimeResolution?.effectiveThinkingLevel ?? input.thinkingLevel
|
|
924
|
+
);
|
|
925
|
+
const options = {
|
|
926
|
+
initialState: {
|
|
927
|
+
systemPrompt: input.systemPrompt,
|
|
928
|
+
model,
|
|
929
|
+
thinkingLevel: effectiveThinkingLevel,
|
|
930
|
+
tools,
|
|
931
|
+
messages: []
|
|
932
|
+
},
|
|
933
|
+
onPayload: async (payload, currentModel) => {
|
|
934
|
+
const middlewareChoice = middlewareToolChoice.current();
|
|
935
|
+
return patchWorkerRequestPayload(payload, currentModel, {
|
|
936
|
+
runtimeId: input.runtime.id,
|
|
937
|
+
thinkingLevel: effectiveThinkingLevel,
|
|
938
|
+
...input.responseSchema !== void 0 ? { responseSchema: input.responseSchema } : {},
|
|
939
|
+
toolChoiceNone: synthesisToolLock || middlewareChoice.kind === "none",
|
|
940
|
+
...middlewareChoice.kind === "required" ? { toolChoiceName: middlewareChoice.toolName } : {}
|
|
941
|
+
});
|
|
942
|
+
},
|
|
943
|
+
getApiKey: async () => input.apiKey
|
|
944
|
+
};
|
|
945
|
+
if (input.sessionId) options.sessionId = input.sessionId;
|
|
946
|
+
const { agent } = createEngineAgent(options);
|
|
947
|
+
abortWorkerForBound = () => agent.abort();
|
|
948
|
+
const unsubscribe = agent.subscribe(async (event) => {
|
|
949
|
+
if (event.type === "turn_start") workerModelRound += 1;
|
|
950
|
+
if (event.type === "tool_execution_start") middlewareToolChoice.toolStarted(event.toolName);
|
|
951
|
+
if (event.type === "tool_execution_start") {
|
|
952
|
+
runEffects.start(event.toolCallId, event.toolName, event.args);
|
|
953
|
+
}
|
|
954
|
+
if (event.type === "tool_execution_end") {
|
|
955
|
+
runEffects.finish(event.toolCallId, event.isError === true);
|
|
956
|
+
}
|
|
957
|
+
if (event.type === "tool_execution_start" && event.toolName === ToolNames.Read) {
|
|
958
|
+
const args = event.args;
|
|
959
|
+
const readPath = typeof args.path === "string" ? args.path.trim() : "";
|
|
960
|
+
const offset = typeof args.offset === "number" && Number.isFinite(args.offset) && args.offset > 0 ? Math.floor(args.offset) : null;
|
|
961
|
+
if (readPath.length > 0) {
|
|
962
|
+
pendingReadCitations.set(event.toolCallId, { path: readPath, offset, tail: args.tail !== void 0 });
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (event.type === "tool_execution_end" && event.toolName === ToolNames.Read) {
|
|
966
|
+
const request = pendingReadCitations.get(event.toolCallId);
|
|
967
|
+
pendingReadCitations.delete(event.toolCallId);
|
|
968
|
+
if (request !== void 0 && event.isError !== true) recordObservedRead(request, event.result);
|
|
969
|
+
}
|
|
970
|
+
if (synthesisToolLock && event.type === "message_end") {
|
|
971
|
+
sanitizeLockedSynthesisMessage(event.message);
|
|
972
|
+
}
|
|
973
|
+
const contract = input.resultContract;
|
|
974
|
+
if (contract && event.type === "message_end" && isTerminalAssistantMessage(event.message)) {
|
|
975
|
+
const violation = terminalContractViolation(
|
|
976
|
+
contract,
|
|
977
|
+
event.message,
|
|
978
|
+
contractCwd,
|
|
979
|
+
observedReadRanges,
|
|
980
|
+
runEffects.snapshot()
|
|
981
|
+
);
|
|
982
|
+
if (violation !== null) {
|
|
983
|
+
if (resultContractRepairsQueued < RESULT_CONTRACT_REPAIR_LIMIT) {
|
|
984
|
+
resultContractRepairsQueued += 1;
|
|
985
|
+
synthesisToolLock = true;
|
|
986
|
+
agent.followUp(
|
|
987
|
+
taskMessage(
|
|
988
|
+
resultContractRepairMessage({
|
|
989
|
+
contract,
|
|
990
|
+
reason: violation,
|
|
991
|
+
attempt: resultContractRepairsQueued,
|
|
992
|
+
anchors: observedReadAnchors()
|
|
993
|
+
})
|
|
994
|
+
)
|
|
995
|
+
);
|
|
996
|
+
} else if (workerBoundFailure === null) {
|
|
997
|
+
workerBoundFailure = `result contract failed after ${RESULT_CONTRACT_REPAIR_LIMIT} bounded repair rounds: ${violation}`;
|
|
998
|
+
emit({ type: "clio_run_outcome", payload: { outcomeCode: "result_contract_exhausted" } });
|
|
999
|
+
process.stderr.write(`[worker] ${workerBoundFailure}
|
|
1000
|
+
`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
emit(event);
|
|
1005
|
+
if (stopAfterToolResultCallId !== null && event.type === "message_end" && event.message.role === "toolResult" && (stopAfterToolResultCallId.length === 0 || event.message.toolCallId === stopAfterToolResultCallId)) {
|
|
1006
|
+
stopAfterToolResultCallId = null;
|
|
1007
|
+
workerBoundAborted = true;
|
|
1008
|
+
abortWorkerForBound?.();
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
const onPermission = input.onPermission ?? "deny";
|
|
1012
|
+
const escalationConfig = onPermission === "escalate" ? {
|
|
1013
|
+
timeoutMs: input.escalation?.timeoutMs ?? DEFAULT_ESCALATION_TIMEOUT_MS,
|
|
1014
|
+
fallback: input.escalation?.fallback ?? DEFAULT_ESCALATION_FALLBACK
|
|
1015
|
+
} : null;
|
|
1016
|
+
let permissionFailure = false;
|
|
1017
|
+
const denyReason = (tool, actionClass) => `permission denied by policy: dispatched workers run non-interactively (workers.onPermission=deny); ${tool} requires ${actionClass} confirmation`;
|
|
1018
|
+
const failReason = (tool, actionClass) => `permission required for ${tool} (${actionClass}); workers.onPermission=fail ends this run`;
|
|
1019
|
+
let activeEscalation = null;
|
|
1020
|
+
const clearActiveEscalation = () => {
|
|
1021
|
+
if (activeEscalation) {
|
|
1022
|
+
clearTimeout(activeEscalation.timer);
|
|
1023
|
+
activeEscalation = null;
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
const resolveEscalation = (requestId, decision, source) => {
|
|
1027
|
+
const active = activeEscalation;
|
|
1028
|
+
if (!active || active.requestId !== requestId) return false;
|
|
1029
|
+
clearActiveEscalation();
|
|
1030
|
+
if (decision === "approve") {
|
|
1031
|
+
emit({
|
|
1032
|
+
type: "clio_permission_resolved",
|
|
1033
|
+
payload: {
|
|
1034
|
+
tool: active.tool,
|
|
1035
|
+
actionClass: active.actionClass,
|
|
1036
|
+
mode: "escalate",
|
|
1037
|
+
source,
|
|
1038
|
+
requestId,
|
|
1039
|
+
decision: "approved",
|
|
1040
|
+
reason: `operator approved permission escalation for ${active.tool} (${active.actionClass})`
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
void registry.resumeParkedCalls({
|
|
1044
|
+
actionClass: active.actionClass,
|
|
1045
|
+
requestId,
|
|
1046
|
+
requestedBy: `escalation:${source}`
|
|
1047
|
+
});
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
const effectiveFail = source === "timeout" && escalationConfig?.fallback === "fail";
|
|
1051
|
+
const mode = effectiveFail ? "fail" : "deny";
|
|
1052
|
+
const denialContext = source === "timeout" ? "escalation timed out with no operator decision" : "operator denied";
|
|
1053
|
+
const reason = effectiveFail ? `permission required for ${active.tool} (${active.actionClass}); ${denialContext} and workers fallback=fail ends this run` : `permission denied by ${source === "timeout" ? "escalation timeout fallback" : "operator"}: ${active.tool} requires ${active.actionClass} confirmation`;
|
|
1054
|
+
emit({
|
|
1055
|
+
type: "clio_permission_resolved",
|
|
1056
|
+
payload: {
|
|
1057
|
+
tool: active.tool,
|
|
1058
|
+
actionClass: active.actionClass,
|
|
1059
|
+
mode,
|
|
1060
|
+
source,
|
|
1061
|
+
requestId,
|
|
1062
|
+
decision: "denied",
|
|
1063
|
+
reason
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
if (effectiveFail) {
|
|
1067
|
+
permissionFailure = true;
|
|
1068
|
+
registry.cancelParkedCalls(reason);
|
|
1069
|
+
agent.abort();
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
1072
|
+
if (source === "operator") registry.cancelParkedCall(requestId, reason);
|
|
1073
|
+
else registry.cancelParkedCalls(reason);
|
|
1074
|
+
return true;
|
|
1075
|
+
};
|
|
1076
|
+
const denyActiveEscalationOnAbort = (reason) => {
|
|
1077
|
+
const active = activeEscalation;
|
|
1078
|
+
if (!active) return;
|
|
1079
|
+
emit({
|
|
1080
|
+
type: "clio_permission_resolved",
|
|
1081
|
+
payload: {
|
|
1082
|
+
tool: active.tool,
|
|
1083
|
+
actionClass: active.actionClass,
|
|
1084
|
+
mode: "escalate",
|
|
1085
|
+
source: "operator",
|
|
1086
|
+
requestId: active.requestId,
|
|
1087
|
+
decision: "denied",
|
|
1088
|
+
reason
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
clearActiveEscalation();
|
|
1092
|
+
};
|
|
1093
|
+
const unsubscribePermission = registry.onPermissionRequired((call, decision, meta) => {
|
|
1094
|
+
const actionClass = decision.classification.actionClass;
|
|
1095
|
+
if (escalationConfig) {
|
|
1096
|
+
if (activeEscalation !== null) return;
|
|
1097
|
+
const requestId = meta.requestId;
|
|
1098
|
+
const timer = setTimeout(() => resolveEscalation(requestId, "deny", "timeout"), escalationConfig.timeoutMs);
|
|
1099
|
+
activeEscalation = { requestId, tool: call.tool, actionClass, timer };
|
|
1100
|
+
const target = describeCallTarget(call.args).slice(0, 200);
|
|
1101
|
+
emit({
|
|
1102
|
+
type: "clio_permission_escalated",
|
|
1103
|
+
payload: {
|
|
1104
|
+
requestId,
|
|
1105
|
+
tool: call.tool,
|
|
1106
|
+
summary: `${call.tool} requires ${actionClass} confirmation`,
|
|
1107
|
+
...target.length > 0 ? { target } : {},
|
|
1108
|
+
axis: meta.axis,
|
|
1109
|
+
decision: {
|
|
1110
|
+
actionClass,
|
|
1111
|
+
reasons: decision.classification.reasons,
|
|
1112
|
+
...decision.policy?.reasonCode ? { reasonCode: decision.policy.reasonCode } : {},
|
|
1113
|
+
...decision.policy?.ruleId ? { ruleId: decision.policy.ruleId } : {},
|
|
1114
|
+
...decision.policy?.policySource ? { policySource: decision.policy.policySource } : {}
|
|
1115
|
+
},
|
|
1116
|
+
timeoutMs: escalationConfig.timeoutMs
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const reason = onPermission === "fail" ? failReason(call.tool, actionClass) : denyReason(call.tool, actionClass);
|
|
1122
|
+
emit({
|
|
1123
|
+
type: "clio_permission_resolved",
|
|
1124
|
+
payload: {
|
|
1125
|
+
tool: call.tool,
|
|
1126
|
+
actionClass,
|
|
1127
|
+
mode: onPermission,
|
|
1128
|
+
source: "policy",
|
|
1129
|
+
requestId: meta.requestId,
|
|
1130
|
+
reason
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
if (onPermission === "fail") {
|
|
1134
|
+
permissionFailure = true;
|
|
1135
|
+
registry.cancelParkedCalls(reason);
|
|
1136
|
+
agent.abort();
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
registry.cancelParkedCalls(reason);
|
|
1140
|
+
});
|
|
1141
|
+
const promise = (async () => {
|
|
1142
|
+
try {
|
|
1143
|
+
await agent.prompt(promptMessagesForWorker(input));
|
|
1144
|
+
await agent.waitForIdle();
|
|
1145
|
+
unsubscribe();
|
|
1146
|
+
if (workerBoundFailure !== null) {
|
|
1147
|
+
return { messages: agent.state.messages, exitCode: 1 };
|
|
1148
|
+
}
|
|
1149
|
+
if (permissionFailure) {
|
|
1150
|
+
return { messages: agent.state.messages, exitCode: WORKER_EXIT_PERMISSION_REQUIRED };
|
|
1151
|
+
}
|
|
1152
|
+
const messages = agent.state.messages;
|
|
1153
|
+
const errorMessage = getTerminalAgentError(messages);
|
|
1154
|
+
if (errorMessage !== null) {
|
|
1155
|
+
if (errorMessage.length > 0) {
|
|
1156
|
+
process.stderr.write(`[worker] agent ended with stopReason=error: ${errorMessage}
|
|
1157
|
+
`);
|
|
1158
|
+
}
|
|
1159
|
+
return { messages, exitCode: 1 };
|
|
1160
|
+
}
|
|
1161
|
+
return { messages, exitCode: 0 };
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
unsubscribe();
|
|
1164
|
+
if (workerBoundFailure !== null) {
|
|
1165
|
+
return { messages: agent.state.messages, exitCode: 1 };
|
|
1166
|
+
}
|
|
1167
|
+
if (permissionFailure) {
|
|
1168
|
+
return { messages: agent.state.messages, exitCode: WORKER_EXIT_PERMISSION_REQUIRED };
|
|
1169
|
+
}
|
|
1170
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1171
|
+
emit({ type: "agent_end", messages: agent.state.messages });
|
|
1172
|
+
process.stderr.write(`[worker] agent error: ${msg}
|
|
1173
|
+
`);
|
|
1174
|
+
return { messages: agent.state.messages, exitCode: 1 };
|
|
1175
|
+
} finally {
|
|
1176
|
+
clearActiveEscalation();
|
|
1177
|
+
unsubscribePermission();
|
|
1178
|
+
}
|
|
1179
|
+
})();
|
|
1180
|
+
return {
|
|
1181
|
+
promise,
|
|
1182
|
+
abort: () => {
|
|
1183
|
+
if (escalationConfig) {
|
|
1184
|
+
const reason = "run aborted while a permission escalation was pending";
|
|
1185
|
+
denyActiveEscalationOnAbort(reason);
|
|
1186
|
+
registry.cancelParkedCalls(reason);
|
|
1187
|
+
}
|
|
1188
|
+
agent.abort();
|
|
1189
|
+
},
|
|
1190
|
+
steer: (text) => {
|
|
1191
|
+
const trimmed = text.trim();
|
|
1192
|
+
if (trimmed.length === 0) return false;
|
|
1193
|
+
agent.steer(taskMessage(trimmed));
|
|
1194
|
+
return true;
|
|
1195
|
+
},
|
|
1196
|
+
resolvePermission: (requestId, decision) => resolveEscalation(requestId, decision, "operator")
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/worker/control-lane.ts
|
|
1201
|
+
function emitControlFrame(frame) {
|
|
1202
|
+
try {
|
|
1203
|
+
process.stderr.write(encodeControlFrame(frame));
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/worker/event-projection.ts
|
|
1209
|
+
function isRecord(value) {
|
|
1210
|
+
return typeof value === "object" && value !== null;
|
|
1211
|
+
}
|
|
1212
|
+
function projectWorkerEventForStdout(event) {
|
|
1213
|
+
if (!isRecord(event) || event.type !== "message_update") return event;
|
|
1214
|
+
const { message: _message, assistantMessageEvent, ...rest } = event;
|
|
1215
|
+
if (!isRecord(assistantMessageEvent)) return rest;
|
|
1216
|
+
const { partial: _partial, ...slimAssistantMessageEvent } = assistantMessageEvent;
|
|
1217
|
+
return { ...rest, assistantMessageEvent: slimAssistantMessageEvent };
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/worker/heartbeat.ts
|
|
1221
|
+
function startWorkerHeartbeat(intervalMs = 1e3) {
|
|
1222
|
+
emitControlFrame({ kind: "heartbeat", at: Date.now() });
|
|
1223
|
+
const id = setInterval(() => emitControlFrame({ kind: "heartbeat", at: Date.now() }), intervalMs);
|
|
1224
|
+
id.unref?.();
|
|
1225
|
+
return () => clearInterval(id);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/worker/ledger-mirror.ts
|
|
1229
|
+
var WORKER_AGENT_LEDGER_POST_CAP = 20;
|
|
1230
|
+
function createWorkerAgentLedgerMirror() {
|
|
1231
|
+
const bySequence = /* @__PURE__ */ new Map();
|
|
1232
|
+
let watermark = 0;
|
|
1233
|
+
return {
|
|
1234
|
+
apply(entries) {
|
|
1235
|
+
for (const entry of entries) {
|
|
1236
|
+
bySequence.set(entry.sequence, entry);
|
|
1237
|
+
if (entry.sequence > watermark) watermark = entry.sequence;
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
entries() {
|
|
1241
|
+
return [...bySequence.values()].sort((left, right) => left.sequence - right.sequence);
|
|
1242
|
+
},
|
|
1243
|
+
watermark() {
|
|
1244
|
+
return watermark;
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function createWorkerAgentLedgerPort(deps) {
|
|
1249
|
+
const mirror = deps.mirror ?? createWorkerAgentLedgerMirror();
|
|
1250
|
+
const { ledger, emitControlFrame: emitControlFrame2 } = deps;
|
|
1251
|
+
let posted = 0;
|
|
1252
|
+
return {
|
|
1253
|
+
post(body) {
|
|
1254
|
+
if (ledger === void 0) return { ok: false, reason: "no-ledger" };
|
|
1255
|
+
const parsed = parseAgentLedgerBody(body);
|
|
1256
|
+
if (!parsed.ok) return { ok: false, reason: "invalid-body" };
|
|
1257
|
+
if (posted >= WORKER_AGENT_LEDGER_POST_CAP) return { ok: false, reason: "per-run-cap" };
|
|
1258
|
+
posted += 1;
|
|
1259
|
+
emitControlFrame2({ kind: "ledger_post", body: parsed.body });
|
|
1260
|
+
return { ok: true };
|
|
1261
|
+
},
|
|
1262
|
+
read() {
|
|
1263
|
+
if (ledger === void 0) return null;
|
|
1264
|
+
return { open: true, watermark: mirror.watermark(), entries: mirror.entries() };
|
|
1265
|
+
},
|
|
1266
|
+
acceptDelta(entries) {
|
|
1267
|
+
mirror.apply(entries);
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/worker/ndjson.ts
|
|
1273
|
+
function emitEvent(event) {
|
|
1274
|
+
try {
|
|
1275
|
+
process.stdout.write(`${JSON.stringify(event)}
|
|
1276
|
+
`);
|
|
1277
|
+
} catch (err) {
|
|
1278
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1279
|
+
process.stderr.write(`[worker] failed to emit event: ${msg}
|
|
1280
|
+
`);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function drainStdout(timeoutMs = 2e3) {
|
|
1284
|
+
return new Promise((resolve) => {
|
|
1285
|
+
let done = false;
|
|
1286
|
+
const finish = () => {
|
|
1287
|
+
if (done) return;
|
|
1288
|
+
done = true;
|
|
1289
|
+
clearTimeout(timer);
|
|
1290
|
+
resolve();
|
|
1291
|
+
};
|
|
1292
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
1293
|
+
try {
|
|
1294
|
+
process.stdout.write("", finish);
|
|
1295
|
+
} catch {
|
|
1296
|
+
finish();
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/worker/resource-facts.ts
|
|
1302
|
+
import { execFileSync } from "node:child_process";
|
|
1303
|
+
import { cpus, freemem, hostname, totalmem } from "node:os";
|
|
1304
|
+
var GPU_PROBE_TIMEOUT_MS = 1e3;
|
|
1305
|
+
function probeNvidiaGpus() {
|
|
1306
|
+
try {
|
|
1307
|
+
const stdout = execFileSync("nvidia-smi", ["--query-gpu=memory.total", "--format=csv,noheader,nounits"], {
|
|
1308
|
+
timeout: GPU_PROBE_TIMEOUT_MS,
|
|
1309
|
+
encoding: "utf8",
|
|
1310
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1311
|
+
});
|
|
1312
|
+
const megabytes = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => Number.parseInt(line, 10)).filter((value) => Number.isFinite(value) && value > 0);
|
|
1313
|
+
if (megabytes.length === 0) return { gpuCount: UNKNOWN_RESOURCE, vramBytes: UNKNOWN_RESOURCE };
|
|
1314
|
+
const totalBytes = megabytes.reduce((sum, value) => sum + value, 0) * 1024 * 1024;
|
|
1315
|
+
return { gpuCount: knownResource(megabytes.length), vramBytes: knownResource(totalBytes) };
|
|
1316
|
+
} catch {
|
|
1317
|
+
return { gpuCount: UNKNOWN_RESOURCE, vramBytes: UNKNOWN_RESOURCE };
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function observeCpuCount() {
|
|
1321
|
+
const count = cpus().length;
|
|
1322
|
+
return count > 0 ? knownResource(count) : UNKNOWN_RESOURCE;
|
|
1323
|
+
}
|
|
1324
|
+
function observeMemory(read) {
|
|
1325
|
+
const bytes = read();
|
|
1326
|
+
return Number.isFinite(bytes) && bytes > 0 ? knownResource(bytes) : UNKNOWN_RESOURCE;
|
|
1327
|
+
}
|
|
1328
|
+
function observeWorkerResourceFacts(labels = []) {
|
|
1329
|
+
const gpu = probeNvidiaGpus();
|
|
1330
|
+
return {
|
|
1331
|
+
labels: [...labels].slice(0, WORKER_RESOURCE_LABEL_MAX),
|
|
1332
|
+
cpuCount: observeCpuCount(),
|
|
1333
|
+
totalMemoryBytes: observeMemory(totalmem),
|
|
1334
|
+
freeMemoryBytes: observeMemory(freemem),
|
|
1335
|
+
gpuCount: gpu.gpuCount,
|
|
1336
|
+
vramBytes: gpu.vramBytes,
|
|
1337
|
+
residentModels: UNKNOWN_RESOURCE
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function observeHostIdentity() {
|
|
1341
|
+
const name = hostname();
|
|
1342
|
+
return name.length > 0 ? name : "unknown-host";
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/worker/runtime-registry.ts
|
|
1346
|
+
async function resolveWorkerRuntime(runtimeId) {
|
|
1347
|
+
const registry = getRuntimeRegistry();
|
|
1348
|
+
registerBuiltinRuntimes(registry);
|
|
1349
|
+
await loadPluginRuntimes(registry, readSettings());
|
|
1350
|
+
return registry.get(runtimeId);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/worker/stdin-demux.ts
|
|
1354
|
+
function createOrderedSteerHandler(deliver, onAccepted, onRejected) {
|
|
1355
|
+
let tail = Promise.resolve();
|
|
1356
|
+
return (steer) => {
|
|
1357
|
+
tail = tail.then(async () => {
|
|
1358
|
+
let rejection = null;
|
|
1359
|
+
try {
|
|
1360
|
+
if (await deliver(steer.text)) onAccepted(steer);
|
|
1361
|
+
else rejection = "runtime does not accept live guidance";
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
rejection = error instanceof Error ? error.message : String(error);
|
|
1364
|
+
}
|
|
1365
|
+
if (rejection !== null) {
|
|
1366
|
+
try {
|
|
1367
|
+
onRejected(rejection);
|
|
1368
|
+
} catch {
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
return tail;
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function createWorkerStdinDemux() {
|
|
1376
|
+
let buffer = "";
|
|
1377
|
+
let specResolve = null;
|
|
1378
|
+
let specReject = null;
|
|
1379
|
+
let specValue = null;
|
|
1380
|
+
let specError = null;
|
|
1381
|
+
let specReceived = false;
|
|
1382
|
+
let closed = false;
|
|
1383
|
+
let steerHandler = null;
|
|
1384
|
+
const pendingSteers = [];
|
|
1385
|
+
let permissionHandler = null;
|
|
1386
|
+
const pendingPermissionDecisions = [];
|
|
1387
|
+
let ledgerDeltaHandler = null;
|
|
1388
|
+
const pendingLedgerDeltas = [];
|
|
1389
|
+
let droppedLines = 0;
|
|
1390
|
+
let channelCloseHandler = null;
|
|
1391
|
+
let channelCloseDelivered = false;
|
|
1392
|
+
let channelClosedAfterSpec = false;
|
|
1393
|
+
function deliverChannelClose() {
|
|
1394
|
+
if (channelCloseDelivered || channelCloseHandler === null) return;
|
|
1395
|
+
channelCloseDelivered = true;
|
|
1396
|
+
channelCloseHandler();
|
|
1397
|
+
}
|
|
1398
|
+
function resolveSpec(spec) {
|
|
1399
|
+
specValue = spec;
|
|
1400
|
+
specResolve?.(spec);
|
|
1401
|
+
}
|
|
1402
|
+
function rejectSpec(err) {
|
|
1403
|
+
specError = err;
|
|
1404
|
+
specReject?.(err);
|
|
1405
|
+
}
|
|
1406
|
+
function deliverSteer(steer) {
|
|
1407
|
+
if (steerHandler) {
|
|
1408
|
+
steerHandler(steer);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
pendingSteers.push(steer);
|
|
1412
|
+
}
|
|
1413
|
+
function deliverPermissionDecision(requestId, decision) {
|
|
1414
|
+
if (permissionHandler) {
|
|
1415
|
+
permissionHandler({ requestId, decision });
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
pendingPermissionDecisions.push({ requestId, decision });
|
|
1419
|
+
}
|
|
1420
|
+
function deliverLedgerDelta(entries) {
|
|
1421
|
+
if (ledgerDeltaHandler) {
|
|
1422
|
+
ledgerDeltaHandler(entries);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
pendingLedgerDeltas.push(entries);
|
|
1426
|
+
}
|
|
1427
|
+
function processPostSpecLine(line) {
|
|
1428
|
+
let value;
|
|
1429
|
+
try {
|
|
1430
|
+
value = JSON.parse(line);
|
|
1431
|
+
} catch {
|
|
1432
|
+
droppedLines += 1;
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
if (typeof value === "object" && value !== null && value.type === "steer" && typeof value.text === "string" && value.text.trim().length > 0 && Number.isSafeInteger(value.sequence) && Number(value.sequence) > 0) {
|
|
1436
|
+
deliverSteer({
|
|
1437
|
+
text: value.text,
|
|
1438
|
+
sequence: value.sequence
|
|
1439
|
+
});
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (typeof value === "object" && value !== null && value.type === "permission_decision" && typeof value.requestId === "string" && value.requestId.length > 0 && (value.decision === "approve" || value.decision === "deny")) {
|
|
1443
|
+
deliverPermissionDecision(
|
|
1444
|
+
value.requestId,
|
|
1445
|
+
value.decision
|
|
1446
|
+
);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
if (typeof value === "object" && value !== null && value.type === "ledger_delta") {
|
|
1450
|
+
const raw = value.entries;
|
|
1451
|
+
if (!Array.isArray(raw)) {
|
|
1452
|
+
droppedLines += 1;
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const entries = [];
|
|
1456
|
+
for (const candidate of raw) {
|
|
1457
|
+
const entry = parseAgentLedgerEntry(candidate);
|
|
1458
|
+
if (entry !== null) entries.push(entry);
|
|
1459
|
+
}
|
|
1460
|
+
if (entries.length === 0) {
|
|
1461
|
+
droppedLines += 1;
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
deliverLedgerDelta(entries);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
droppedLines += 1;
|
|
1468
|
+
}
|
|
1469
|
+
function processLine(line) {
|
|
1470
|
+
if (line.length === 0) return;
|
|
1471
|
+
if (!withinFrameBudget(line, WORKER_STDIN_FRAME_MAX_BYTES)) {
|
|
1472
|
+
if (!specReceived) {
|
|
1473
|
+
specReceived = true;
|
|
1474
|
+
rejectSpec(new Error(`WorkerSpec line exceeds the ${WORKER_STDIN_FRAME_MAX_BYTES} byte stdin frame limit`));
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
droppedLines += 1;
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
if (!specReceived) {
|
|
1481
|
+
specReceived = true;
|
|
1482
|
+
try {
|
|
1483
|
+
resolveSpec(parseWorkerSpec(JSON.parse(line)));
|
|
1484
|
+
} catch (err) {
|
|
1485
|
+
rejectSpec(err instanceof Error ? err : new Error(String(err)));
|
|
1486
|
+
}
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
processPostSpecLine(line);
|
|
1490
|
+
}
|
|
1491
|
+
let discardingOversizedLine = false;
|
|
1492
|
+
return {
|
|
1493
|
+
feed(chunk) {
|
|
1494
|
+
if (closed) return;
|
|
1495
|
+
buffer += chunk;
|
|
1496
|
+
let idx = buffer.indexOf("\n");
|
|
1497
|
+
while (idx >= 0) {
|
|
1498
|
+
const line = buffer.slice(0, idx);
|
|
1499
|
+
buffer = buffer.slice(idx + 1);
|
|
1500
|
+
if (discardingOversizedLine) {
|
|
1501
|
+
discardingOversizedLine = false;
|
|
1502
|
+
droppedLines += 1;
|
|
1503
|
+
} else {
|
|
1504
|
+
processLine(line);
|
|
1505
|
+
}
|
|
1506
|
+
idx = buffer.indexOf("\n");
|
|
1507
|
+
}
|
|
1508
|
+
if (!withinFrameBudget(buffer, WORKER_STDIN_FRAME_MAX_BYTES)) {
|
|
1509
|
+
if (!specReceived) {
|
|
1510
|
+
specReceived = true;
|
|
1511
|
+
rejectSpec(new Error(`WorkerSpec line exceeds the ${WORKER_STDIN_FRAME_MAX_BYTES} byte stdin frame limit`));
|
|
1512
|
+
}
|
|
1513
|
+
buffer = "";
|
|
1514
|
+
discardingOversizedLine = true;
|
|
1515
|
+
}
|
|
1516
|
+
},
|
|
1517
|
+
eof() {
|
|
1518
|
+
if (closed) return;
|
|
1519
|
+
closed = true;
|
|
1520
|
+
if (buffer.length > 0) {
|
|
1521
|
+
processLine(buffer);
|
|
1522
|
+
buffer = "";
|
|
1523
|
+
}
|
|
1524
|
+
if (!specReceived) {
|
|
1525
|
+
rejectSpec(new Error("worker stdin closed before spec received"));
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
channelClosedAfterSpec = true;
|
|
1529
|
+
deliverChannelClose();
|
|
1530
|
+
},
|
|
1531
|
+
readSpec() {
|
|
1532
|
+
if (specValue) return Promise.resolve(specValue);
|
|
1533
|
+
if (specError) return Promise.reject(specError);
|
|
1534
|
+
if (closed && !specReceived) return Promise.reject(new Error("worker stdin closed before spec received"));
|
|
1535
|
+
return new Promise((resolve, reject) => {
|
|
1536
|
+
specResolve = resolve;
|
|
1537
|
+
specReject = reject;
|
|
1538
|
+
});
|
|
1539
|
+
},
|
|
1540
|
+
onSteer(handler) {
|
|
1541
|
+
steerHandler = handler;
|
|
1542
|
+
while (pendingSteers.length > 0) {
|
|
1543
|
+
const steer = pendingSteers.shift();
|
|
1544
|
+
if (steer !== void 0) handler(steer);
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
onPermissionDecision(handler) {
|
|
1548
|
+
permissionHandler = handler;
|
|
1549
|
+
while (pendingPermissionDecisions.length > 0) {
|
|
1550
|
+
const entry = pendingPermissionDecisions.shift();
|
|
1551
|
+
if (entry !== void 0) handler(entry);
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
onLedgerDelta(handler) {
|
|
1555
|
+
ledgerDeltaHandler = handler;
|
|
1556
|
+
while (pendingLedgerDeltas.length > 0) {
|
|
1557
|
+
const batch = pendingLedgerDeltas.shift();
|
|
1558
|
+
if (batch !== void 0) handler(batch);
|
|
1559
|
+
}
|
|
1560
|
+
},
|
|
1561
|
+
droppedLineCount() {
|
|
1562
|
+
return droppedLines;
|
|
1563
|
+
},
|
|
1564
|
+
onChannelClose(handler) {
|
|
1565
|
+
channelCloseHandler = handler;
|
|
1566
|
+
if (channelClosedAfterSpec) deliverChannelClose();
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// src/worker/entry.ts
|
|
1572
|
+
var CHANNEL_CLOSE_EXIT_GRACE_MS = 5e3;
|
|
1573
|
+
function configuredNodeLabels() {
|
|
1574
|
+
return (process.env.CLIO_CODER_WORKER_LABELS ?? "").split(",").map((label) => label.trim()).filter((label) => label.length > 0);
|
|
1575
|
+
}
|
|
1576
|
+
function announcedProcessGroupId() {
|
|
1577
|
+
const declared = Number.parseInt(process.env.CLIO_CODER_WORKER_PGID ?? "", 10);
|
|
1578
|
+
if (Number.isSafeInteger(declared) && declared > 0) return declared;
|
|
1579
|
+
return process.platform === "win32" ? null : process.pid;
|
|
1580
|
+
}
|
|
1581
|
+
function announceWorker(spec, input) {
|
|
1582
|
+
emitControlFrame({
|
|
1583
|
+
kind: "announce",
|
|
1584
|
+
attestation: {
|
|
1585
|
+
protocolVersion: WORKER_PROTOCOL_VERSION,
|
|
1586
|
+
specVersion: spec.specVersion,
|
|
1587
|
+
pid: process.pid,
|
|
1588
|
+
processGroupId: announcedProcessGroupId(),
|
|
1589
|
+
host: observeHostIdentity(),
|
|
1590
|
+
settingsFingerprint: spec.settingsFingerprint,
|
|
1591
|
+
specDigest: workerSpecDigest(spec),
|
|
1592
|
+
runtimeId: spec.runtimeId,
|
|
1593
|
+
targetId: spec.target.id,
|
|
1594
|
+
endpointIdentityHash: endpointIdentityHash(spec.target.url),
|
|
1595
|
+
wireModelId: spec.wireModelId,
|
|
1596
|
+
toolSignature: attestedToolSignature({
|
|
1597
|
+
allowedTools: spec.allowedTools,
|
|
1598
|
+
toolsSupported: workerProviderSupportsTools(input),
|
|
1599
|
+
...spec.toolProfile !== void 0 ? { toolProfile: spec.toolProfile } : {},
|
|
1600
|
+
agentId: spec.agentId,
|
|
1601
|
+
task: spec.task
|
|
1602
|
+
}),
|
|
1603
|
+
resources: observeWorkerResourceFacts(configuredNodeLabels())
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
async function main() {
|
|
1608
|
+
setResidencyNoticeSink((notice) => {
|
|
1609
|
+
process.stderr.write(`[worker] residency ${notice.kind}: ${notice.message}
|
|
1610
|
+
`);
|
|
1611
|
+
if (notice.kind === "will-not-fit") {
|
|
1612
|
+
emitEvent({ type: "clio_run_outcome", payload: { outcomeCode: "vram_capacity_fit_failure" } });
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
const demux = createWorkerStdinDemux();
|
|
1616
|
+
process.stdin.setEncoding("utf8");
|
|
1617
|
+
process.stdin.on("data", (chunk) => demux.feed(chunk));
|
|
1618
|
+
process.stdin.on("end", () => demux.eof());
|
|
1619
|
+
process.stdin.on("error", () => demux.eof());
|
|
1620
|
+
process.stdin.resume();
|
|
1621
|
+
const spec = await demux.readSpec();
|
|
1622
|
+
setProtectedModelsProvider(() => spec.protectedModels ?? []);
|
|
1623
|
+
const stopHeartbeat = startWorkerHeartbeat();
|
|
1624
|
+
const runtime = await resolveWorkerRuntime(spec.runtimeId);
|
|
1625
|
+
if (!runtime) {
|
|
1626
|
+
process.stderr.write(`[worker] runtime '${spec.runtimeId}' not registered
|
|
1627
|
+
`);
|
|
1628
|
+
stopHeartbeat();
|
|
1629
|
+
return 2;
|
|
1630
|
+
}
|
|
1631
|
+
try {
|
|
1632
|
+
validateRehydratedWorkerRuntime(spec, runtime);
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
process.stderr.write(`[worker] ${err instanceof Error ? err.message : String(err)}
|
|
1635
|
+
`);
|
|
1636
|
+
stopHeartbeat();
|
|
1637
|
+
return 2;
|
|
1638
|
+
}
|
|
1639
|
+
const ledgerPort = createWorkerAgentLedgerPort({
|
|
1640
|
+
...spec.ledger !== void 0 ? { ledger: spec.ledger } : {},
|
|
1641
|
+
emitControlFrame
|
|
1642
|
+
});
|
|
1643
|
+
demux.onLedgerDelta((entries) => ledgerPort.acceptDelta(entries));
|
|
1644
|
+
const input = {
|
|
1645
|
+
systemPrompt: spec.systemPrompt,
|
|
1646
|
+
dynamicPromptMessages: spec.dynamicPromptMessages ?? [],
|
|
1647
|
+
agentId: spec.agentId,
|
|
1648
|
+
task: spec.task,
|
|
1649
|
+
target: spec.target,
|
|
1650
|
+
runtime,
|
|
1651
|
+
wireModelId: spec.wireModelId,
|
|
1652
|
+
allowedTools: spec.allowedTools,
|
|
1653
|
+
budget: spec.budget,
|
|
1654
|
+
...spec.noSkills !== void 0 ? { noSkills: spec.noSkills } : {},
|
|
1655
|
+
...spec.skillPaths !== void 0 ? { skillPaths: [...spec.skillPaths] } : {},
|
|
1656
|
+
...spec.agentSkills !== void 0 ? { agentSkills: [...spec.agentSkills] } : {},
|
|
1657
|
+
...spec.trustProjectCompatRoots !== void 0 ? { trustProjectCompatRoots: spec.trustProjectCompatRoots } : {},
|
|
1658
|
+
...spec.onPermission !== void 0 ? { onPermission: spec.onPermission } : {},
|
|
1659
|
+
...spec.escalation !== void 0 ? { escalation: spec.escalation } : {},
|
|
1660
|
+
...spec.toolProfile !== void 0 ? { toolProfile: spec.toolProfile } : {},
|
|
1661
|
+
...spec.autonomy !== void 0 ? { autonomy: spec.autonomy } : {},
|
|
1662
|
+
...spec.writeRoots !== void 0 ? { writeRoots: [...spec.writeRoots] } : {},
|
|
1663
|
+
...spec.protectedArtifactState !== void 0 ? {
|
|
1664
|
+
protectedArtifactState: {
|
|
1665
|
+
version: spec.protectedArtifactState.version,
|
|
1666
|
+
artifacts: structuredClone(spec.protectedArtifactState.artifacts)
|
|
1667
|
+
}
|
|
1668
|
+
} : {},
|
|
1669
|
+
...spec.responseSchema !== void 0 ? { responseSchema: spec.responseSchema } : {},
|
|
1670
|
+
...spec.resultContract !== void 0 ? { resultContract: spec.resultContract } : {},
|
|
1671
|
+
...spec.product !== void 0 ? { product: spec.product } : {},
|
|
1672
|
+
agentLedger: ledgerPort,
|
|
1673
|
+
cwd: process.cwd()
|
|
1674
|
+
};
|
|
1675
|
+
if (spec.modelCapabilities) input.modelCapabilities = spec.modelCapabilities;
|
|
1676
|
+
if (spec.sessionId) input.sessionId = spec.sessionId;
|
|
1677
|
+
if (spec.apiKey) input.apiKey = spec.apiKey;
|
|
1678
|
+
if (spec.thinkingLevel) input.thinkingLevel = spec.thinkingLevel;
|
|
1679
|
+
if (spec.runtimeResolution) input.runtimeResolution = spec.runtimeResolution;
|
|
1680
|
+
if (spec.middlewareSnapshot) input.middlewareSnapshot = spec.middlewareSnapshot;
|
|
1681
|
+
announceWorker(spec, input);
|
|
1682
|
+
const handle = startWorkerRun(input, (event) => emitEvent(projectWorkerEventForStdout(event)));
|
|
1683
|
+
demux.onSteer(
|
|
1684
|
+
createOrderedSteerHandler(
|
|
1685
|
+
(text) => handle.steer?.(text) ?? false,
|
|
1686
|
+
({ text, sequence }) => emitEvent({ type: "clio_steer_received", payload: { chars: text.trim().length, sequence } }),
|
|
1687
|
+
(reason) => process.stderr.write(`[worker] dropped steer: ${reason}
|
|
1688
|
+
`)
|
|
1689
|
+
)
|
|
1690
|
+
);
|
|
1691
|
+
demux.onPermissionDecision(({ requestId, decision }) => {
|
|
1692
|
+
const resolved = handle.resolvePermission?.(requestId, decision) ?? false;
|
|
1693
|
+
if (!resolved) {
|
|
1694
|
+
process.stderr.write(`[worker] dropped permission_decision for unknown request '${requestId}'
|
|
1695
|
+
`);
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
demux.onChannelClose(() => {
|
|
1699
|
+
process.stderr.write("[worker] control channel closed; aborting run\n");
|
|
1700
|
+
emitControlFrame({ kind: "cancel_ack", at: Date.now() });
|
|
1701
|
+
handle.abort();
|
|
1702
|
+
const forceExit = setTimeout(() => process.exit(1), CHANNEL_CLOSE_EXIT_GRACE_MS);
|
|
1703
|
+
forceExit.unref?.();
|
|
1704
|
+
});
|
|
1705
|
+
const onSignal = () => {
|
|
1706
|
+
emitControlFrame({ kind: "cancel_ack", at: Date.now() });
|
|
1707
|
+
handle.abort();
|
|
1708
|
+
};
|
|
1709
|
+
process.on("SIGINT", onSignal);
|
|
1710
|
+
process.on("SIGTERM", onSignal);
|
|
1711
|
+
try {
|
|
1712
|
+
const result = await handle.promise;
|
|
1713
|
+
return result.exitCode;
|
|
1714
|
+
} finally {
|
|
1715
|
+
stopHeartbeat();
|
|
1716
|
+
process.off("SIGINT", onSignal);
|
|
1717
|
+
process.off("SIGTERM", onSignal);
|
|
1718
|
+
const dropped = demux.droppedLineCount();
|
|
1719
|
+
if (dropped > 0) {
|
|
1720
|
+
process.stderr.write(`[worker] dropped ${dropped} unrecognized stdin line(s) after the spec
|
|
1721
|
+
`);
|
|
1722
|
+
}
|
|
1723
|
+
await disposeLmStudioClients();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
main().then(
|
|
1727
|
+
async (code) => {
|
|
1728
|
+
await drainStdout();
|
|
1729
|
+
process.exit(code);
|
|
1730
|
+
},
|
|
1731
|
+
async (err) => {
|
|
1732
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1733
|
+
process.stderr.write(`[worker] fatal: ${msg}
|
|
1734
|
+
`);
|
|
1735
|
+
await drainStdout();
|
|
1736
|
+
process.exit(2);
|
|
1737
|
+
}
|
|
1738
|
+
);
|
|
1739
|
+
//# sourceMappingURL=entry.js.map
|