dsh-openwrite 0.2.28 → 0.2.33
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/LICENSE +1 -1
- package/README.md +49 -8
- package/THIRD_PARTY_NOTICES.md +4 -3
- package/companions.mjs +508 -0
- package/cordis.patch.yml +0 -2
- package/docs/CLOSED_ISSUE_FOLLOWUPS.md +1 -1
- package/docs/COMPANION_PLUGINS.md +142 -0
- package/docs/CONTEXT_SLOTS.md +1 -1
- package/docs/COST_MEASUREMENT.md +2 -2
- package/docs/DEVELOPMENT.md +6 -11
- package/docs/DSH_STORE.md +14 -2
- package/docs/EDITOR_SAFETY_REPAIRS.md +2 -2
- package/docs/INSTALL.md +112 -4
- package/docs/ISSUES_121_123.md +4 -4
- package/docs/ISSUES_140_142.md +58 -0
- package/docs/ISSUES_149_150_151.md +77 -0
- package/docs/ISSUE_123_FOLLOWUP.md +3 -2
- package/docs/ISSUE_146_REVIEW_EVIDENCE.md +53 -0
- package/docs/LEARNING.md +1 -1
- package/docs/MOBILE_READINESS.md +62 -0
- package/docs/PLUGIN_MAINTENANCE.md +1 -3
- package/docs/RELEASE_ACCEPTANCE.md +20 -3
- package/docs/REVIEW_ROUND_22.md +67 -0
- package/docs/REVIEW_ROUND_23.md +48 -0
- package/docs/REVIEW_ROUND_25.md +51 -0
- package/docs/SELECTION_UPGRADE_REPAIRS.md +2 -2
- package/docs/TOOLS.md +181 -604
- package/docs/WORKFLOWS.md +5 -5
- package/package.json +17 -21
- package/packages/openwrite-bridge/lib/client.d.ts +0 -1
- package/packages/openwrite-bridge/lib/client.js +90 -9
- package/packages/openwrite-bridge/lib/contracts-generated.js +2 -1
- package/packages/openwrite-bridge/lib/dog-delivery.d.ts +1 -0
- package/packages/openwrite-bridge/lib/dog-delivery.js +3 -1
- package/packages/openwrite-bridge/lib/dog-review.js +15 -3
- package/packages/openwrite-bridge/lib/domain.d.ts +27 -0
- package/packages/openwrite-bridge/lib/domain.js +250 -29
- package/packages/openwrite-bridge/lib/host-llm.d.ts +188 -0
- package/packages/openwrite-bridge/lib/host-llm.js +657 -0
- package/packages/openwrite-bridge/lib/host-web.d.ts +61 -0
- package/packages/openwrite-bridge/lib/host-web.js +166 -0
- package/packages/openwrite-bridge/lib/index.d.ts +4 -0
- package/packages/openwrite-bridge/lib/index.js +150 -6
- package/packages/openwrite-bridge/lib/managed-runtime.d.ts +47 -2
- package/packages/openwrite-bridge/lib/managed-runtime.js +176 -34
- package/packages/openwrite-bridge/lib/tool-policy.js +37 -12
- package/packages/openwrite-bridge/lib/tools.js +1223 -1227
- package/packages/openwrite-bridge/package.json +7 -5
- package/packages/studio-panel/lib/client.js +123207 -121534
- package/packages/studio-panel/lib/client.js.map +1 -1
- package/packages/studio-panel/lib/types/client/LaunchOpenWrite.d.ts +10 -0
- package/packages/studio-panel/lib/types/client/ModelView.d.ts +28 -4
- package/packages/studio-panel/lib/types/client/OpenWriteSettings.d.ts +64 -0
- package/packages/studio-panel/lib/types/client/SceneWorkbench.d.ts +2 -0
- package/packages/studio-panel/lib/types/client/SearchSettings.d.ts +2 -0
- package/packages/studio-panel/lib/types/client/SerialWritingView.d.ts +2 -1
- package/packages/studio-panel/lib/types/client/TurnMutationSummary.d.ts +2 -0
- package/packages/studio-panel/lib/types/client/VditorBody.d.ts +1 -1
- package/packages/studio-panel/lib/types/client/api.d.ts +41 -2
- package/packages/studio-panel/lib/types/client/browser-download.d.ts +4 -0
- package/packages/studio-panel/lib/types/client/companions.d.ts +46 -0
- package/packages/studio-panel/lib/types/client/dto.d.ts +22 -0
- package/packages/studio-panel/lib/types/client/locales.d.ts +1 -1
- package/packages/studio-panel/lib/types/client/workspace-chrome.d.ts +41 -0
- package/packages/studio-panel/package.json +5 -3
- package/plugin.mjs +159 -20
- package/preset-definition.mjs +144 -4
- package/presets/openwrite/agent.cordis.yml +7 -9
- package/presets/openwrite/ops-integration.mjs +28 -0
- package/presets/openwrite/preset.yml +2 -1
- package/presets/openwrite/skills/canon/SKILL.md +3 -3
- package/presets/openwrite/skills/delivery-artifact-query/SKILL.md +41 -0
- package/presets/openwrite/skills/dialoguequality/SKILL.md +16 -17
- package/presets/openwrite/skills/dog-delivery-verification/SKILL.md +113 -0
- package/presets/openwrite/skills/export-book/SKILL.md +3 -3
- package/presets/openwrite/skills/foreshadow/SKILL.md +1 -1
- package/presets/openwrite/skills/foreshadowing-system/SKILL.md +16 -25
- package/presets/openwrite/skills/import-artifact-query/SKILL.md +30 -0
- package/presets/openwrite/skills/learn/SKILL.md +3 -3
- package/presets/openwrite/skills/novel-creator/SKILL.md +10 -10
- package/presets/openwrite/skills/novel-reviewer/SKILL.md +11 -11
- package/presets/openwrite/skills/progress/SKILL.md +1 -1
- package/presets/openwrite/skills/review-artifact-query/SKILL.md +41 -0
- package/presets/openwrite/skills/review-chapter/SKILL.md +2 -2
- package/presets/openwrite/skills/revise-span/SKILL.md +5 -5
- package/presets/openwrite/skills/style-system/SKILL.md +13 -6
- package/presets/openwrite/skills/truth-validation/SKILL.md +9 -12
- package/presets/openwrite/skills/workflow-manager/SKILL.md +13 -13
- package/presets/openwrite/skills/world-query/SKILL.md +16 -11
- package/presets/openwrite/skills/write-next/SKILL.md +1 -1
- package/release/{openwrite-5.8.20-py3-none-any.whl → openwrite-5.8.30-py3-none-any.whl} +0 -0
- package/release/runtime-manifest.json +4 -13
- package/release/source.json +21 -21
- package/release/wheels/licenses/OPENSSL-3.6.3-LICENSE.txt +225 -0
- package/scripts/maintenance.mjs +67 -7
- package/scripts/plugin-doctor.mjs +179 -0
- package/scripts/prepare.mjs +11 -2
- package/scripts/safe-upgrade.mjs +38 -5
- package/dog-tools.mjs +0 -13
- package/presets/openwrite/skills/dog-delivery-query/SKILL.md +0 -42
- package/presets/openwrite/skills/dog-import-query/SKILL.md +0 -29
- package/presets/openwrite/skills/dog-review-query/SKILL.md +0 -42
- package/scripts/dog/delivery-stage.js +0 -32
- package/scripts/dog/import-chapter.js +0 -20
- package/scripts/dog/import-record.js +0 -35
- package/scripts/dog/review-dimension.js +0 -41
- package/scripts/dog/review-record.js +0 -42
- package/vendor/dsh-dog/LICENSE +0 -29
- package/vendor/dsh-dog/README.md +0 -114
- package/vendor/dsh-dog/UPSTREAM.md +0 -30
- package/vendor/dsh-dog/cordis.patch.yml +0 -14
- package/vendor/dsh-dog/lib/client.js +0 -2960
- package/vendor/dsh-dog/lib/client.js.map +0 -1
- package/vendor/dsh-dog/lib/core--byUPJL8.js +0 -2191
- package/vendor/dsh-dog/lib/core-BiEBMLz3.d.ts +0 -495
- package/vendor/dsh-dog/lib/core.d.ts +0 -2
- package/vendor/dsh-dog/lib/core.js +0 -2
- package/vendor/dsh-dog/lib/index.d.ts +0 -143
- package/vendor/dsh-dog/lib/index.js +0 -1230
- package/vendor/dsh-dog/package.json +0 -110
- package/vendor/dsh-dog/schemas/schema-0.2/graph.schema.json +0 -139
- package/vendor/dsh-dog/schemas/schema-0.2/report.schema.json +0 -87
- package/vendor/dsh-dog/schemas/schema-0.2/run.schema.json +0 -259
- package/vendor/dsh-dog/schemas/schema-0.2/runtime-event.schema.json +0 -105
- package/vendor/dsh-dog/schemas/schema-0.2/verification.schema.json +0 -40
|
@@ -1,1230 +0,0 @@
|
|
|
1
|
-
import { a as DogRepository, c as parseGraph, d as isJsonValue, f as ScriptExecutionError, i as runPlan, l as evaluateBoolExpr, m as runOwnedScript, n as resolveScriptPath, o as loadSchemaSet, p as outputPreview, r as WorkspaceManager, s as DogValidationError, t as DogEngine, u as parseBoolExpr } from "./core--byUPJL8.js";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
6
|
-
import z from "@deepseek-ai/schemastery";
|
|
7
|
-
//#region src/script-verdict.ts
|
|
8
|
-
function scriptFailureVerdict(error) {
|
|
9
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
10
|
-
return {
|
|
11
|
-
state: "inconclusive",
|
|
12
|
-
reason: message,
|
|
13
|
-
evidence: error instanceof ScriptExecutionError ? {
|
|
14
|
-
code: error.code,
|
|
15
|
-
error: message,
|
|
16
|
-
...error.diagnostics
|
|
17
|
-
} : {
|
|
18
|
-
code: "SCRIPT_EXECUTION_FAILED",
|
|
19
|
-
error: message
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function parseVerdict(out) {
|
|
24
|
-
let value;
|
|
25
|
-
try {
|
|
26
|
-
value = JSON.parse(out);
|
|
27
|
-
} catch {
|
|
28
|
-
return {
|
|
29
|
-
state: "inconclusive",
|
|
30
|
-
evidence: {
|
|
31
|
-
code: "SCRIPT_OUTPUT_NOT_JSON",
|
|
32
|
-
parseError: "script output was not JSON",
|
|
33
|
-
outputPreview: outputPreview(out)
|
|
34
|
-
},
|
|
35
|
-
reason: "script output was not JSON"
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return {
|
|
39
|
-
state: "inconclusive",
|
|
40
|
-
evidence: {
|
|
41
|
-
code: "SCRIPT_OUTPUT_INVALID",
|
|
42
|
-
parseError: "script output was not an object",
|
|
43
|
-
outputPreview: outputPreview(out)
|
|
44
|
-
},
|
|
45
|
-
reason: "script output was not an object"
|
|
46
|
-
};
|
|
47
|
-
const record = value;
|
|
48
|
-
const verdict = record.verdict;
|
|
49
|
-
if (verdict !== "pass" && verdict !== "fail" && verdict !== "inconclusive") return {
|
|
50
|
-
state: "inconclusive",
|
|
51
|
-
evidence: {
|
|
52
|
-
code: "SCRIPT_OUTPUT_INVALID",
|
|
53
|
-
parseError: "invalid verdict",
|
|
54
|
-
outputPreview: outputPreview(out)
|
|
55
|
-
},
|
|
56
|
-
reason: "script returned an invalid verdict"
|
|
57
|
-
};
|
|
58
|
-
return {
|
|
59
|
-
state: verdict,
|
|
60
|
-
evidence: isJsonValue(record.evidence) ? record.evidence : { outcome: "no evidence supplied by script" }
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
//#endregion
|
|
64
|
-
//#region src/host-shims.ts
|
|
65
|
-
/**
|
|
66
|
-
* Self-contained replacements for the two host packages this plugin used to
|
|
67
|
-
* import at runtime: `@deepseek-ai/dsh-home-paths` (`dshHomePath`) and
|
|
68
|
-
* `@deepseek-ai/dsh-tools` (`defineTool`). Both were declared as
|
|
69
|
-
* peerDependencies, and under the DSH profile install layout (pnpm isolated
|
|
70
|
-
* node_modules) peer instances are not resolvable from this vendored
|
|
71
|
-
* directory, so every entry import failed with ERR_MODULE_NOT_FOUND and the
|
|
72
|
-
* plugin never activated (GitHub issue #74). The implementations below match
|
|
73
|
-
* the host behavior for the schema subset this plugin declares; type-only
|
|
74
|
-
* imports from the host packages are still used and erased at build time.
|
|
75
|
-
*/
|
|
76
|
-
/** Directory name for the default DeepSeek Harness home under the OS home. */
|
|
77
|
-
const DSH_HOME_DIR_NAME = ".dsh";
|
|
78
|
-
/** Environment variable that overrides the default DeepSeek Harness home. */
|
|
79
|
-
const DSH_HOME_ENV = "DSH_HOME";
|
|
80
|
-
/** Expand supported tilde prefixes against the operating-system home. */
|
|
81
|
-
function expandHomePath(path) {
|
|
82
|
-
if (path === "~") return homedir();
|
|
83
|
-
if (path.startsWith("~/") || path.startsWith("~\\")) return join(homedir(), path.slice(2));
|
|
84
|
-
return path;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Resolve the single-root DeepSeek Harness home: `$DSH_HOME` when set to a
|
|
88
|
-
* non-blank value, otherwise `~/.dsh`. Mirrors the host `resolveDshHome`.
|
|
89
|
-
*/
|
|
90
|
-
function resolveDshHome() {
|
|
91
|
-
const fromEnv = process.env[DSH_HOME_ENV];
|
|
92
|
-
return resolve(expandHomePath(fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(homedir(), DSH_HOME_DIR_NAME)));
|
|
93
|
-
}
|
|
94
|
-
/** Join path segments onto the resolved DeepSeek Harness home. */
|
|
95
|
-
function dshHomePath(...segments) {
|
|
96
|
-
return join(resolveDshHome(), ...segments);
|
|
97
|
-
}
|
|
98
|
-
/** Thrown when model-generated arguments violate the compiled schema. */
|
|
99
|
-
var ToolArgsError = class extends Error {
|
|
100
|
-
violations;
|
|
101
|
-
constructor(violations) {
|
|
102
|
-
super(`invalid arguments: ${violations.join("; ")}`);
|
|
103
|
-
this.name = "ToolArgsError";
|
|
104
|
-
this.violations = violations;
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
/** Compile one author-facing value schema node to raw JSON Schema. */
|
|
108
|
-
function compileValueSchema(spec, path) {
|
|
109
|
-
if (spec === null || typeof spec !== "object") throw new Error(`${path}: schema node must be an object`);
|
|
110
|
-
const node = {};
|
|
111
|
-
if (spec.description !== void 0) node.description = spec.description;
|
|
112
|
-
switch (spec.type) {
|
|
113
|
-
case "json": return node;
|
|
114
|
-
case "string":
|
|
115
|
-
case "number":
|
|
116
|
-
case "integer":
|
|
117
|
-
case "boolean":
|
|
118
|
-
case "null":
|
|
119
|
-
node.type = spec.type;
|
|
120
|
-
if ("enum" in spec && spec.enum !== void 0) node.enum = [...spec.enum];
|
|
121
|
-
if ("const" in spec && spec.const !== void 0) node.const = spec.const;
|
|
122
|
-
return node;
|
|
123
|
-
case "array":
|
|
124
|
-
node.type = "array";
|
|
125
|
-
if (spec.items !== void 0) node.items = compileValueSchema(spec.items, `${path}.items`);
|
|
126
|
-
return node;
|
|
127
|
-
case "object":
|
|
128
|
-
node.type = "object";
|
|
129
|
-
if (spec.additionalProperties !== void 0) node.additionalProperties = spec.additionalProperties;
|
|
130
|
-
if (spec.properties !== void 0) {
|
|
131
|
-
const { properties, required } = compilePropertyMap(spec.properties, `${path}.properties`);
|
|
132
|
-
node.properties = properties;
|
|
133
|
-
if (required.length > 0) node.required = required;
|
|
134
|
-
}
|
|
135
|
-
return node;
|
|
136
|
-
default: throw new Error(`${path}.type must be string/number/integer/boolean/null/array/object/json`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/** Compile a property map, hoisting per-property `required` flags onto the parent. */
|
|
140
|
-
function compilePropertyMap(input, path) {
|
|
141
|
-
const properties = {};
|
|
142
|
-
const required = [];
|
|
143
|
-
for (const [key, property] of Object.entries(input)) {
|
|
144
|
-
const { required: isRequired, ...spec } = property;
|
|
145
|
-
properties[key] = compileValueSchema(spec, `${path}.${key}`);
|
|
146
|
-
if (isRequired === true) required.push(key);
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
properties,
|
|
150
|
-
required
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
/** Compile the implicit open parameter object into raw JSON Schema. */
|
|
154
|
-
function compileParameters(spec) {
|
|
155
|
-
const { properties, required } = compilePropertyMap(spec, "parameters");
|
|
156
|
-
const schema = {
|
|
157
|
-
type: "object",
|
|
158
|
-
properties
|
|
159
|
-
};
|
|
160
|
-
if (required.length > 0) schema.required = required;
|
|
161
|
-
return schema;
|
|
162
|
-
}
|
|
163
|
-
/** Validate one JSON value against the compiled subset, collecting violations. */
|
|
164
|
-
function validateValue(schema, value, path, violations) {
|
|
165
|
-
const at = path === "" ? "arguments" : path;
|
|
166
|
-
if (schema.const !== void 0 && value !== schema.const) {
|
|
167
|
-
violations.push(`${at}: must equal ${JSON.stringify(schema.const)}`);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (Array.isArray(schema.enum) && !schema.enum.some((entry) => entry === value)) {
|
|
171
|
-
violations.push(`${at}: must be one of ${JSON.stringify(schema.enum)}`);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
switch (schema.type) {
|
|
175
|
-
case void 0: return;
|
|
176
|
-
case "null":
|
|
177
|
-
if (value !== null) violations.push(`${at}: expected null`);
|
|
178
|
-
return;
|
|
179
|
-
case "boolean":
|
|
180
|
-
if (typeof value !== "boolean") violations.push(`${at}: expected boolean, got ${describe(value)}`);
|
|
181
|
-
return;
|
|
182
|
-
case "string":
|
|
183
|
-
if (typeof value !== "string") violations.push(`${at}: expected string, got ${describe(value)}`);
|
|
184
|
-
return;
|
|
185
|
-
case "number":
|
|
186
|
-
if (typeof value !== "number" || !Number.isFinite(value)) violations.push(`${at}: expected number, got ${describe(value)}`);
|
|
187
|
-
return;
|
|
188
|
-
case "integer":
|
|
189
|
-
if (typeof value !== "number" || !Number.isInteger(value)) violations.push(`${at}: expected integer, got ${describe(value)}`);
|
|
190
|
-
return;
|
|
191
|
-
case "array": {
|
|
192
|
-
if (!Array.isArray(value)) {
|
|
193
|
-
violations.push(`${at}: expected array, got ${describe(value)}`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const items = schema.items;
|
|
197
|
-
if (items !== void 0) value.forEach((item, index) => validateValue(items, item, `${at}[${index}]`, violations));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
case "object": {
|
|
201
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
202
|
-
violations.push(`${at}: expected object, got ${describe(value)}`);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const record = value;
|
|
206
|
-
for (const key of schema.required ?? []) if (!Object.hasOwn(record, key)) violations.push(`${at}: missing required property "${key}"`);
|
|
207
|
-
const properties = schema.properties;
|
|
208
|
-
if (properties !== void 0) {
|
|
209
|
-
for (const [key, propertySchema] of Object.entries(properties)) if (Object.hasOwn(record, key)) validateValue(propertySchema, record[key], `${at}.${key}`, violations);
|
|
210
|
-
}
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
function describe(value) {
|
|
216
|
-
if (value === null) return "null";
|
|
217
|
-
if (Array.isArray(value)) return "array";
|
|
218
|
-
return typeof value;
|
|
219
|
-
}
|
|
220
|
-
/** Validate model-generated arguments against the compiled parameter schema. */
|
|
221
|
-
function validateArgs(parameters, args) {
|
|
222
|
-
const violations = [];
|
|
223
|
-
validateValue(parameters, args, "", violations);
|
|
224
|
-
return violations;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Define a first-party tool with strict execution validation, matching the
|
|
228
|
-
* host `defineTool` contract: arguments are validated before `execute`, and
|
|
229
|
-
* replay presenters validate softly and fall back to generic rendering.
|
|
230
|
-
*/
|
|
231
|
-
function defineTool(options) {
|
|
232
|
-
const userExecute = options.execute;
|
|
233
|
-
const userRender = options.output.render;
|
|
234
|
-
const userPresentationMeta = options.output.presentationMeta;
|
|
235
|
-
const userIsConcurrencySafe = options.isConcurrencySafe;
|
|
236
|
-
if (options.timeoutMs !== void 0 && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) throw new Error(`defineTool(${options.name}): timeoutMs must be a positive finite number`);
|
|
237
|
-
const parameters = compileParameters(options.parameters);
|
|
238
|
-
const outputSchema = compileValueSchema(options.output.schema, "schema");
|
|
239
|
-
const validate = (args) => validateArgs(parameters, args);
|
|
240
|
-
const tool = {
|
|
241
|
-
name: options.name,
|
|
242
|
-
description: options.description,
|
|
243
|
-
parameters,
|
|
244
|
-
output: {
|
|
245
|
-
schema: outputSchema,
|
|
246
|
-
render: (args, value) => userRender(args, value),
|
|
247
|
-
...userPresentationMeta !== void 0 ? { presentationMeta: (args, value) => userPresentationMeta(args, value) } : {}
|
|
248
|
-
},
|
|
249
|
-
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
250
|
-
async execute(args, exec) {
|
|
251
|
-
const violations = validate(args);
|
|
252
|
-
if (violations.length > 0) throw new ToolArgsError(violations);
|
|
253
|
-
return userExecute(args, exec);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
if (userIsConcurrencySafe) tool.isConcurrencySafe = (args) => {
|
|
257
|
-
if (validate(args).length > 0) return false;
|
|
258
|
-
return userIsConcurrencySafe(args);
|
|
259
|
-
};
|
|
260
|
-
return tool;
|
|
261
|
-
}
|
|
262
|
-
//#endregion
|
|
263
|
-
//#region src/debug-contract.ts
|
|
264
|
-
/**
|
|
265
|
-
* Dedicated debugger channel. It is served by this plugin directly on the Web
|
|
266
|
-
* carrier (see serveDogDebugRpc): Connection's generic channel registry mounts
|
|
267
|
-
* its HTTP route through the Connection plugin's own context, which only sees
|
|
268
|
-
* `webServer` when a bundle patch injects it into the shared connection row —
|
|
269
|
-
* and per-key patch semantics make any such patch clobber other bundles
|
|
270
|
-
* (OpenWrite issue #81), while the shared `/api` channel already belongs to
|
|
271
|
-
* the host's Typert gateway. The wire envelope mirrors Connection's, so the
|
|
272
|
-
* browser side keeps using its stock `connection.rpc.call`.
|
|
273
|
-
*/
|
|
274
|
-
const DOG_DEBUG_RPC_CHANNEL = "/dog-rpc";
|
|
275
|
-
const DOG_DEBUG_SNAPSHOT_ENDPOINT = "snapshot";
|
|
276
|
-
const DOG_RUNTIME_TRACE_ENDPOINT = "goal-runtime";
|
|
277
|
-
//#endregion
|
|
278
|
-
//#region src/debug.ts
|
|
279
|
-
const MAX_RUNTIME_EVENTS_PER_GOAL = 64;
|
|
280
|
-
/** Build a digest-safe view: an old run is never shown against a newer graph revision. */
|
|
281
|
-
async function buildDogDebugSnapshot(repository, now = () => /* @__PURE__ */ new Date()) {
|
|
282
|
-
const [allGraphs, runs] = await Promise.all([repository.listGraphs(), repository.listRuns()]);
|
|
283
|
-
const graphs = allGraphs.filter((graph) => graph.input.schemaVersion === "0.9");
|
|
284
|
-
const graphIds = [...new Set(graphs.map((graph) => graph.input.id))];
|
|
285
|
-
const settled = await Promise.allSettled(graphIds.map((id) => repository.loadGraph(id)));
|
|
286
|
-
const currentDigests = new Set(settled.filter((result) => result.status === "fulfilled").map((result) => result.value.graphDigest));
|
|
287
|
-
const revisions = graphs.map((graph) => ({
|
|
288
|
-
graph,
|
|
289
|
-
current: currentDigests.has(graph.graphDigest),
|
|
290
|
-
runs: runs.filter((run) => run.graphDigest === graph.graphDigest)
|
|
291
|
-
})).sort(compareRevisions);
|
|
292
|
-
return {
|
|
293
|
-
schemaVersion: "0.1",
|
|
294
|
-
generatedAt: now().toISOString(),
|
|
295
|
-
graphs: revisions
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
/** Build one bounded node trace without loading any DSH transcript or artifact bytes. */
|
|
299
|
-
async function buildGoalRuntimeTrace(repository, runId, goalId) {
|
|
300
|
-
const run = await repository.loadRun(runId);
|
|
301
|
-
const result = run.goals[goalId];
|
|
302
|
-
if (result === void 0) throw new Error(`run ${runId} has no goal ${goalId}`);
|
|
303
|
-
const matching = [...await repository.loadGoalRuntimeEvents(runId, goalId)];
|
|
304
|
-
const truncated = matching.length > MAX_RUNTIME_EVENTS_PER_GOAL;
|
|
305
|
-
return {
|
|
306
|
-
schemaVersion: "0.1",
|
|
307
|
-
runId: run.runId,
|
|
308
|
-
graphId: run.graphId,
|
|
309
|
-
graphDigest: run.graphDigest,
|
|
310
|
-
runState: run.state,
|
|
311
|
-
...run.rootState === void 0 ? {} : { rootState: run.rootState },
|
|
312
|
-
goalId,
|
|
313
|
-
result,
|
|
314
|
-
...run.invocation === void 0 ? {} : { invocation: run.invocation },
|
|
315
|
-
...run.runtimeWarning === void 0 ? {} : { runtimeWarning: boundedMessage(run.runtimeWarning) },
|
|
316
|
-
events: truncated ? matching.slice(-64) : matching,
|
|
317
|
-
truncated
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
/** Register this handler on the trusted-host Connection channel, never as an unauthenticated raw HTTP route. */
|
|
321
|
-
function createDogDebugRpcHandler(repository) {
|
|
322
|
-
return async (endpoint, payload, signal) => {
|
|
323
|
-
try {
|
|
324
|
-
signal.throwIfAborted();
|
|
325
|
-
if (endpoint === "snapshot") {
|
|
326
|
-
requireEmptyPayload(payload);
|
|
327
|
-
return {
|
|
328
|
-
ok: true,
|
|
329
|
-
value: await buildDogDebugSnapshot(repository)
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
if (endpoint === "goal-runtime") {
|
|
333
|
-
const request = parseGoalRuntimeRequest(payload);
|
|
334
|
-
return {
|
|
335
|
-
ok: true,
|
|
336
|
-
value: await buildGoalRuntimeTrace(repository, request.runId, request.goalId)
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
return internalError(`unknown DoG debugger endpoint ${endpoint}`);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
if (signal.aborted) return {
|
|
342
|
-
ok: false,
|
|
343
|
-
error: {
|
|
344
|
-
code: "cancelled",
|
|
345
|
-
message: "DoG debugger request cancelled",
|
|
346
|
-
details: {}
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
return internalError(messageOf(error));
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
const DOG_DEBUG_BODY_LIMIT = 8388608;
|
|
354
|
-
const ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/;
|
|
355
|
-
/**
|
|
356
|
-
* Serve the debugger RPC channel directly on the Web carrier. Connection's
|
|
357
|
-
* generic channel registry (`rpc.handle`) mounts its HTTP route through the
|
|
358
|
-
* Connection plugin's own context, which only sees `webServer` when a bundle
|
|
359
|
-
* patch injects it into the shared connection row — and per-key patch
|
|
360
|
-
* semantics make any such patch clobber other bundles (OpenWrite issue #81);
|
|
361
|
-
* the shared `/api` channel is already claimed by the host's Typert gateway.
|
|
362
|
-
* The wire envelope mirrors Connection's (`client-request`/`server-response`)
|
|
363
|
-
* so the browser side keeps using its stock `connection.rpc.call`; admission
|
|
364
|
-
* at the route reuses Connection's trusted-host fence and browser
|
|
365
|
-
* authentication, so the channel stays exactly as protected as a native one.
|
|
366
|
-
*/
|
|
367
|
-
async function serveDogDebugRpc(req, res, handler) {
|
|
368
|
-
const respond = (status, body) => {
|
|
369
|
-
res.writeHead(status, { "content-type": "application/json" });
|
|
370
|
-
res.end(typeof body === "string" ? body : JSON.stringify(body));
|
|
371
|
-
};
|
|
372
|
-
const endpoint = dogDebugEndpointOf(req.url ?? "");
|
|
373
|
-
if (endpoint === void 0 || req.method !== "POST") {
|
|
374
|
-
respond(404, "not found");
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (((req.headers["content-type"] ?? "").split(";", 1)[0] ?? "").trim().toLowerCase() !== "application/json") {
|
|
378
|
-
respond(415, "content type must be application/json");
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
let raw = "";
|
|
382
|
-
let size = 0;
|
|
383
|
-
for await (const chunk of req) {
|
|
384
|
-
size += chunk.length;
|
|
385
|
-
if (size > DOG_DEBUG_BODY_LIMIT) {
|
|
386
|
-
respond(413, "request too large");
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
raw += chunk;
|
|
390
|
-
}
|
|
391
|
-
let body;
|
|
392
|
-
try {
|
|
393
|
-
body = JSON.parse(raw);
|
|
394
|
-
} catch {
|
|
395
|
-
respond(400, "body is not JSON");
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
const rpcId = typeof body?.rpcId === "string" ? body.rpcId : "invalid-request";
|
|
399
|
-
if (body?.type !== "client-request" || body.method !== endpoint) {
|
|
400
|
-
respond(200, {
|
|
401
|
-
type: "server-response",
|
|
402
|
-
rpcId,
|
|
403
|
-
result: {
|
|
404
|
-
ok: false,
|
|
405
|
-
error: {
|
|
406
|
-
code: "gateway/bad-request",
|
|
407
|
-
message: "invalid client-request message",
|
|
408
|
-
details: {}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const controller = new AbortController();
|
|
415
|
-
req.on("close", () => controller.abort());
|
|
416
|
-
try {
|
|
417
|
-
respond(200, {
|
|
418
|
-
type: "server-response",
|
|
419
|
-
rpcId,
|
|
420
|
-
result: await handler(endpoint, body.payload, controller.signal)
|
|
421
|
-
});
|
|
422
|
-
} catch (error) {
|
|
423
|
-
respond(500, `handler failure: ${String(error)}`);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
function dogDebugEndpointOf(url) {
|
|
427
|
-
const pathname = url.split("?", 1)[0] ?? "";
|
|
428
|
-
if (!pathname.startsWith(`/dog-rpc/`)) return void 0;
|
|
429
|
-
const endpoint = pathname.slice(9);
|
|
430
|
-
if (endpoint.split("/").some((segment) => segment === "" || segment === "." || segment === ".." || !ENDPOINT_SEGMENT.test(segment))) return void 0;
|
|
431
|
-
return endpoint;
|
|
432
|
-
}
|
|
433
|
-
function compareRevisions(left, right) {
|
|
434
|
-
if (left.current !== right.current) return left.current ? -1 : 1;
|
|
435
|
-
const leftUpdated = left.runs[0]?.updatedAt ?? "";
|
|
436
|
-
const byActivity = (right.runs[0]?.updatedAt ?? "").localeCompare(leftUpdated);
|
|
437
|
-
if (byActivity !== 0) return byActivity;
|
|
438
|
-
const byId = left.graph.input.id.localeCompare(right.graph.input.id);
|
|
439
|
-
return byId === 0 ? left.graph.graphDigest.localeCompare(right.graph.graphDigest) : byId;
|
|
440
|
-
}
|
|
441
|
-
function requireEmptyPayload(value) {
|
|
442
|
-
if (value === null || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== 0) throw new Error("DoG snapshot request payload must be an empty object");
|
|
443
|
-
}
|
|
444
|
-
function parseGoalRuntimeRequest(value) {
|
|
445
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("DoG runtime request payload must be an object");
|
|
446
|
-
const record = Object.fromEntries(Object.entries(value));
|
|
447
|
-
if (Object.keys(record).length !== 2) throw new Error("DoG runtime request requires exactly runId and goalId");
|
|
448
|
-
return {
|
|
449
|
-
runId: requireBoundedId(record.runId, "runId"),
|
|
450
|
-
goalId: requireBoundedId(record.goalId, "goalId")
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
function requireBoundedId(value, label) {
|
|
454
|
-
if (typeof value !== "string" || value.length === 0 || value.length > 512) throw new Error(`${label} must contain 1-512 characters`);
|
|
455
|
-
return value;
|
|
456
|
-
}
|
|
457
|
-
function internalError(message) {
|
|
458
|
-
return {
|
|
459
|
-
ok: false,
|
|
460
|
-
error: {
|
|
461
|
-
code: "internal",
|
|
462
|
-
message: boundedMessage(message),
|
|
463
|
-
details: {}
|
|
464
|
-
}
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
function boundedMessage(value) {
|
|
468
|
-
const normalized = value.length === 0 ? "DoG debugger request failed" : value;
|
|
469
|
-
return normalized.length <= 512 ? normalized : `${normalized.slice(0, 509)}...`;
|
|
470
|
-
}
|
|
471
|
-
function messageOf(error) {
|
|
472
|
-
return error instanceof Error ? error.message : String(error);
|
|
473
|
-
}
|
|
474
|
-
//#endregion
|
|
475
|
-
//#region src/tools.ts
|
|
476
|
-
/** Model-facing DoG tools over the verification-first engine. */
|
|
477
|
-
const DOG_VALIDATE_TOOL = "dog_validate";
|
|
478
|
-
const DOG_CREATE_TOOL = "dog_create";
|
|
479
|
-
const DOG_RUN_TOOL = "dog_run";
|
|
480
|
-
const DOG_BIND_AGENT_TOOL = "dog_bind_agent";
|
|
481
|
-
const DOG_DELEGATE_AGENT_TOOL = "dog_delegate_agent";
|
|
482
|
-
const DOG_STATUS_TOOL = "dog_status";
|
|
483
|
-
const DOG_CANCEL_TOOL = "dog_cancel";
|
|
484
|
-
const DOG_WAIT_TOOL = "dog_wait";
|
|
485
|
-
const JSON_OUTPUT = {
|
|
486
|
-
schema: { type: "json" },
|
|
487
|
-
render: (_args, value) => [{
|
|
488
|
-
type: "text",
|
|
489
|
-
text: JSON.stringify(value, null, 2)
|
|
490
|
-
}]
|
|
491
|
-
};
|
|
492
|
-
/** Build the complete DoG v0.1 tool surface around one engine instance. */
|
|
493
|
-
function createDogTools(engine, hostJobs = () => void 0) {
|
|
494
|
-
return [
|
|
495
|
-
defineTool({
|
|
496
|
-
name: DOG_VALIDATE_TOOL,
|
|
497
|
-
description: "Statically validate a DoG v0.2 graph. This does not write files, capture sandbox files, or run goals.",
|
|
498
|
-
parameters: { graph: {
|
|
499
|
-
type: "json",
|
|
500
|
-
required: true,
|
|
501
|
-
description: "Complete JSON DoG graph using schemaVersion 0.2."
|
|
502
|
-
} },
|
|
503
|
-
output: JSON_OUTPUT,
|
|
504
|
-
isConcurrencySafe: () => true,
|
|
505
|
-
execute: async (args, exec) => {
|
|
506
|
-
exec.signal.throwIfAborted();
|
|
507
|
-
return jsonResult(engine().validate(args.graph));
|
|
508
|
-
}
|
|
509
|
-
}),
|
|
510
|
-
defineTool({
|
|
511
|
-
name: DOG_CREATE_TOOL,
|
|
512
|
-
description: "Compile and persist a valid DoG v0.2 graph, capturing verifier input files from the configured sandbox as immutable bytes.",
|
|
513
|
-
parameters: { graph: {
|
|
514
|
-
type: "json",
|
|
515
|
-
required: true,
|
|
516
|
-
description: "Complete JSON DoG graph using schemaVersion 0.2."
|
|
517
|
-
} },
|
|
518
|
-
output: JSON_OUTPUT,
|
|
519
|
-
async execute(args, exec) {
|
|
520
|
-
exec.signal.throwIfAborted();
|
|
521
|
-
const cwd = exec.agent?.session.header.cwd;
|
|
522
|
-
const compiled = await engine().create(args.graph, typeof cwd === "string" && cwd.length > 0 ? { captureBaseDir: cwd } : {});
|
|
523
|
-
exec.signal.throwIfAborted();
|
|
524
|
-
return jsonResult(compiledGraphSummary(compiled));
|
|
525
|
-
}
|
|
526
|
-
}),
|
|
527
|
-
defineTool({
|
|
528
|
-
name: DOG_RUN_TOOL,
|
|
529
|
-
description: "Run trusted atomic verification for the latest persisted revision of a graph and recompute its root result.",
|
|
530
|
-
parameters: { graphId: {
|
|
531
|
-
type: "string",
|
|
532
|
-
required: true,
|
|
533
|
-
description: "Graph ID previously persisted by dog_create."
|
|
534
|
-
} },
|
|
535
|
-
output: JSON_OUTPUT,
|
|
536
|
-
async execute(args, exec) {
|
|
537
|
-
exec.signal.throwIfAborted();
|
|
538
|
-
const run = await engine().startRun(args.graphId, {
|
|
539
|
-
invocation: {
|
|
540
|
-
callId: String(exec.callId),
|
|
541
|
-
...exec.agent === void 0 ? {} : { agentSessionId: String(exec.agent.id) },
|
|
542
|
-
...exec.agent?.session.header.parentSession === void 0 ? {} : { parentSessionId: String(exec.agent.session.header.parentSession) }
|
|
543
|
-
},
|
|
544
|
-
...exec.agent === void 0 ? {} : { agent: exec.agent }
|
|
545
|
-
});
|
|
546
|
-
exec.signal.throwIfAborted();
|
|
547
|
-
return jsonResult(Object.assign({}, runSummary(run), { note: "verification runs in the background; agentic leaves spawn verifier Agents that live in THIS process. If this is a one-shot launch (e.g. dsh --profile headless), the process exits after answering and verifier workers are interrupted: background agentic verification requires a resident host process. Poll with dog_status." }));
|
|
548
|
-
}
|
|
549
|
-
}),
|
|
550
|
-
defineTool({
|
|
551
|
-
name: DOG_BIND_AGENT_TOOL,
|
|
552
|
-
description: "Bind the current trusted DSH Agent session to one goal in an existing DoG run. Use dog_delegate_agent instead when creating a new worker that must remain directly interactive after its first turn. Session identity is captured from the tool execution context and cannot be supplied by the model.",
|
|
553
|
-
parameters: {
|
|
554
|
-
runId: {
|
|
555
|
-
type: "string",
|
|
556
|
-
required: true,
|
|
557
|
-
description: "Run ID returned by dog_run."
|
|
558
|
-
},
|
|
559
|
-
goalId: {
|
|
560
|
-
type: "string",
|
|
561
|
-
required: true,
|
|
562
|
-
description: "Goal ID this Agent is working on or verifying."
|
|
563
|
-
},
|
|
564
|
-
role: {
|
|
565
|
-
type: "string",
|
|
566
|
-
required: true,
|
|
567
|
-
enum: [
|
|
568
|
-
"orchestrator",
|
|
569
|
-
"verifier",
|
|
570
|
-
"reviewer"
|
|
571
|
-
],
|
|
572
|
-
description: "The Agent role for this goal."
|
|
573
|
-
}
|
|
574
|
-
},
|
|
575
|
-
output: JSON_OUTPUT,
|
|
576
|
-
isConcurrencySafe: () => true,
|
|
577
|
-
async execute(args, exec) {
|
|
578
|
-
exec.signal.throwIfAborted();
|
|
579
|
-
if (exec.agent === void 0) throw new Error("dog_bind_agent requires a trusted DSH Agent execution context");
|
|
580
|
-
const binding = await engine().bindAgent({
|
|
581
|
-
runId: args.runId,
|
|
582
|
-
goalId: args.goalId,
|
|
583
|
-
role: args.role,
|
|
584
|
-
sessionId: String(exec.agent.id),
|
|
585
|
-
...exec.agent.session.header.parentSession === void 0 ? {} : { parentSessionId: String(exec.agent.session.header.parentSession) }
|
|
586
|
-
});
|
|
587
|
-
exec.signal.throwIfAborted();
|
|
588
|
-
return jsonResult({
|
|
589
|
-
runId: binding.run.runId,
|
|
590
|
-
goalId: args.goalId,
|
|
591
|
-
agent: binding.agent
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
}),
|
|
595
|
-
defineTool({
|
|
596
|
-
name: DOG_STATUS_TOOL,
|
|
597
|
-
description: "Read the bounded persisted status and verifier evidence for one DoG run.",
|
|
598
|
-
parameters: { runId: {
|
|
599
|
-
type: "string",
|
|
600
|
-
required: true,
|
|
601
|
-
description: "Run ID returned by dog_run."
|
|
602
|
-
} },
|
|
603
|
-
output: JSON_OUTPUT,
|
|
604
|
-
isConcurrencySafe: () => true,
|
|
605
|
-
async execute(args, exec) {
|
|
606
|
-
exec.signal.throwIfAborted();
|
|
607
|
-
const run = await engine().status(args.runId);
|
|
608
|
-
exec.signal.throwIfAborted();
|
|
609
|
-
return jsonResult(runSummary(run));
|
|
610
|
-
}
|
|
611
|
-
}),
|
|
612
|
-
defineTool({
|
|
613
|
-
name: DOG_WAIT_TOOL,
|
|
614
|
-
description: "Wait for one DoG run to settle. Preferred path: when the host job service is available this registers a background \"dog\" job (returns instantly with jobId) and the harness notifies you automatically when the run completes — inspect with job_output, cancel with job_kill. Fallback (no job service, e.g. a bare one-shot process): returns the current state instantly and you must call dog_wait repeatedly until the state is terminal; each poll keeps the calling session and process alive, which protects the verifier workers the run spawned. Always wait until the returned state is terminal before summarizing — finishing early releases the workers. Same report shape as dog_status.",
|
|
615
|
-
parameters: {
|
|
616
|
-
runId: {
|
|
617
|
-
type: "string",
|
|
618
|
-
required: true,
|
|
619
|
-
description: "Run ID returned by dog_run."
|
|
620
|
-
},
|
|
621
|
-
timeout: {
|
|
622
|
-
type: "number",
|
|
623
|
-
description: "Advisory total wait budget in seconds; not enforced (each call returns instantly)."
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
output: JSON_OUTPUT,
|
|
627
|
-
isConcurrencySafe: () => true,
|
|
628
|
-
async execute(args, exec) {
|
|
629
|
-
exec.signal.throwIfAborted();
|
|
630
|
-
const run = await engine().status(args.runId);
|
|
631
|
-
exec.signal.throwIfAborted();
|
|
632
|
-
const jobs = hostJobs();
|
|
633
|
-
if (jobs !== void 0 && run.state === "running") {
|
|
634
|
-
const controller = new AbortController();
|
|
635
|
-
const done = engine().waitRun(args.runId, {
|
|
636
|
-
signal: controller.signal,
|
|
637
|
-
timeoutMs: 72e5
|
|
638
|
-
}).then((settled) => ({
|
|
639
|
-
status: "completed",
|
|
640
|
-
output: JSON.stringify(runSummary(settled))
|
|
641
|
-
})).catch((error) => ({
|
|
642
|
-
status: controller.signal.aborted ? "killed" : "failed",
|
|
643
|
-
detail: String(error instanceof Error ? error.message : error).slice(0, 200)
|
|
644
|
-
}));
|
|
645
|
-
const jobId = jobs.start({
|
|
646
|
-
kind: "dog",
|
|
647
|
-
label: `DoG wait ${String(args.runId).slice(0, 8)}`,
|
|
648
|
-
...exec.agent === void 0 ? {} : { owner: exec.agent },
|
|
649
|
-
run: () => ({
|
|
650
|
-
cancel: (reason) => {
|
|
651
|
-
controller.abort(new Error(reason ?? "cancelled"));
|
|
652
|
-
},
|
|
653
|
-
done
|
|
654
|
-
})
|
|
655
|
-
});
|
|
656
|
-
return jsonResult({
|
|
657
|
-
runId: run.runId,
|
|
658
|
-
graphId: run.graphId,
|
|
659
|
-
state: run.state,
|
|
660
|
-
jobId,
|
|
661
|
-
note: "background wait job registered; the harness will notify you when the run completes (that is the wait). NEVER call job_output with wait:true — a long wait gets interrupted by background notices and that interruption is expected; if an unofficial job_output result comes back interrupted, just keep your current turn alive or reply with another dog_wait."
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
return jsonResult(Object.assign({}, runSummary(run), { ...run.state === "running" ? { note: "instant poll: the run is still running (this call returned immediately and cannot be interrupted). Call dog_wait again, repeatedly, until the state is terminal." } : {} }));
|
|
665
|
-
}
|
|
666
|
-
}),
|
|
667
|
-
defineTool({
|
|
668
|
-
name: DOG_CANCEL_TOOL,
|
|
669
|
-
description: "Cancel one running DoG run and its verifier workers; partial records remain inspectable.",
|
|
670
|
-
parameters: {
|
|
671
|
-
runId: {
|
|
672
|
-
type: "string",
|
|
673
|
-
required: true,
|
|
674
|
-
description: "Run ID returned by dog_run."
|
|
675
|
-
},
|
|
676
|
-
reason: {
|
|
677
|
-
type: "string",
|
|
678
|
-
description: "Bounded human-readable cancellation reason."
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
output: JSON_OUTPUT,
|
|
682
|
-
isConcurrencySafe: () => true,
|
|
683
|
-
async execute(args, exec) {
|
|
684
|
-
exec.signal.throwIfAborted();
|
|
685
|
-
const runId = args.runId;
|
|
686
|
-
const reason = typeof args.reason === "string" && args.reason.length > 0 ? args.reason : "cancelled by operator";
|
|
687
|
-
const cancelled = await engine().cancelRun(runId, reason);
|
|
688
|
-
exec.signal.throwIfAborted();
|
|
689
|
-
return jsonResult(runSummary(cancelled));
|
|
690
|
-
}
|
|
691
|
-
})
|
|
692
|
-
];
|
|
693
|
-
}
|
|
694
|
-
/** Build the DoG-aware continuable Agent launcher once the host subagent service is available. */
|
|
695
|
-
function createDogDelegateAgentTool(engine, launcher) {
|
|
696
|
-
return defineTool({
|
|
697
|
-
name: DOG_DELEGATE_AGENT_TOOL,
|
|
698
|
-
description: "Start a durable continuable DSH Agent for one DoG goal and bind its host-issued session identity to the run. The child remains directly interactive in the session UI after its first turn. Use this instead of a foreground or one-shot subagent for user-addressable DoG workers.",
|
|
699
|
-
parameters: {
|
|
700
|
-
runId: {
|
|
701
|
-
type: "string",
|
|
702
|
-
required: true,
|
|
703
|
-
description: "Run ID returned by dog_run."
|
|
704
|
-
},
|
|
705
|
-
goalId: {
|
|
706
|
-
type: "string",
|
|
707
|
-
required: true,
|
|
708
|
-
description: "Goal ID assigned to the new Agent."
|
|
709
|
-
},
|
|
710
|
-
role: {
|
|
711
|
-
type: "string",
|
|
712
|
-
required: true,
|
|
713
|
-
enum: [
|
|
714
|
-
"orchestrator",
|
|
715
|
-
"verifier",
|
|
716
|
-
"reviewer"
|
|
717
|
-
],
|
|
718
|
-
description: "The Agent role for this goal."
|
|
719
|
-
},
|
|
720
|
-
label: {
|
|
721
|
-
type: "string",
|
|
722
|
-
required: true,
|
|
723
|
-
description: "Short human-readable Agent label shown in the session tree."
|
|
724
|
-
},
|
|
725
|
-
prompt: {
|
|
726
|
-
type: "string",
|
|
727
|
-
required: true,
|
|
728
|
-
description: "Complete first-turn assignment for the Agent."
|
|
729
|
-
}
|
|
730
|
-
},
|
|
731
|
-
output: JSON_OUTPUT,
|
|
732
|
-
async execute(args, exec) {
|
|
733
|
-
exec.signal.throwIfAborted();
|
|
734
|
-
if (exec.agent === void 0) throw new Error("dog_delegate_agent requires a trusted DSH Agent execution context");
|
|
735
|
-
const parentSessionId = String(exec.agent.id);
|
|
736
|
-
await engine().assertAgentCanDelegate({
|
|
737
|
-
runId: args.runId,
|
|
738
|
-
goalId: args.goalId,
|
|
739
|
-
parentSessionId
|
|
740
|
-
});
|
|
741
|
-
exec.signal.throwIfAborted();
|
|
742
|
-
const child = await launcher.launch({
|
|
743
|
-
label: args.label,
|
|
744
|
-
prompt: args.prompt,
|
|
745
|
-
parent: exec.agent,
|
|
746
|
-
signal: exec.signal
|
|
747
|
-
});
|
|
748
|
-
if (child.sessionId.length === 0) throw new Error("DoG Agent launcher returned an empty child session ID");
|
|
749
|
-
try {
|
|
750
|
-
const binding = await engine().bindAgent({
|
|
751
|
-
runId: args.runId,
|
|
752
|
-
goalId: args.goalId,
|
|
753
|
-
role: args.role,
|
|
754
|
-
sessionId: child.sessionId,
|
|
755
|
-
parentSessionId
|
|
756
|
-
});
|
|
757
|
-
return jsonResult({
|
|
758
|
-
runId: binding.run.runId,
|
|
759
|
-
goalId: args.goalId,
|
|
760
|
-
lifecycle: "continuable",
|
|
761
|
-
agent: binding.agent
|
|
762
|
-
});
|
|
763
|
-
} catch (error) {
|
|
764
|
-
try {
|
|
765
|
-
await launcher.interrupt?.(child.sessionId, exec.agent);
|
|
766
|
-
} catch {}
|
|
767
|
-
throw error;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
function compiledGraphSummary(compiled) {
|
|
773
|
-
return {
|
|
774
|
-
graphId: compiled.input.id,
|
|
775
|
-
graphDigest: compiled.graphDigest,
|
|
776
|
-
acceptancePlans: Object.values(compiled.acceptancePlans).map((plan) => ({
|
|
777
|
-
goalId: plan.goalId,
|
|
778
|
-
mode: plan.verifier.mode,
|
|
779
|
-
...plan.verifier.mode === "programmatic" ? { script: plan.verifier.script } : { instruction: plan.verifier.instruction },
|
|
780
|
-
target: plan.target,
|
|
781
|
-
...plan.input === void 0 ? {} : {
|
|
782
|
-
inputPath: plan.input.path,
|
|
783
|
-
digest: plan.input.digest,
|
|
784
|
-
exists: plan.input.exists,
|
|
785
|
-
byteLength: plan.input.byteLength
|
|
786
|
-
}
|
|
787
|
-
}))
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
function ciReport(run) {
|
|
791
|
-
const goals = Object.entries(run.goals).map(([goalId, result]) => goalReport(goalId, result));
|
|
792
|
-
const revalidated = Object.entries(run.gmDigests).filter(([goalId]) => run.goals[goalId]?.state !== "inherited").map(([goalId]) => goalId);
|
|
793
|
-
const inherited = Object.entries(run.goals).filter(([, result]) => result.state === "inherited").map(([goalId, result]) => ({
|
|
794
|
-
goalId,
|
|
795
|
-
fromRunId: result.inheritedFrom ?? run.runId,
|
|
796
|
-
state: "inherited"
|
|
797
|
-
}));
|
|
798
|
-
const warning = revalidateWarning(run, revalidated);
|
|
799
|
-
return {
|
|
800
|
-
runId: run.runId,
|
|
801
|
-
graphId: run.graphId,
|
|
802
|
-
...run.graphDigest === void 0 ? {} : { graphDigest: run.graphDigest },
|
|
803
|
-
rootState: run.rootState ?? (run.state === "running" ? "running" : "cancelled"),
|
|
804
|
-
goals: goals.sort((left, right) => left.goalId.localeCompare(right.goalId)),
|
|
805
|
-
revalidated,
|
|
806
|
-
inherited,
|
|
807
|
-
...warning === void 0 ? {} : { warning },
|
|
808
|
-
generatedAt: run.updatedAt
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
function goalReport(goalId, result) {
|
|
812
|
-
const verification = result.verification;
|
|
813
|
-
return {
|
|
814
|
-
goalId,
|
|
815
|
-
state: result.state,
|
|
816
|
-
...verification === void 0 ? {} : { verifier: { mode: verification.judgment.mode } },
|
|
817
|
-
...verification === void 0 || verification.evidence === void 0 ? {} : { evidence: [verification.evidence] },
|
|
818
|
-
...result.reason === void 0 ? {} : { defect: result.reason }
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
function revalidateWarning(run, revalidated) {
|
|
822
|
-
const warning = run.runtimeWarning;
|
|
823
|
-
return warning === void 0 ? void 0 : warning;
|
|
824
|
-
}
|
|
825
|
-
function runSummary(run) {
|
|
826
|
-
return ciReport(run);
|
|
827
|
-
}
|
|
828
|
-
function jsonResult(value) {
|
|
829
|
-
if (!isJsonValue(value)) throw new Error("DoG produced a non-JSON tool result");
|
|
830
|
-
return value;
|
|
831
|
-
}
|
|
832
|
-
//#endregion
|
|
833
|
-
//#region src/verifier-file.ts
|
|
834
|
-
/** File-backed settlement channel for continuable verifier subagents. */
|
|
835
|
-
/**
|
|
836
|
-
* Same as {@link waitForSettlement}, but short-circuits once the verifier child
|
|
837
|
-
* has finished its turn: if the settlement file is still absent shortly after
|
|
838
|
-
* the child's turn ended, the child is not coming back — settle inconclusive
|
|
839
|
-
* immediately instead of blocking the run for the full timeout.
|
|
840
|
-
*/
|
|
841
|
-
async function waitForSettlementFlexible(resultPath, signal, timeoutMs, childSettled) {
|
|
842
|
-
const startAt = Date.now();
|
|
843
|
-
while (Date.now() - startAt < timeoutMs) {
|
|
844
|
-
if (signal.aborted) return {
|
|
845
|
-
state: "inconclusive",
|
|
846
|
-
observation: { outcome: "aborted" },
|
|
847
|
-
waitedMs: Date.now() - startAt
|
|
848
|
-
};
|
|
849
|
-
try {
|
|
850
|
-
const source = await readFile(resultPath, "utf8");
|
|
851
|
-
return {
|
|
852
|
-
...parseSettlementFile(source),
|
|
853
|
-
source,
|
|
854
|
-
waitedMs: Date.now() - startAt
|
|
855
|
-
};
|
|
856
|
-
} catch (error) {
|
|
857
|
-
if (!isMissing(error)) return {
|
|
858
|
-
state: "inconclusive",
|
|
859
|
-
observation: { outcome: "unreadable settlement file" },
|
|
860
|
-
waitedMs: Date.now() - startAt
|
|
861
|
-
};
|
|
862
|
-
if (await childSettled()) {
|
|
863
|
-
await sleep(15e3);
|
|
864
|
-
try {
|
|
865
|
-
const source = await readFile(resultPath, "utf8");
|
|
866
|
-
return {
|
|
867
|
-
...parseSettlementFile(source),
|
|
868
|
-
source,
|
|
869
|
-
waitedMs: Date.now() - startAt
|
|
870
|
-
};
|
|
871
|
-
} catch {
|
|
872
|
-
return {
|
|
873
|
-
state: "inconclusive",
|
|
874
|
-
observation: { outcome: "verifier child finished without writing the settlement file" },
|
|
875
|
-
waitedMs: Date.now() - startAt
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
await sleep(500);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
return {
|
|
883
|
-
state: "inconclusive",
|
|
884
|
-
observation: { outcome: "timed out waiting for verifier result" },
|
|
885
|
-
waitedMs: Date.now() - startAt
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
function parseSettlementFile(source) {
|
|
889
|
-
const value = parseLooseJson(source);
|
|
890
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return {
|
|
891
|
-
state: "inconclusive",
|
|
892
|
-
observation: { outcome: "settlement file is not a JSON object" }
|
|
893
|
-
};
|
|
894
|
-
const raw = value;
|
|
895
|
-
const state = raw.verdict ?? raw.settlement;
|
|
896
|
-
if (state !== "pass" && state !== "fail" && state !== "inconclusive") return {
|
|
897
|
-
state: "inconclusive",
|
|
898
|
-
observation: { outcome: "settlement file has an invalid settlement value" }
|
|
899
|
-
};
|
|
900
|
-
const observation = raw.evidence ?? raw.observation;
|
|
901
|
-
if (observation === void 0) return {
|
|
902
|
-
state: "inconclusive",
|
|
903
|
-
observation: { outcome: "settlement file has no observation evidence" }
|
|
904
|
-
};
|
|
905
|
-
if (observation === null || typeof observation !== "object" || Array.isArray(observation)) return {
|
|
906
|
-
state: "inconclusive",
|
|
907
|
-
observation: { outcome: "settlement observation is not an object" }
|
|
908
|
-
};
|
|
909
|
-
const record = {};
|
|
910
|
-
for (const [key, item] of Object.entries(observation)) if (typeof item === "string" || typeof item === "number" || typeof item === "boolean" || item === null) record[key] = item;
|
|
911
|
-
else if (Array.isArray(item) || typeof item === "object") {
|
|
912
|
-
if (isJsonValueLike(item)) record[key] = item;
|
|
913
|
-
}
|
|
914
|
-
if (Object.keys(record).length === 0) record.settled = state;
|
|
915
|
-
return {
|
|
916
|
-
state,
|
|
917
|
-
observation: record
|
|
918
|
-
};
|
|
919
|
-
}
|
|
920
|
-
/** Parse a settlement payload leniently: strip markdown fences, then try to extract the outermost JSON object. */
|
|
921
|
-
function parseLooseJson(source) {
|
|
922
|
-
let text = source.trim();
|
|
923
|
-
if (text.startsWith("```")) {
|
|
924
|
-
text = text.replace(/^```[A-Za-z]*\s*/u, "").replace(/\s*```\s*$/u, "");
|
|
925
|
-
text = text.trim();
|
|
926
|
-
}
|
|
927
|
-
try {
|
|
928
|
-
return JSON.parse(text);
|
|
929
|
-
} catch {
|
|
930
|
-
const start = text.indexOf("{");
|
|
931
|
-
if (start < 0) throw new Error("no JSON object found");
|
|
932
|
-
let depth = 0;
|
|
933
|
-
for (let index = start; index < text.length; index++) {
|
|
934
|
-
const ch = text[index];
|
|
935
|
-
if (ch === "{") depth++;
|
|
936
|
-
else if (ch === "}") {
|
|
937
|
-
depth--;
|
|
938
|
-
if (depth === 0) return JSON.parse(text.slice(start, index + 1));
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
throw new Error("no complete JSON object found");
|
|
943
|
-
}
|
|
944
|
-
function isJsonValueLike(value) {
|
|
945
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
946
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
947
|
-
if (Array.isArray(value)) return value.every(isJsonValueLike);
|
|
948
|
-
if (typeof value !== "object") return false;
|
|
949
|
-
return Object.values(value).every(isJsonValueLike);
|
|
950
|
-
}
|
|
951
|
-
function isMissing(error) {
|
|
952
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
953
|
-
}
|
|
954
|
-
function sleep(ms) {
|
|
955
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
956
|
-
}
|
|
957
|
-
//#endregion
|
|
958
|
-
//#region src/index.ts
|
|
959
|
-
/** Cordis plugin name. */
|
|
960
|
-
const name = "dsh-dog";
|
|
961
|
-
/** Required Harness services. */
|
|
962
|
-
const inject = [];
|
|
963
|
-
/** Loader-validated DoG deployment configuration. */
|
|
964
|
-
const Config = z.object({
|
|
965
|
-
storageDirectory: z.string().default("dog"),
|
|
966
|
-
workspaceRoot: z.string().default("dog/workspace"),
|
|
967
|
-
scriptsDirectory: z.string().default(fileURLToPath(new URL("../../../scripts/dog/", import.meta.url))),
|
|
968
|
-
maxGraphNodes: z.natural().min(1).default(256),
|
|
969
|
-
maxExpressionNodes: z.natural().min(1).default(512),
|
|
970
|
-
maxExpressionDepth: z.natural().min(1).default(64),
|
|
971
|
-
maxSandboxBytes: z.natural().min(1).default(67108864),
|
|
972
|
-
allowPartialRoot: z.boolean().default(false),
|
|
973
|
-
maxConcurrentVerifications: z.natural().min(1).default(1),
|
|
974
|
-
revalidateThreshold: z.number().default(.3),
|
|
975
|
-
gmDigestAlgo: z.string().default("sha256"),
|
|
976
|
-
subagentProvider: z.string().default("spawn"),
|
|
977
|
-
subagentMaxDepth: z.natural().default(3)
|
|
978
|
-
});
|
|
979
|
-
Object.assign(Config.dict.maxConcurrentVerifications.meta, { volatile: true });
|
|
980
|
-
function concurrencyValue(value) {
|
|
981
|
-
const reference = value;
|
|
982
|
-
return typeof reference?.get === "function" ? reference.get() : value;
|
|
983
|
-
}
|
|
984
|
-
/** Register all DoG tools as Cordis-owned effects so fiber disposal removes them. */
|
|
985
|
-
async function apply(ctx, config) {
|
|
986
|
-
const schema = await loadSchemaSet();
|
|
987
|
-
let resolved = config;
|
|
988
|
-
let settingsCurrent;
|
|
989
|
-
ctx.inject(["settings"], (settingsCtx) => {
|
|
990
|
-
if (typeof settingsCtx.settings.installSection !== "function") {
|
|
991
|
-
settingsCurrent = () => config;
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
return settingsCtx.settings.installSection(ctx, "dog", z.object({
|
|
995
|
-
storageDirectory: z.string().default("dog"),
|
|
996
|
-
workspaceRoot: z.string().default("dog/workspace"),
|
|
997
|
-
scriptsDirectory: z.string().default(fileURLToPath(new URL("../../../scripts/dog/", import.meta.url))),
|
|
998
|
-
maxGraphNodes: z.natural().min(1).default(256),
|
|
999
|
-
maxExpressionNodes: z.natural().min(1).default(512),
|
|
1000
|
-
maxExpressionDepth: z.natural().min(1).default(64),
|
|
1001
|
-
maxSandboxBytes: z.natural().min(1).default(67108864),
|
|
1002
|
-
allowPartialRoot: z.boolean().default(false),
|
|
1003
|
-
maxConcurrentVerifications: z.natural().min(1).default(1),
|
|
1004
|
-
revalidateThreshold: z.number().default(.3),
|
|
1005
|
-
gmDigestAlgo: z.string().default("sha256"),
|
|
1006
|
-
subagentProvider: z.string().default("spawn"),
|
|
1007
|
-
subagentMaxDepth: z.natural().default(3)
|
|
1008
|
-
}), config, {
|
|
1009
|
-
setSource: (current) => {
|
|
1010
|
-
settingsCurrent = current;
|
|
1011
|
-
resolved = {
|
|
1012
|
-
...config,
|
|
1013
|
-
...current()
|
|
1014
|
-
};
|
|
1015
|
-
},
|
|
1016
|
-
onChange: () => void 0
|
|
1017
|
-
});
|
|
1018
|
-
});
|
|
1019
|
-
const repository = new DogRepository(dshHomePath(config.storageDirectory), schema);
|
|
1020
|
-
repository.markOrphanedRunningRuns((/* @__PURE__ */ new Date()).toISOString()).catch(() => void 0);
|
|
1021
|
-
const scriptsDir = isAbsolute(config.scriptsDirectory) ? config.scriptsDirectory : dshHomePath(config.scriptsDirectory);
|
|
1022
|
-
const programmatic = async (script, inputPath, env) => {
|
|
1023
|
-
let out;
|
|
1024
|
-
try {
|
|
1025
|
-
out = await runOwnedScript(resolveScriptPath(scriptsDir, script), inputPath, env.signal);
|
|
1026
|
-
} catch (error) {
|
|
1027
|
-
return scriptFailureVerdict(error);
|
|
1028
|
-
}
|
|
1029
|
-
return parseVerdict(out);
|
|
1030
|
-
};
|
|
1031
|
-
let dogEngine;
|
|
1032
|
-
ctx.effect(() => async () => {
|
|
1033
|
-
await dogEngine?.dispose();
|
|
1034
|
-
});
|
|
1035
|
-
let agenticRunnerRef;
|
|
1036
|
-
let subagentRuntimeRef;
|
|
1037
|
-
const activeVerifiers = /* @__PURE__ */ new Map();
|
|
1038
|
-
const getEngine = () => {
|
|
1039
|
-
if (dogEngine === void 0) {
|
|
1040
|
-
const effective = settingsCurrent === void 0 ? resolved : {
|
|
1041
|
-
...config,
|
|
1042
|
-
...settingsCurrent()
|
|
1043
|
-
};
|
|
1044
|
-
dogEngine = new DogEngine({
|
|
1045
|
-
config: {
|
|
1046
|
-
...effective,
|
|
1047
|
-
maxConcurrentVerifications: concurrencyValue(effective.maxConcurrentVerifications),
|
|
1048
|
-
workspaceRoot: isAbsolute(effective.workspaceRoot) ? effective.workspaceRoot : dshHomePath(effective.workspaceRoot),
|
|
1049
|
-
scriptsDirectory: isAbsolute(effective.scriptsDirectory) ? effective.scriptsDirectory : dshHomePath(effective.scriptsDirectory)
|
|
1050
|
-
},
|
|
1051
|
-
programmatic,
|
|
1052
|
-
...agenticRunnerRef === void 0 ? {} : { agentic: agenticRunnerRef },
|
|
1053
|
-
repository,
|
|
1054
|
-
workspaces: new WorkspaceManager({ baseDir: effective.workspaceRoot }),
|
|
1055
|
-
onRunCancelled: (runId) => {
|
|
1056
|
-
for (const [sessionId, record] of activeVerifiers) {
|
|
1057
|
-
if (record.runId !== runId) continue;
|
|
1058
|
-
try {
|
|
1059
|
-
subagentRuntimeRef?.interrupt(sessionId, {
|
|
1060
|
-
kind: "ancestor",
|
|
1061
|
-
agent: record.parent
|
|
1062
|
-
});
|
|
1063
|
-
} catch {}
|
|
1064
|
-
}
|
|
1065
|
-
},
|
|
1066
|
-
liveConfig: () => {
|
|
1067
|
-
const live = settingsCurrent?.();
|
|
1068
|
-
return live === void 0 ? {} : { maxConcurrentVerifications: concurrencyValue(live.maxConcurrentVerifications) };
|
|
1069
|
-
},
|
|
1070
|
-
resolveLivingAgent: (sessionId) => {
|
|
1071
|
-
return ctx.get("agents")?.get(sessionId);
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
return dogEngine;
|
|
1076
|
-
};
|
|
1077
|
-
const scopedTools = [...createDogTools(getEngine, () => ctx.get("jobs"))];
|
|
1078
|
-
ctx.provide("openwriteDog", { tools: scopedTools });
|
|
1079
|
-
ctx.inject(["subagents"], (subagentCtx) => {
|
|
1080
|
-
const subagents = subagentCtx.subagents;
|
|
1081
|
-
subagentRuntimeRef = subagents;
|
|
1082
|
-
const agenticRunner = async (instruction, workspace, inputPath, env) => {
|
|
1083
|
-
const { parent, runId } = env;
|
|
1084
|
-
const signal = env.signal ?? new AbortController().signal;
|
|
1085
|
-
const goalId = env.goalId ?? "verification";
|
|
1086
|
-
const resultPath = join(workspace.path, "settlement.json");
|
|
1087
|
-
const parentSessionId = readSessionId(parent);
|
|
1088
|
-
if (parent === void 0) throw new Error("verifier delegation requires a live parent Agent (recovered runs without a live parent cannot spawn verification workers)");
|
|
1089
|
-
const sessionId = (await subagents.startContinuable({
|
|
1090
|
-
provider: config.subagentProvider,
|
|
1091
|
-
label: `verifier ${goalId} · ${runId === void 0 ? "run" : runId.slice(0, 6)}`,
|
|
1092
|
-
request: {
|
|
1093
|
-
prompt: [{
|
|
1094
|
-
type: "text",
|
|
1095
|
-
text: buildVerifierPrompt(instruction, inputPath, resultPath)
|
|
1096
|
-
}],
|
|
1097
|
-
parent,
|
|
1098
|
-
maxDepth: config.subagentMaxDepth
|
|
1099
|
-
},
|
|
1100
|
-
signal
|
|
1101
|
-
})).childId;
|
|
1102
|
-
activeVerifiers.set(sessionId, {
|
|
1103
|
-
parent,
|
|
1104
|
-
runId: runId ?? ""
|
|
1105
|
-
});
|
|
1106
|
-
if (runId !== void 0) try {
|
|
1107
|
-
await getEngine().annotateRun(runId, `bind-branch entered for ${goalId}: session ${sessionId.slice(0, 8)}`);
|
|
1108
|
-
const binding = await getEngine().bindAgent({
|
|
1109
|
-
runId,
|
|
1110
|
-
goalId,
|
|
1111
|
-
role: "verifier",
|
|
1112
|
-
sessionId,
|
|
1113
|
-
...parentSessionId.length > 0 ? { parentSessionId } : {}
|
|
1114
|
-
}, { allowUnrooted: parentSessionId.length === 0 });
|
|
1115
|
-
await getEngine().annotateRun(runId, `verifier bound for ${goalId}: ${sessionId.slice(0, 8)} (${binding.run.goals[goalId]?.agentSessions?.length ?? 0} bound)`);
|
|
1116
|
-
} catch (bindingError) {
|
|
1117
|
-
await getEngine().annotateRun(runId, `verifier binding failed for ${goalId}: ${String(bindingError).slice(0, 300)}`);
|
|
1118
|
-
await getEngine().recordVerifierLifecycle(runId, goalId, "verifier_bind_failed", sessionId, String(bindingError));
|
|
1119
|
-
}
|
|
1120
|
-
const settlement = await waitForSettlementFlexible(resultPath, signal, 9e5, async () => verifierChildTurnEnded(ctx, sessionId));
|
|
1121
|
-
if (settlement.source !== void 0 && runId !== void 0) repository.saveSettlement(runId, goalId, settlement.source).catch(() => void 0);
|
|
1122
|
-
if (runId !== void 0 && parent !== void 0 && parentSessionId.length > 0) try {
|
|
1123
|
-
await subagents.drainContinuableChildren(parent, [sessionId]);
|
|
1124
|
-
activeVerifiers.delete(sessionId);
|
|
1125
|
-
await getEngine().recordVerifierLifecycle(runId, goalId, "verifier_released", sessionId);
|
|
1126
|
-
} catch (releaseError) {
|
|
1127
|
-
await getEngine().annotateRun(runId, `verifier release failed for ${goalId}: ${String(releaseError).slice(0, 300)}`);
|
|
1128
|
-
}
|
|
1129
|
-
return {
|
|
1130
|
-
state: settlement.state,
|
|
1131
|
-
evidence: settlement.observation
|
|
1132
|
-
};
|
|
1133
|
-
};
|
|
1134
|
-
agenticRunnerRef = agenticRunner;
|
|
1135
|
-
const delegateTool = createDogDelegateAgentTool(getEngine, {
|
|
1136
|
-
async launch(input) {
|
|
1137
|
-
return { sessionId: (await subagents.startContinuable({
|
|
1138
|
-
provider: config.subagentProvider,
|
|
1139
|
-
label: input.label,
|
|
1140
|
-
request: {
|
|
1141
|
-
prompt: [{
|
|
1142
|
-
type: "text",
|
|
1143
|
-
text: input.prompt
|
|
1144
|
-
}],
|
|
1145
|
-
parent: input.parent,
|
|
1146
|
-
maxDepth: config.subagentMaxDepth
|
|
1147
|
-
},
|
|
1148
|
-
signal: input.signal
|
|
1149
|
-
})).childId };
|
|
1150
|
-
},
|
|
1151
|
-
interrupt(sessionId, parent) {
|
|
1152
|
-
subagents.interrupt(sessionId, {
|
|
1153
|
-
kind: "ancestor",
|
|
1154
|
-
agent: parent
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
scopedTools.push(delegateTool);
|
|
1159
|
-
});
|
|
1160
|
-
const debugHandler = createDogDebugRpcHandler(repository);
|
|
1161
|
-
ctx.on("session/event", (session, event) => {
|
|
1162
|
-
if (event.type !== "turn/end") return;
|
|
1163
|
-
if (event.data.reason?.kind !== "interrupted") return;
|
|
1164
|
-
captureInterruptedTurn(repository, session, event).catch(() => void 0);
|
|
1165
|
-
});
|
|
1166
|
-
ctx.inject(["connection", "webServer"], (c) => {
|
|
1167
|
-
c.effect(() => c.webServer.register({
|
|
1168
|
-
kind: "prefix",
|
|
1169
|
-
path: DOG_DEBUG_RPC_CHANNEL,
|
|
1170
|
-
handler: async (req, res) => {
|
|
1171
|
-
const rejection = c.connection.requestRejection({ headers: req.headers });
|
|
1172
|
-
if (rejection !== void 0) {
|
|
1173
|
-
res.writeHead(rejection);
|
|
1174
|
-
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
await serveDogDebugRpc(req, res, debugHandler);
|
|
1178
|
-
}
|
|
1179
|
-
}), "dog: trusted debugger RPC");
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
async function captureInterruptedTurn(repository, session, event) {
|
|
1183
|
-
const diagnosticsDir = join(repository.rootPath, "diagnostics");
|
|
1184
|
-
await mkdir(diagnosticsDir, { recursive: true });
|
|
1185
|
-
const recentEvents = session.snapshotEvents().slice(-12).map((candidate) => ({
|
|
1186
|
-
seq: candidate.seq,
|
|
1187
|
-
type: candidate.type,
|
|
1188
|
-
time: candidate.time
|
|
1189
|
-
}));
|
|
1190
|
-
const payload = {
|
|
1191
|
-
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1192
|
-
process: {
|
|
1193
|
-
pid: process.pid,
|
|
1194
|
-
startedAt: (/* @__PURE__ */ new Date(Date.now() - process.uptime() * 1e3)).toISOString()
|
|
1195
|
-
},
|
|
1196
|
-
sessionId: session.id,
|
|
1197
|
-
interruptSeq: event.seq,
|
|
1198
|
-
interruptEventTime: new Date(event.time).toISOString(),
|
|
1199
|
-
recentEvents
|
|
1200
|
-
};
|
|
1201
|
-
await writeFile(join(diagnosticsDir, `interrupt-${session.id.replaceAll(/[^a-zA-Z0-9-]/gu, "_")}-${event.seq}.json`), JSON.stringify(payload, null, 2));
|
|
1202
|
-
}
|
|
1203
|
-
function buildVerifierPrompt(instruction, inputPath, resultPath) {
|
|
1204
|
-
return [
|
|
1205
|
-
"You are a DoG verifier. Judge only the object handed to you.",
|
|
1206
|
-
"",
|
|
1207
|
-
`Object (read from here; only this tree is yours): ${inputPath}`,
|
|
1208
|
-
"",
|
|
1209
|
-
"Instruction:",
|
|
1210
|
-
instruction,
|
|
1211
|
-
"",
|
|
1212
|
-
"Decide on your own terms; gather the strongest evidence you can with your own judgment. No fixed evidence format.",
|
|
1213
|
-
`Write your verdict to this exact absolute path: ${resultPath}. The file must contain exactly:`,
|
|
1214
|
-
"{\"verdict\": \"pass\" | \"fail\" | \"inconclusive\", \"evidence\": <any JSON>}",
|
|
1215
|
-
"Create it (it does not exist yet); never write it as a relative path or to another location. After writing, stop."
|
|
1216
|
-
].join("\n");
|
|
1217
|
-
}
|
|
1218
|
-
/** Read the durable session id from a live Agent, or '' outside an agent boundary. */
|
|
1219
|
-
function readSessionId(parent) {
|
|
1220
|
-
if (parent === void 0) return "";
|
|
1221
|
-
return String(parent.id);
|
|
1222
|
-
}
|
|
1223
|
-
/** True once the verifier child's turn has ended (or the child is gone entirely). */
|
|
1224
|
-
function verifierChildTurnEnded(ctx, sessionId) {
|
|
1225
|
-
const agent = ctx.get("agents")?.get(sessionId);
|
|
1226
|
-
if (agent === void 0) return true;
|
|
1227
|
-
return agent.session.snapshotEvents().at(-1)?.type === "turn/end";
|
|
1228
|
-
}
|
|
1229
|
-
//#endregion
|
|
1230
|
-
export { Config, DOG_BIND_AGENT_TOOL, DOG_CREATE_TOOL, DOG_DEBUG_RPC_CHANNEL, DOG_DEBUG_SNAPSHOT_ENDPOINT, DOG_DELEGATE_AGENT_TOOL, DOG_RUNTIME_TRACE_ENDPOINT, DOG_RUN_TOOL, DOG_STATUS_TOOL, DOG_VALIDATE_TOOL, DOG_WAIT_TOOL, DogEngine, DogValidationError, WorkspaceManager, apply, buildDogDebugSnapshot, buildGoalRuntimeTrace, createDogDebugRpcHandler, createDogDelegateAgentTool, createDogTools, evaluateBoolExpr, inject, loadSchemaSet, name, parseBoolExpr, parseGraph, runPlan };
|