harnery 0.31.6 → 0.32.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/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +70 -95
- package/dist/commands/browse-session.d.ts +21 -0
- package/dist/commands/browse-session.d.ts.map +1 -0
- package/dist/commands/browse-session.js +157 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +205 -37
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +40 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +100 -17
- package/dist/commands/init.d.ts +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -5
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +1 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +1 -2
- package/dist/core/agents/cli.js +123 -88
- package/dist/core/agents/coord-client.d.ts +7 -3
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +5 -4
- package/dist/core/agents/finalization.d.ts +68 -0
- package/dist/core/agents/finalization.d.ts.map +1 -0
- package/dist/core/agents/finalization.js +443 -0
- package/dist/core/agents/git-hook.d.ts +51 -0
- package/dist/core/agents/git-hook.d.ts.map +1 -0
- package/dist/core/agents/git-hook.js +118 -0
- package/dist/core/agents/render/prompt-context.d.ts +6 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +26 -13
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +15 -2
- package/dist/core/agents/rules/claim-conflict.js +3 -3
- package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +21 -5
- package/dist/core/agents/rules/stop-hook.d.ts +3 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +59 -23
- package/dist/core/agents/session-events.d.ts +8 -16
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -26
- package/dist/core/agents/state/heartbeat-projector.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +44 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +32 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +54 -17
- package/dist/core/agents/state/names.d.ts +22 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +40 -2
- package/dist/core/config.d.ts +30 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +74 -11
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +12 -13
- package/dist/core/hooks/adapter/detect.d.ts +2 -4
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +4 -14
- package/dist/core/hooks/adapter/output.d.ts +1 -1
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +5 -4
- package/dist/core/hooks/adapter/wiring.d.ts +23 -0
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +32 -0
- package/dist/core/hooks/cli.d.ts +1 -2
- package/dist/core/hooks/cli.d.ts.map +1 -1
- package/dist/core/hooks/cli.js +169 -27
- package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
- package/dist/core/hooks/codex-wsl-bridge.js +137 -0
- package/dist/core/hooks/effects/index.d.ts +1 -1
- package/dist/core/hooks/effects/index.js +3 -3
- package/dist/core/hooks/events/schema.d.ts +27 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +7 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +15 -0
- package/dist/core/hooks/resolve/transcript.d.ts +44 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +176 -1
- package/dist/core/hooks/session-name-presence.d.ts +30 -0
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
- package/dist/core/hooks/session-name-presence.js +43 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
- package/dist/core/hooks/unsafe-cross-shell.js +151 -0
- package/dist/core/work/state.d.ts +4 -5
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +5 -8
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +2 -2
- package/dist/core/workflow/run-state.d.ts +2 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +2 -2
- package/dist/core/workflow/types.d.ts +3 -3
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.d.ts +2 -2
- package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.js +5 -0
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +1 -2
- package/dist/core/workflow/workspaces/paths.d.ts +2 -2
- package/dist/core/workflow/workspaces/types.d.ts +0 -6
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.d.ts +0 -2
- package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.js +0 -2
- package/dist/lib/browser/client.d.ts +118 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +435 -6
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +180 -37
- package/dist/lib/browser/index.d.ts +5 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +5 -1
- package/dist/lib/browser/netscape-cookies.d.ts +6 -0
- package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
- package/dist/lib/browser/netscape-cookies.js +31 -0
- package/dist/lib/browser/proxy.d.ts +24 -0
- package/dist/lib/browser/proxy.d.ts.map +1 -0
- package/dist/lib/browser/proxy.js +84 -0
- package/dist/lib/browser/runts.d.ts +6 -0
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +21 -4
- package/dist/lib/browser/session-control.d.ts +112 -0
- package/dist/lib/browser/session-control.d.ts.map +1 -0
- package/dist/lib/browser/session-control.js +670 -0
- package/dist/lib/docs-links.d.ts +108 -0
- package/dist/lib/docs-links.d.ts.map +1 -0
- package/dist/lib/docs-links.js +555 -0
- package/dist/lib/exec.d.ts +1 -1
- package/dist/lib/exec.js +1 -1
- package/dist/lib/identities/assume.d.ts +4 -2
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +15 -2
- package/dist/lib/instructions/git-hooks.d.ts +75 -0
- package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
- package/dist/lib/instructions/git-hooks.js +238 -0
- package/dist/lib/instructions/splice.d.ts +10 -4
- package/dist/lib/instructions/splice.d.ts.map +1 -1
- package/dist/lib/instructions/splice.js +19 -11
- package/package.json +1 -1
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +2 -0
- package/src/commands/agents.ts +96 -115
- package/src/commands/browse-session.ts +245 -0
- package/src/commands/browse.ts +287 -44
- package/src/commands/checkpoint.ts +1 -1
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +100 -18
- package/src/commands/init.ts +17 -9
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +1 -2
- package/src/core/agents/cli.ts +137 -88
- package/src/core/agents/coord-client.ts +10 -5
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/render/prompt-context.ts +28 -13
- package/src/core/agents/render/session-context.ts +15 -2
- package/src/core/agents/rules/claim-conflict.ts +3 -3
- package/src/core/agents/rules/commit-conflict.ts +35 -8
- package/src/core/agents/rules/stop-hook.ts +71 -23
- package/src/core/agents/session-events.ts +14 -41
- package/src/core/agents/state/heartbeat-projector.ts +45 -1
- package/src/core/agents/state/heartbeat-writer.ts +78 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/config.ts +92 -12
- package/src/core/governor/planning.ts +17 -12
- package/src/core/hooks/adapter/detect.ts +4 -13
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +201 -23
- package/src/core/hooks/codex-wsl-bridge.ts +188 -0
- package/src/core/hooks/effects/index.ts +5 -5
- package/src/core/hooks/events/schema.ts +27 -0
- package/src/core/hooks/resolve/owner.ts +22 -1
- package/src/core/hooks/resolve/transcript.ts +164 -1
- package/src/core/hooks/session-name-presence.ts +52 -0
- package/src/core/hooks/unsafe-cross-shell.ts +160 -0
- package/src/core/work/state.ts +8 -12
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/proof.ts +4 -4
- package/src/core/workflow/run-state.ts +5 -5
- package/src/core/workflow/types.ts +3 -3
- package/src/core/workflow/workspaces/execution.ts +11 -4
- package/src/core/workflow/workspaces/index.ts +0 -3
- package/src/core/workflow/workspaces/local-git.ts +1 -2
- package/src/core/workflow/workspaces/paths.ts +2 -2
- package/src/core/workflow/workspaces/types.ts +0 -9
- package/src/core/workflow/workspaces/validate.ts +1 -5
- package/src/lib/browser/client.ts +528 -6
- package/src/lib/browser/geometry.ts +197 -37
- package/src/lib/browser/index.ts +39 -0
- package/src/lib/browser/netscape-cookies.ts +39 -0
- package/src/lib/browser/proxy.ts +105 -0
- package/src/lib/browser/runts.ts +27 -3
- package/src/lib/browser/session-control.ts +892 -0
- package/src/lib/docs-links.ts +674 -0
- package/src/lib/exec.ts +1 -1
- package/src/lib/identities/assume.ts +22 -1
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { isWslUncPath } from "./codex-wsl-bridge.ts";
|
|
2
|
+
import type { Adapter } from "./events/schema.ts";
|
|
3
|
+
|
|
4
|
+
export interface CrossShellInspection {
|
|
5
|
+
adapter: Adapter;
|
|
6
|
+
cwd: unknown;
|
|
7
|
+
toolName: unknown;
|
|
8
|
+
toolInput: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DENY_REASON =
|
|
12
|
+
"Unsafe Windows-to-WSL shell-string construction blocked. Use the host's argv-preserving " +
|
|
13
|
+
"WSL bridge for a single executable, or its literal-script mode for compound Bash. Do not " +
|
|
14
|
+
"invoke wsl.exe with bash -c or bash -lc.";
|
|
15
|
+
|
|
16
|
+
function shellCommand(toolName: unknown, toolInput: unknown): string | null {
|
|
17
|
+
if (typeof toolName !== "string") return null;
|
|
18
|
+
const normalized = toolName.toLowerCase();
|
|
19
|
+
if (
|
|
20
|
+
normalized !== "bash" &&
|
|
21
|
+
normalized !== "shell" &&
|
|
22
|
+
normalized !== "shell_command" &&
|
|
23
|
+
normalized !== "functions.shell_command"
|
|
24
|
+
) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let input = toolInput;
|
|
29
|
+
if (typeof input === "string") {
|
|
30
|
+
try {
|
|
31
|
+
input = JSON.parse(input) as unknown;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!input || typeof input !== "object") return null;
|
|
37
|
+
const command = (input as Record<string, unknown>).command;
|
|
38
|
+
return typeof command === "string" ? command : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function quotedToken(command: string, start: number): { token: string; end: number } {
|
|
42
|
+
const quote = command[start]!;
|
|
43
|
+
let token = "";
|
|
44
|
+
for (let index = start + 1; index < command.length; index += 1) {
|
|
45
|
+
const char = command[index]!;
|
|
46
|
+
if (char === "`" && quote === '"' && index + 1 < command.length) {
|
|
47
|
+
token += command[index + 1]!;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === quote) {
|
|
52
|
+
if (quote === "'" && command[index + 1] === "'") {
|
|
53
|
+
token += "'";
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
return { token, end: index + 1 };
|
|
58
|
+
}
|
|
59
|
+
token += char;
|
|
60
|
+
}
|
|
61
|
+
return { token, end: command.length };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Find direct PowerShell command invocations of wsl/wsl.exe outside quoted strings. */
|
|
65
|
+
function wslInvocationOffsets(command: string): number[] {
|
|
66
|
+
const offsets: number[] = [];
|
|
67
|
+
let expectCommand = true;
|
|
68
|
+
let callOperator = false;
|
|
69
|
+
|
|
70
|
+
for (let index = 0; index < command.length; ) {
|
|
71
|
+
const char = command[index]!;
|
|
72
|
+
if (/\s/.test(char)) {
|
|
73
|
+
if (char === "\n" || char === "\r") {
|
|
74
|
+
expectCommand = true;
|
|
75
|
+
callOperator = false;
|
|
76
|
+
}
|
|
77
|
+
index += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (char === "#" && expectCommand) {
|
|
81
|
+
const newline = command.indexOf("\n", index + 1);
|
|
82
|
+
index = newline === -1 ? command.length : newline + 1;
|
|
83
|
+
expectCommand = true;
|
|
84
|
+
callOperator = false;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (";|{}=()".includes(char)) {
|
|
88
|
+
expectCommand = true;
|
|
89
|
+
callOperator = false;
|
|
90
|
+
index += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (char === "&") {
|
|
94
|
+
expectCommand = true;
|
|
95
|
+
callOperator = true;
|
|
96
|
+
index += command[index + 1] === "&" ? 2 : 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (char === "'" || char === '"') {
|
|
100
|
+
const parsed = quotedToken(command, index);
|
|
101
|
+
if (expectCommand && callOperator && /^wsl(?:\.exe)?$/i.test(parsed.token)) {
|
|
102
|
+
offsets.push(index);
|
|
103
|
+
}
|
|
104
|
+
expectCommand = false;
|
|
105
|
+
callOperator = false;
|
|
106
|
+
index = parsed.end;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const start = index;
|
|
111
|
+
while (index < command.length && !/[\s;|{}=()&]/.test(command[index]!)) index += 1;
|
|
112
|
+
const token = command.slice(start, index);
|
|
113
|
+
if (expectCommand && /^wsl(?:\.exe)?$/i.test(token)) offsets.push(start);
|
|
114
|
+
expectCommand = false;
|
|
115
|
+
callOperator = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return offsets;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function statementEnd(command: string, start: number): number {
|
|
122
|
+
let quote: string | null = null;
|
|
123
|
+
for (let index = start; index < command.length; index += 1) {
|
|
124
|
+
const char = command[index]!;
|
|
125
|
+
if (quote) {
|
|
126
|
+
if (char === "`" && quote === '"') {
|
|
127
|
+
index += 1;
|
|
128
|
+
} else if (char === quote) {
|
|
129
|
+
if (quote === "'" && command[index + 1] === "'") index += 1;
|
|
130
|
+
else quote = null;
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (char === "'" || char === '"') quote = char;
|
|
135
|
+
else if (char === ";" || char === "\n" || char === "\r" || char === "|") return index;
|
|
136
|
+
else if (char === "&" && command[index + 1] === "&") return index;
|
|
137
|
+
}
|
|
138
|
+
return command.length;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function invokesBashCommandString(command: string): boolean {
|
|
142
|
+
for (const offset of wslInvocationOffsets(command)) {
|
|
143
|
+
const segment = command.slice(offset, statementEnd(command, offset));
|
|
144
|
+
if (/(?:^|\s)(?:\/bin\/)?bash(?=\s|$)[\s\S]*?\s-[a-z]*c[a-z]*(?=\s|$)/i.test(segment)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Deny only the unsafe hybrid shape: Windows-native Codex, WSL UNC workspace,
|
|
153
|
+
* shell tool, and a direct wsl.exe -> bash -c/-lc command-string invocation.
|
|
154
|
+
*/
|
|
155
|
+
export function unsafeCrossShellReason(input: CrossShellInspection): string | null {
|
|
156
|
+
if (input.adapter !== "codex" || !isWslUncPath(input.cwd)) return null;
|
|
157
|
+
const command = shellCommand(input.toolName, input.toolInput);
|
|
158
|
+
if (!command || !invokesBashCommandString(command)) return null;
|
|
159
|
+
return DENY_REASON;
|
|
160
|
+
}
|
package/src/core/work/state.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { readWorkflowRunManifest, workflowScriptDigest } from "../workflow/run-s
|
|
|
21
21
|
import { WORKFLOW_TRANSCRIPT_EVENT_BYTES, workflowTranscriptPath } from "../workflow/transcript.ts";
|
|
22
22
|
import { isWorkspaceBoundExecutionEvidence } from "../workflow/workspaces/validate.ts";
|
|
23
23
|
|
|
24
|
-
export const WORK_INTENT_SCHEMA_VERSION =
|
|
24
|
+
export const WORK_INTENT_SCHEMA_VERSION = 2 as const;
|
|
25
25
|
export const WORK_EVENT_SCHEMA_VERSION = 1 as const;
|
|
26
26
|
|
|
27
27
|
const WORK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
@@ -77,12 +77,11 @@ export interface WorkIntent {
|
|
|
77
77
|
dependencies: string[];
|
|
78
78
|
workflow: { path: string; sha256: string };
|
|
79
79
|
max_attempts: number;
|
|
80
|
-
/** Ceiling on
|
|
80
|
+
/** Ceiling on consecutive uncharged attempts (ADR 0046), separate from
|
|
81
81
|
* max_attempts. An upstream outage produces uncharged attempt after uncharged
|
|
82
82
|
* attempt; without a bound it would retry forever. At the bound the item stops
|
|
83
|
-
* and reports it is blocked on an outside service.
|
|
84
|
-
|
|
85
|
-
max_uncharged_attempts?: number;
|
|
83
|
+
* and reports it is blocked on an outside service. */
|
|
84
|
+
max_uncharged_attempts: number;
|
|
86
85
|
source?: { kind: "human" | "workflow" | "external"; ref?: string };
|
|
87
86
|
created_at: string;
|
|
88
87
|
}
|
|
@@ -551,7 +550,7 @@ function deriveWorkProjection(
|
|
|
551
550
|
// Count trailing consecutive uncharged attempts in the current window. The
|
|
552
551
|
// bound is the only brake on an outage that never ends, so at the limit the
|
|
553
552
|
// item stops and names the outside service — distinct from work-blocked.
|
|
554
|
-
const maxUncharged = intent.max_uncharged_attempts
|
|
553
|
+
const maxUncharged = intent.max_uncharged_attempts;
|
|
555
554
|
let trailingUncharged = 0;
|
|
556
555
|
for (let index = currentAttempts.length - 1; index >= 0; index--) {
|
|
557
556
|
if (currentAttempts[index]!.uncharged === undefined) break;
|
|
@@ -1041,12 +1040,9 @@ function validateWorkIntent(intent: WorkIntent, workId: string): void {
|
|
|
1041
1040
|
!Number.isSafeInteger(intent.max_attempts) ||
|
|
1042
1041
|
intent.max_attempts < 1 ||
|
|
1043
1042
|
intent.max_attempts > 100 ||
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
(!Number.isSafeInteger(intent.max_uncharged_attempts) ||
|
|
1048
|
-
intent.max_uncharged_attempts < 1 ||
|
|
1049
|
-
intent.max_uncharged_attempts > 100))
|
|
1043
|
+
!Number.isSafeInteger(intent.max_uncharged_attempts) ||
|
|
1044
|
+
intent.max_uncharged_attempts < 1 ||
|
|
1045
|
+
intent.max_uncharged_attempts > 100
|
|
1050
1046
|
) {
|
|
1051
1047
|
throw new Error(`work intent ${workId} has an unsupported or mismatched schema`);
|
|
1052
1048
|
}
|
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
import type {
|
|
88
88
|
WorkspaceAttestation,
|
|
89
89
|
WorkspaceBinding,
|
|
90
|
-
|
|
90
|
+
WorkspaceCompatibilityExecutionEvidence,
|
|
91
91
|
} from "./workspaces/types.ts";
|
|
92
92
|
import { isWorkspaceAttestation } from "./workspaces/validate.ts";
|
|
93
93
|
|
|
@@ -324,7 +324,7 @@ async function executeWorkflow(
|
|
|
324
324
|
const fallbackMeta = normalizeWorkflowMeta(undefined, fallbackName);
|
|
325
325
|
const workspaceProvider = isolation === "shared" ? undefined : opts.workspace?.provider;
|
|
326
326
|
let workspaceBinding: WorkspaceBinding | undefined;
|
|
327
|
-
let workspaceFallback:
|
|
327
|
+
let workspaceFallback: WorkspaceCompatibilityExecutionEvidence | undefined =
|
|
328
328
|
resumeState?.manifest.execution.workspace_fallback;
|
|
329
329
|
try {
|
|
330
330
|
workspaceBinding = await resolveWorkspaceBinding({
|
|
@@ -40,13 +40,13 @@ import { isCanonicalWorkflowWorkContext } from "./work-context.ts";
|
|
|
40
40
|
import type {
|
|
41
41
|
WorkspaceAttestation,
|
|
42
42
|
WorkspaceBinding,
|
|
43
|
+
WorkspaceCompatibilityExecutionEvidence,
|
|
43
44
|
WorkspaceExecutionEvidence,
|
|
44
|
-
WorkspaceUnsupportedExecutionEvidence,
|
|
45
45
|
} from "./workspaces/index.ts";
|
|
46
46
|
import {
|
|
47
47
|
isWorkspaceBoundExecutionEvidence,
|
|
48
|
+
isWorkspaceCompatibilityExecutionEvidence,
|
|
48
49
|
isWorkspaceExecutionEvidence,
|
|
49
|
-
isWorkspaceUnsupportedExecutionEvidence,
|
|
50
50
|
workspaceProofLifecycle,
|
|
51
51
|
} from "./workspaces/validate.ts";
|
|
52
52
|
|
|
@@ -101,7 +101,7 @@ export interface BuildWorkflowProofInput {
|
|
|
101
101
|
blocked?: { reason: string; decisionId?: string };
|
|
102
102
|
workspaceBinding?: WorkspaceBinding;
|
|
103
103
|
workspaceAttestation?: WorkspaceAttestation;
|
|
104
|
-
workspaceFallback?:
|
|
104
|
+
workspaceFallback?: WorkspaceCompatibilityExecutionEvidence;
|
|
105
105
|
result?: unknown;
|
|
106
106
|
error?: string;
|
|
107
107
|
}
|
|
@@ -401,7 +401,7 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
|
|
|
401
401
|
? isWorkspaceBoundExecutionEvidence(proofExecution, runId) &&
|
|
402
402
|
stableDigest(proofExecution.binding) === stableDigest(manifestBinding)
|
|
403
403
|
: manifestFallback !== undefined
|
|
404
|
-
?
|
|
404
|
+
? isWorkspaceCompatibilityExecutionEvidence(proofExecution, runId) &&
|
|
405
405
|
stableDigest(proofExecution) === stableDigest(manifestFallback)
|
|
406
406
|
: proofExecution === undefined;
|
|
407
407
|
if (!executionMatches) {
|
|
@@ -33,12 +33,12 @@ import { isCanonicalWorkflowWorkContext } from "./work-context.ts";
|
|
|
33
33
|
import type {
|
|
34
34
|
WorkspaceAllocationRequest,
|
|
35
35
|
WorkspaceBinding,
|
|
36
|
-
|
|
36
|
+
WorkspaceCompatibilityExecutionEvidence,
|
|
37
37
|
} from "./workspaces/index.ts";
|
|
38
38
|
import {
|
|
39
39
|
assertWorkspaceManifestAuthority,
|
|
40
40
|
isWorkspaceBinding,
|
|
41
|
-
|
|
41
|
+
isWorkspaceCompatibilityExecutionEvidence,
|
|
42
42
|
} from "./workspaces/validate.ts";
|
|
43
43
|
|
|
44
44
|
export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 1 as const;
|
|
@@ -70,7 +70,7 @@ export interface WorkflowRunManifest {
|
|
|
70
70
|
policy?: NormalizedPolicy;
|
|
71
71
|
specialists?: Record<string, WorkflowSpecialistProfile>;
|
|
72
72
|
workspace_binding?: WorkspaceBinding;
|
|
73
|
-
workspace_fallback?:
|
|
73
|
+
workspace_fallback?: WorkspaceCompatibilityExecutionEvidence;
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -307,9 +307,9 @@ function validWorkspaceBinding(binding: WorkspaceBinding | undefined): boolean {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
function validWorkspaceFallback(
|
|
310
|
-
fallback:
|
|
310
|
+
fallback: WorkspaceCompatibilityExecutionEvidence | undefined,
|
|
311
311
|
): boolean {
|
|
312
|
-
return fallback === undefined ||
|
|
312
|
+
return fallback === undefined || isWorkspaceCompatibilityExecutionEvidence(fallback);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
function validSpecialists(
|
|
@@ -425,11 +425,11 @@ export type Spawner = (req: SpawnRequest) => Promise<SpawnResult>;
|
|
|
425
425
|
/** The API surface injected into a workflow script's default export. Explicit
|
|
426
426
|
* injection (no ambient globals): keeps scripts portable and unit-testable. */
|
|
427
427
|
export interface WorkflowContext {
|
|
428
|
-
/** Frozen durable-work assignment for work-linked runs. Standalone
|
|
429
|
-
*
|
|
428
|
+
/** Frozen durable-work assignment for work-linked runs. Standalone workflows
|
|
429
|
+
* have no work context. */
|
|
430
430
|
work?: Readonly<WorkflowWorkContext>;
|
|
431
431
|
/** Frozen attempt identity and, on retry, a bounded synopsis of prior
|
|
432
|
-
* terminal evidence. Absent for standalone
|
|
432
|
+
* terminal evidence. Absent for standalone workflows. */
|
|
433
433
|
attempt?: Readonly<WorkflowAttemptContext>;
|
|
434
434
|
/** Spawn one subagent; resolves to validated JSON (schema) or reply text. */
|
|
435
435
|
agent: (prompt: string, opts?: AgentOpts) => Promise<unknown>;
|
|
@@ -14,9 +14,9 @@ import { appendWorkflowTranscriptEvent, stableDigest, writeWorkspaceRequest } fr
|
|
|
14
14
|
import type {
|
|
15
15
|
WorkspaceAttestation,
|
|
16
16
|
WorkspaceBinding,
|
|
17
|
+
WorkspaceCompatibilityExecutionEvidence,
|
|
17
18
|
WorkspaceIsolation,
|
|
18
19
|
WorkspaceProvider,
|
|
19
|
-
WorkspaceUnsupportedExecutionEvidence,
|
|
20
20
|
} from "./types.ts";
|
|
21
21
|
import { isWorkspaceAttestation, isWorkspaceBinding } from "./validate.ts";
|
|
22
22
|
|
|
@@ -36,7 +36,7 @@ export async function resolveWorkspaceBinding(input: {
|
|
|
36
36
|
workContext: Readonly<WorkflowWorkContext> | undefined;
|
|
37
37
|
attemptContext: Readonly<WorkflowAttemptContext> | undefined;
|
|
38
38
|
policy: NormalizedPolicy | undefined;
|
|
39
|
-
onUnsupportedFallback?: (evidence:
|
|
39
|
+
onUnsupportedFallback?: (evidence: WorkspaceCompatibilityExecutionEvidence) => void;
|
|
40
40
|
}): Promise<WorkspaceBinding | undefined> {
|
|
41
41
|
if (input.isolation === "shared") return undefined;
|
|
42
42
|
if (
|
|
@@ -47,6 +47,13 @@ export async function resolveWorkspaceBinding(input: {
|
|
|
47
47
|
throw new Error(`unsupported workflow isolation ${JSON.stringify(input.isolation)}`);
|
|
48
48
|
}
|
|
49
49
|
if (input.resumeState && !input.resumeState.manifest.execution.workspace_binding) {
|
|
50
|
+
const frozenFallback = input.resumeState.manifest.execution.workspace_fallback;
|
|
51
|
+
if (!frozenFallback) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`workflow run ${input.runId} requested ${input.isolation} isolation without frozen workspace evidence`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
input.onUnsupportedFallback?.(frozenFallback);
|
|
50
57
|
return undefined;
|
|
51
58
|
}
|
|
52
59
|
const capability = input.isolation as WorkspaceIsolation;
|
|
@@ -55,7 +62,7 @@ export async function resolveWorkspaceBinding(input: {
|
|
|
55
62
|
if (input.resumeState?.manifest.execution.workspace_binding) {
|
|
56
63
|
throw new Error("isolated workflow resume requires the frozen workspace provider");
|
|
57
64
|
}
|
|
58
|
-
const fallback:
|
|
65
|
+
const fallback: WorkspaceCompatibilityExecutionEvidence = {
|
|
59
66
|
schema_version: 1,
|
|
60
67
|
run_id: input.runId,
|
|
61
68
|
requested_isolation: capability,
|
|
@@ -163,7 +170,7 @@ export async function resolveWorkspaceBinding(input: {
|
|
|
163
170
|
message: `provider does not support ${capability} isolation`,
|
|
164
171
|
},
|
|
165
172
|
];
|
|
166
|
-
const fallback:
|
|
173
|
+
const fallback: WorkspaceCompatibilityExecutionEvidence = {
|
|
167
174
|
schema_version: 1,
|
|
168
175
|
run_id: input.runId,
|
|
169
176
|
requested_isolation: capability,
|
|
@@ -56,7 +56,6 @@ export type {
|
|
|
56
56
|
RepositoryBinding,
|
|
57
57
|
RepositoryObservation,
|
|
58
58
|
ValidatedFilesystemPath,
|
|
59
|
-
ValidatedFilesystemRoot,
|
|
60
59
|
WorkspaceAllocationRequest,
|
|
61
60
|
WorkspaceAttestation,
|
|
62
61
|
WorkspaceBinding,
|
|
@@ -77,13 +76,11 @@ export type {
|
|
|
77
76
|
WorkspaceOwner,
|
|
78
77
|
WorkspaceProbeInput,
|
|
79
78
|
WorkspaceProbeResult,
|
|
80
|
-
WorkspaceProofLifecycleState,
|
|
81
79
|
WorkspaceProofOutcome,
|
|
82
80
|
WorkspaceProvider,
|
|
83
81
|
WorkspaceProviderCapabilities,
|
|
84
82
|
WorkspaceProviderRef,
|
|
85
83
|
WorkspaceResourceState,
|
|
86
|
-
WorkspaceUnsupportedExecutionEvidence,
|
|
87
84
|
} from "./types.ts";
|
|
88
85
|
export {
|
|
89
86
|
WORKSPACE_BINDING_SCHEMA_VERSION,
|
|
@@ -76,7 +76,6 @@ import { isWorkspaceAttestation } from "./validate.ts";
|
|
|
76
76
|
const PROVIDER_ID = "local-git-worktree";
|
|
77
77
|
const PROVIDER_VERSION = "1";
|
|
78
78
|
const WORKSPACE_PARENT_SEGMENT = ".harnery-workspaces";
|
|
79
|
-
const LEGACY_WORKSPACE_PARENT_SEGMENT = "harnery-workspaces";
|
|
80
79
|
const LOCK_STALE_MS = 5 * 60 * 1_000;
|
|
81
80
|
const OBJECT_FORMAT = /^[a-f0-9]{40,64}$/;
|
|
82
81
|
const REF_FORMAT = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._/-]{0,180}$/;
|
|
@@ -308,7 +307,7 @@ async function allocate(
|
|
|
308
307
|
*/
|
|
309
308
|
function claimedParentSegment(claim: WorkspaceClaim): string {
|
|
310
309
|
const segment = basename(dirname(claim.workspace_root));
|
|
311
|
-
if (segment !== WORKSPACE_PARENT_SEGMENT
|
|
310
|
+
if (segment !== WORKSPACE_PARENT_SEGMENT) {
|
|
312
311
|
throw new Error("claimed workspace path is not under a provider-owned workspace parent");
|
|
313
312
|
}
|
|
314
313
|
return segment;
|
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
statSync,
|
|
10
10
|
} from "node:fs";
|
|
11
11
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
12
|
-
import type { FilesystemIdentity,
|
|
12
|
+
import type { FilesystemIdentity, ValidatedFilesystemPath } from "./types.ts";
|
|
13
13
|
|
|
14
14
|
const WINDOWS_AMBIGUOUS = /[<>:"|?*]|[ .]$/;
|
|
15
15
|
|
|
16
|
-
export type ValidatedRoot =
|
|
16
|
+
export type ValidatedRoot = ValidatedFilesystemPath;
|
|
17
17
|
|
|
18
18
|
export interface OpenContainedDirectory {
|
|
19
19
|
path: string;
|
|
@@ -21,9 +21,6 @@ export interface ValidatedFilesystemPath {
|
|
|
21
21
|
identity: FilesystemIdentity;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/** @deprecated Use ValidatedFilesystemPath. */
|
|
25
|
-
export type ValidatedFilesystemRoot = ValidatedFilesystemPath;
|
|
26
|
-
|
|
27
24
|
export interface WorkspaceProviderRef {
|
|
28
25
|
id: string;
|
|
29
26
|
version: string;
|
|
@@ -184,9 +181,6 @@ export type WorkspaceProofOutcome =
|
|
|
184
181
|
| "blocked"
|
|
185
182
|
| "lost";
|
|
186
183
|
|
|
187
|
-
/** @deprecated Use WorkspaceProofOutcome. */
|
|
188
|
-
export type WorkspaceProofLifecycleState = WorkspaceProofOutcome;
|
|
189
|
-
|
|
190
184
|
export type WorkspaceResourceState = "active" | "preserved_dirty" | "released" | "blocked" | "lost";
|
|
191
185
|
|
|
192
186
|
export type WorkspaceIntegrationState = "none" | "planned" | "applying" | "applied";
|
|
@@ -492,9 +486,6 @@ export interface WorkspaceCompatibilityExecutionEvidence {
|
|
|
492
486
|
receipts: Record<string, never>;
|
|
493
487
|
}
|
|
494
488
|
|
|
495
|
-
/** @deprecated Use WorkspaceCompatibilityExecutionEvidence. */
|
|
496
|
-
export type WorkspaceUnsupportedExecutionEvidence = WorkspaceCompatibilityExecutionEvidence;
|
|
497
|
-
|
|
498
489
|
export type WorkspaceExecutionEvidence =
|
|
499
490
|
| WorkspaceBoundExecutionEvidence
|
|
500
491
|
| WorkspaceCompatibilityExecutionEvidence;
|
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
WorkspaceCompatibilityExecutionEvidence,
|
|
12
12
|
WorkspaceExecutionEvidence,
|
|
13
13
|
WorkspaceOwner,
|
|
14
|
-
WorkspaceProofLifecycleState,
|
|
15
14
|
WorkspaceProofOutcome,
|
|
16
15
|
} from "./types.ts";
|
|
17
16
|
|
|
@@ -376,7 +375,7 @@ function workspaceCwdMatchesBinding(
|
|
|
376
375
|
}
|
|
377
376
|
|
|
378
377
|
function validWorkspaceProofOutcome(
|
|
379
|
-
outcome:
|
|
378
|
+
outcome: WorkspaceProofOutcome,
|
|
380
379
|
attestation: WorkspaceAttestation,
|
|
381
380
|
workflowStatus?: "succeeded" | "failed",
|
|
382
381
|
): boolean {
|
|
@@ -420,9 +419,6 @@ export function isWorkspaceCompatibilityExecutionEvidence(
|
|
|
420
419
|
);
|
|
421
420
|
}
|
|
422
421
|
|
|
423
|
-
/** @deprecated Use isWorkspaceCompatibilityExecutionEvidence. */
|
|
424
|
-
export const isWorkspaceUnsupportedExecutionEvidence = isWorkspaceCompatibilityExecutionEvidence;
|
|
425
|
-
|
|
426
422
|
function sameOwner(left: WorkspaceOwner, right: WorkspaceOwner): boolean {
|
|
427
423
|
return (
|
|
428
424
|
left.kind === right.kind &&
|