@nifrajs/coding-agent 4.0.2 → 5.1.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/LICENSE +21 -0
- package/README.md +22 -2
- package/dist/approvals.d.ts +30 -2
- package/dist/approvals.d.ts.map +1 -1
- package/dist/approvals.js +43 -12
- package/dist/approvals.js.map +1 -1
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +120 -100
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +9 -21
- package/dist/context.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +8 -21
- package/dist/diff.js.map +1 -1
- package/dist/extensions.js +4 -2
- package/dist/extensions.js.map +1 -1
- package/dist/handoffs.d.ts +5 -0
- package/dist/handoffs.d.ts.map +1 -1
- package/dist/handoffs.js +27 -0
- package/dist/handoffs.js.map +1 -1
- package/dist/host.d.ts +15 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +152 -37
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/isolated.js +3 -2
- package/dist/isolated.js.map +1 -1
- package/dist/native.d.ts +29 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +86 -26
- package/dist/native.js.map +1 -1
- package/dist/orchestration/dispatch.d.ts +5 -1
- package/dist/orchestration/dispatch.d.ts.map +1 -1
- package/dist/orchestration/dispatch.js +1 -1
- package/dist/orchestration/dispatch.js.map +1 -1
- package/dist/orchestration/evidence-store.d.ts.map +1 -1
- package/dist/orchestration/evidence-store.js +5 -6
- package/dist/orchestration/evidence-store.js.map +1 -1
- package/dist/orchestration/host.d.ts +14 -0
- package/dist/orchestration/host.d.ts.map +1 -1
- package/dist/orchestration/host.js +26 -0
- package/dist/orchestration/host.js.map +1 -1
- package/dist/orchestration/index.d.ts +1 -1
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/recovery.d.ts +5 -1
- package/dist/orchestration/recovery.d.ts.map +1 -1
- package/dist/orchestration/recovery.js +5 -3
- package/dist/orchestration/recovery.js.map +1 -1
- package/dist/process.d.ts +22 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +75 -1
- package/dist/process.js.map +1 -1
- package/dist/rpc.d.ts +29 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +205 -46
- package/dist/rpc.js.map +1 -1
- package/dist/session-migration.d.ts.map +1 -1
- package/dist/session-migration.js +30 -6
- package/dist/session-migration.js.map +1 -1
- package/dist/sessions.d.ts +13 -2
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +104 -24
- package/dist/sessions.js.map +1 -1
- package/dist/subagents.d.ts.map +1 -1
- package/dist/subagents.js +13 -2
- package/dist/subagents.js.map +1 -1
- package/dist/turn-replay.d.ts +40 -0
- package/dist/turn-replay.d.ts.map +1 -0
- package/dist/turn-replay.js +136 -0
- package/dist/turn-replay.js.map +1 -0
- package/dist/ui.js +84 -30
- package/dist/ui.js.map +1 -1
- package/dist/verification.d.ts +7 -0
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +66 -63
- package/dist/verification.js.map +1 -1
- package/dist/workflows.d.ts +5 -0
- package/dist/workflows.d.ts.map +1 -1
- package/dist/workflows.js +2 -1
- package/dist/workflows.js.map +1 -1
- package/package.json +6 -6
- package/src/approvals.ts +70 -16
- package/src/cli.ts +125 -93
- package/src/context.ts +12 -20
- package/src/diff.ts +11 -23
- package/src/extensions.ts +6 -2
- package/src/handoffs.ts +27 -0
- package/src/host.ts +165 -34
- package/src/index.ts +3 -0
- package/src/isolated.ts +3 -2
- package/src/native.ts +137 -34
- package/src/orchestration/dispatch.ts +6 -2
- package/src/orchestration/evidence-store.ts +5 -6
- package/src/orchestration/host.ts +44 -0
- package/src/orchestration/index.ts +1 -0
- package/src/orchestration/recovery.ts +14 -8
- package/src/process.ts +89 -0
- package/src/rpc.ts +268 -49
- package/src/session-migration.ts +29 -5
- package/src/sessions.ts +115 -29
- package/src/subagents.ts +11 -2
- package/src/turn-replay.ts +151 -0
- package/src/ui.ts +90 -32
- package/src/verification.ts +74 -60
- package/src/workflows.ts +7 -1
package/src/verification.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "node:fs"
|
|
2
|
+
import { isAbsolute, resolve } from "node:path"
|
|
1
3
|
import { publicErrorDetails } from "./errors.ts"
|
|
2
|
-
import {
|
|
4
|
+
import { type BoundedProcessResult, runBoundedProcess } from "./process.ts"
|
|
3
5
|
|
|
4
6
|
export interface VerificationResult {
|
|
5
7
|
readonly name: "check" | "assure" | "test"
|
|
@@ -9,6 +11,8 @@ export interface VerificationResult {
|
|
|
9
11
|
readonly output?: string
|
|
10
12
|
readonly error?: string
|
|
11
13
|
readonly stack?: string
|
|
14
|
+
/** The gate's command was not found or cannot run, so nothing was checked; never a repair task. */
|
|
15
|
+
readonly unavailable?: true
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export interface VerificationRepairTask {
|
|
@@ -27,7 +31,7 @@ export function createVerificationRepairTask(
|
|
|
27
31
|
result: VerificationResult,
|
|
28
32
|
cwd: string,
|
|
29
33
|
): VerificationRepairTask | undefined {
|
|
30
|
-
if (result.ok) return undefined
|
|
34
|
+
if (result.ok || result.unavailable === true) return undefined
|
|
31
35
|
return Object.freeze({
|
|
32
36
|
id: `verification-${result.name}-${Date.now().toString(36)}`,
|
|
33
37
|
verification: result.name,
|
|
@@ -108,56 +112,35 @@ export async function runNifraReview(options: ReviewOptions): Promise<ReviewExec
|
|
|
108
112
|
const args = ["review", "--json"]
|
|
109
113
|
if (options.strict === true) args.push("--strict")
|
|
110
114
|
if (options.diff !== undefined) args.push("--diff", options.diff)
|
|
111
|
-
|
|
115
|
+
const maxOutputBytes = options.maxOutputBytes ?? 1_048_576
|
|
116
|
+
let run: BoundedProcessResult
|
|
112
117
|
try {
|
|
113
|
-
|
|
118
|
+
run = await runBoundedProcess([command, ...commandArgs, ...args], {
|
|
114
119
|
cwd: options.cwd,
|
|
115
|
-
stdin: "ignore",
|
|
116
|
-
stdout: "pipe",
|
|
117
|
-
stderr: "pipe",
|
|
118
120
|
env: safeEnv(options.env),
|
|
121
|
+
timeoutMs: options.timeoutMs ?? 120_000,
|
|
122
|
+
maxStdoutBytes: maxOutputBytes,
|
|
123
|
+
maxStderrBytes: maxOutputBytes,
|
|
119
124
|
})
|
|
120
125
|
} catch {
|
|
121
126
|
return { name: "review", ok: false, status: null, errorCode: "spawn-failed" }
|
|
122
127
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
proc.kill()
|
|
128
|
-
}, timeoutMs)
|
|
128
|
+
const { stdout, stderr, status } = run
|
|
129
|
+
if (run.timedOut) return { name: "review", ok: false, status, errorCode: "timeout" }
|
|
130
|
+
if (stdout.truncated || stderr.truncated)
|
|
131
|
+
return { name: "review", ok: false, status, errorCode: "output-truncated" }
|
|
129
132
|
try {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
proc.exited,
|
|
134
|
-
])
|
|
135
|
-
void stderr
|
|
136
|
-
if (timedOut) return { name: "review", ok: false, status, errorCode: "timeout" }
|
|
137
|
-
if (stdout.truncated || stderr.truncated)
|
|
138
|
-
return { name: "review", ok: false, status, errorCode: "output-truncated" }
|
|
139
|
-
try {
|
|
140
|
-
const raw = JSON.parse(stdout.text) as unknown
|
|
141
|
-
const { parseReviewReport } = await import("@nifrajs/agent-review")
|
|
142
|
-
const report = await parseReviewReport(raw)
|
|
143
|
-
return {
|
|
144
|
-
name: "review",
|
|
145
|
-
ok: status === 0 && report.ok,
|
|
146
|
-
status,
|
|
147
|
-
report,
|
|
148
|
-
}
|
|
149
|
-
} catch {
|
|
150
|
-
return { name: "review", ok: false, status, errorCode: "invalid-report" }
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
133
|
+
const raw = JSON.parse(stdout.text) as unknown
|
|
134
|
+
const { parseReviewReport } = await import("@nifrajs/agent-review")
|
|
135
|
+
const report = await parseReviewReport(raw)
|
|
153
136
|
return {
|
|
154
137
|
name: "review",
|
|
155
|
-
ok:
|
|
156
|
-
status
|
|
157
|
-
|
|
138
|
+
ok: status === 0 && report.ok,
|
|
139
|
+
status,
|
|
140
|
+
report,
|
|
158
141
|
}
|
|
159
|
-
}
|
|
160
|
-
|
|
142
|
+
} catch {
|
|
143
|
+
return { name: "review", ok: false, status, errorCode: "invalid-report" }
|
|
161
144
|
}
|
|
162
145
|
}
|
|
163
146
|
|
|
@@ -173,25 +156,19 @@ export async function runNifraVerification(
|
|
|
173
156
|
const command = options.command ?? (name === "test" ? process.execPath : "nifra")
|
|
174
157
|
const commandArgs = options.commandArgs ?? []
|
|
175
158
|
const args = name === "test" ? ["test"] : [name, "--json"]
|
|
176
|
-
const proc = Bun.spawn([command, ...commandArgs, ...args], {
|
|
177
|
-
cwd: options.cwd,
|
|
178
|
-
stdin: "ignore",
|
|
179
|
-
stdout: "pipe",
|
|
180
|
-
stderr: "pipe",
|
|
181
|
-
env: safeEnv(options.env),
|
|
182
|
-
})
|
|
183
159
|
const timeoutMs = options.timeoutMs ?? 120_000
|
|
184
|
-
let timedOut = false
|
|
185
|
-
const timer = setTimeout(() => {
|
|
186
|
-
timedOut = true
|
|
187
|
-
proc.kill()
|
|
188
|
-
}, timeoutMs)
|
|
189
160
|
try {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
161
|
+
const maxOutputBytes = options.maxOutputBytes ?? 1_048_576
|
|
162
|
+
const { stdout, stderr, status, timedOut } = await runBoundedProcess(
|
|
163
|
+
[command, ...commandArgs, ...args],
|
|
164
|
+
{
|
|
165
|
+
cwd: options.cwd,
|
|
166
|
+
env: safeEnv(options.env),
|
|
167
|
+
timeoutMs,
|
|
168
|
+
maxStdoutBytes: maxOutputBytes,
|
|
169
|
+
maxStderrBytes: maxOutputBytes,
|
|
170
|
+
},
|
|
171
|
+
)
|
|
195
172
|
const output = stdout.text || stderr.text
|
|
196
173
|
let report: unknown
|
|
197
174
|
try {
|
|
@@ -208,6 +185,14 @@ export async function runNifraVerification(
|
|
|
208
185
|
...(timedOut ? { error: `nifra ${name} timed out after ${timeoutMs}ms` } : {}),
|
|
209
186
|
}
|
|
210
187
|
} catch (error) {
|
|
188
|
+
if (commandMissing(error, options.cwd))
|
|
189
|
+
return {
|
|
190
|
+
name,
|
|
191
|
+
ok: false,
|
|
192
|
+
status: null,
|
|
193
|
+
unavailable: true,
|
|
194
|
+
error: `${command} is not installed or cannot run, so ${name} did not run`,
|
|
195
|
+
}
|
|
211
196
|
const details = publicErrorDetails(
|
|
212
197
|
error,
|
|
213
198
|
"verification failed",
|
|
@@ -220,8 +205,37 @@ export async function runNifraVerification(
|
|
|
220
205
|
error: details.message,
|
|
221
206
|
...(details.stack === undefined ? {} : { stack: details.stack }),
|
|
222
207
|
}
|
|
223
|
-
}
|
|
224
|
-
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Whether a gate's command can be found and run from `cwd`, without running the gate. `test` runs
|
|
213
|
+
* on the Bun this agent runs on, so it is always available unless `command` replaces it.
|
|
214
|
+
*/
|
|
215
|
+
export function isVerificationAvailable(
|
|
216
|
+
name: "check" | "assure" | "test",
|
|
217
|
+
options: Pick<VerificationOptions, "cwd" | "command" | "env">,
|
|
218
|
+
): boolean {
|
|
219
|
+
const command = options.command ?? (name === "test" ? process.execPath : "nifra")
|
|
220
|
+
if (!command.includes("/") && !(process.platform === "win32" && command.includes("\\")))
|
|
221
|
+
return Bun.which(command, { PATH: safeEnv(options.env).PATH ?? "" }) !== null
|
|
222
|
+
try {
|
|
223
|
+
accessSync(isAbsolute(command) ? command : resolve(options.cwd, command), constants.X_OK)
|
|
224
|
+
return true
|
|
225
|
+
} catch {
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Bun reports a missing executable and a missing cwd alike (ENOENT), so the cwd decides which.
|
|
231
|
+
function commandMissing(error: unknown, cwd: string): boolean {
|
|
232
|
+
const code =
|
|
233
|
+
typeof error === "object" && error !== null && "code" in error ? error.code : undefined
|
|
234
|
+
if (code !== "ENOENT" && code !== "EACCES") return false
|
|
235
|
+
try {
|
|
236
|
+
return statSync(cwd).isDirectory()
|
|
237
|
+
} catch {
|
|
238
|
+
return false
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
241
|
|
package/src/workflows.ts
CHANGED
|
@@ -58,6 +58,11 @@ export type WorkflowStep =
|
|
|
58
58
|
|
|
59
59
|
export interface WorkflowRunnerOptions {
|
|
60
60
|
readonly signal?: AbortSignal
|
|
61
|
+
/**
|
|
62
|
+
* The map steps read and write through `values` and `set`. Pass one map to several runners to
|
|
63
|
+
* carry values from one run into the next. Default: a fresh map per runner.
|
|
64
|
+
*/
|
|
65
|
+
readonly values?: Map<string, unknown>
|
|
61
66
|
readonly maxSteps?: number
|
|
62
67
|
readonly maxDepth?: number
|
|
63
68
|
readonly onEvent?: (event: WorkflowEvent) => void | PromiseLike<void>
|
|
@@ -89,7 +94,7 @@ export class WorkflowRunner {
|
|
|
89
94
|
private readonly options: Required<Pick<WorkflowRunnerOptions, "maxSteps" | "maxDepth">> &
|
|
90
95
|
WorkflowRunnerOptions
|
|
91
96
|
private readonly signal: AbortSignal
|
|
92
|
-
private readonly values
|
|
97
|
+
private readonly values: Map<string, unknown>
|
|
93
98
|
private readonly completed: string[] = []
|
|
94
99
|
private steps = 0
|
|
95
100
|
|
|
@@ -100,6 +105,7 @@ export class WorkflowRunner {
|
|
|
100
105
|
maxDepth: options.maxDepth ?? 8,
|
|
101
106
|
}
|
|
102
107
|
this.signal = options.signal ?? new AbortController().signal
|
|
108
|
+
this.values = options.values ?? new Map()
|
|
103
109
|
if (!Number.isSafeInteger(this.options.maxSteps) || this.options.maxSteps < 1)
|
|
104
110
|
throw new RangeError("workflow: maxSteps must be positive")
|
|
105
111
|
if (!Number.isSafeInteger(this.options.maxDepth) || this.options.maxDepth < 1)
|