@euqns/nudge-mcp 1.36.0 → 1.38.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/README.md +50 -0
- package/dist/runner-t3-codex-bridge.js +101 -0
- package/dist/runner-t3-codex-bridge.js.map +1 -0
- package/dist/runner-t3-engine.js +243 -0
- package/dist/runner-t3-engine.js.map +1 -0
- package/dist/runner-t3-engine.test.js +121 -0
- package/dist/runner-t3-engine.test.js.map +1 -0
- package/dist/runner-t3-log-viewer.js +44 -0
- package/dist/runner-t3-log-viewer.js.map +1 -0
- package/dist/runner-t3.js +343 -0
- package/dist/runner-t3.js.map +1 -0
- package/dist/runner-t3.test.js +154 -0
- package/dist/runner-t3.test.js.map +1 -0
- package/dist/runner.js +36 -1
- package/dist/runner.js.map +1 -1
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +6 -0
- package/dist/service.test.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -680,6 +680,56 @@ Overrides are read at each job startup; already-running turns retain their
|
|
|
680
680
|
existing settings. Do not repeat generated top-level model/auth keys in the
|
|
681
681
|
TOML overrides. Desktop MCP servers remain isolated unless explicitly enabled.
|
|
682
682
|
|
|
683
|
+
### T3 Code desktop runner threads
|
|
684
|
+
|
|
685
|
+
When T3 Code is running on the runner's machine, new **Codex** jobs on macOS
|
|
686
|
+
and Linux execute through T3 itself. Look for **Nudge · running · Board · task …**
|
|
687
|
+
under the matching checkout's project. Open the thread to see the prompt,
|
|
688
|
+
streaming responses, and tool activity in the conversation pane. Nudge receives
|
|
689
|
+
the final answer and token usage, and updates the title with the job status.
|
|
690
|
+
|
|
691
|
+
Nudge resumes and steers these jobs in the same T3 conversation. Cancelling or
|
|
692
|
+
steering a job in Nudge interrupts its T3 turn. Keep using Nudge for job questions,
|
|
693
|
+
review, and scheduling; T3's composer also operates the underlying conversation,
|
|
694
|
+
but does not create or reopen a Nudge job. Completed conversations remain
|
|
695
|
+
available for manual follow-ups in T3.
|
|
696
|
+
|
|
697
|
+
The Codex bridge preserves the runner's Nudge MCP tools and sandbox: intake and
|
|
698
|
+
question turns stay read-only, ordinary tasks use workspace-write, and jobs that
|
|
699
|
+
explicitly need repository/network actions retain their existing access. T3 gets
|
|
700
|
+
a separate Codex provider instance per conversation using Nudge's isolated Codex
|
|
701
|
+
home. Job credentials live in an owner-only context under `~/.nudge/t3-native`,
|
|
702
|
+
are removed when execution ends, and are never put in T3 titles or prompts.
|
|
703
|
+
|
|
704
|
+
Claude, Cursor, Windows, and conversations originally started by an external CLI
|
|
705
|
+
continue using the terminal log view. Their previous conversation IDs are kept
|
|
706
|
+
with the original engine. Existing log-only threads and jobs already running
|
|
707
|
+
are not converted or backfilled.
|
|
708
|
+
|
|
709
|
+
Discovery reads `~/.t3/userdata/server-runtime.json` (or the `dev` variant) and
|
|
710
|
+
only connects over loopback. On macOS it finds T3 Code or T3 Code (Nightly) in
|
|
711
|
+
`/Applications` or `~/Applications`; other installs need a `t3` executable on
|
|
712
|
+
the runner's `PATH`. The installed CLI issues an expiring local session and
|
|
713
|
+
revokes it afterward (24 hours maximum for native execution, five minutes for
|
|
714
|
+
status updates). T3 API credentials are held in memory. Windows gets the fallback
|
|
715
|
+
thread and status updates, with its log saved locally.
|
|
716
|
+
|
|
717
|
+
| Runner environment variable | Purpose |
|
|
718
|
+
| --- | --- |
|
|
719
|
+
| `NUDGE_T3=0` | Disable automatic T3 threads. |
|
|
720
|
+
| `T3CODE_HOME=/path/to/.t3` | Find a nonstandard T3 data directory. |
|
|
721
|
+
| `NUDGE_T3_CLI=/path/to/t3` | Use a specific installed T3 CLI or executable wrapper. |
|
|
722
|
+
|
|
723
|
+
T3 must be open before the job starts. Missing T3 is silently skipped for new
|
|
724
|
+
jobs. A native conversation must resume in its original T3 environment; if that
|
|
725
|
+
environment is unavailable, the job reports an error. Once a native turn has
|
|
726
|
+
been submitted, the runner never falls back to a second CLI agent. Verified
|
|
727
|
+
against T3 Code Nightly `0.0.41-nightly.20260908.1400`; native execution requires
|
|
728
|
+
its HTTP orchestration and provider settings RPC interfaces. Fallback JSONL
|
|
729
|
+
logs are owner-only under `~/.nudge/t3-logs` and
|
|
730
|
+
retain up to 8 MiB of live output per run, followed by its final result.
|
|
731
|
+
They remain available after completion and can be removed when no longer needed.
|
|
732
|
+
|
|
683
733
|
### Runner output files
|
|
684
734
|
|
|
685
735
|
A coding turn can optionally write the JSON manifest at the absolute path supplied
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// T3 owns the app-server connection. This transparent stdio bridge applies the
|
|
2
|
+
// runner's MCP identity and sandbox to that connection, preserving native events.
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { bundledCodexCommand, companionCodexConfig, subscriptionEnvironment } from "./codex-companion.js";
|
|
8
|
+
import { addUsage, emptyUsage } from "./engines/types.js";
|
|
9
|
+
export function applyRunnerPolicy(message, context) {
|
|
10
|
+
if (!["thread/start", "thread/resume", "thread/fork", "turn/start"].includes(message.method))
|
|
11
|
+
return message;
|
|
12
|
+
const params = { ...message.params, approvalPolicy: "never", approvalsReviewer: "user" };
|
|
13
|
+
if (message.method === "turn/start") {
|
|
14
|
+
params.sandboxPolicy = { type: context.sandbox === "read-only" ? "readOnly"
|
|
15
|
+
: context.sandbox === "workspace-write" ? "workspaceWrite" : "dangerFullAccess" };
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const config = companionCodexConfig(context.env);
|
|
19
|
+
params.sandbox = context.sandbox;
|
|
20
|
+
params.config = { ...params.config, model_provider: config.model_provider, "mcp_servers.nudge": config.mcp_servers.nudge };
|
|
21
|
+
}
|
|
22
|
+
return { ...message, params };
|
|
23
|
+
}
|
|
24
|
+
export function usageDelta(previous, current, last) {
|
|
25
|
+
const keys = ["inputTokens", "outputTokens", "cachedInputTokens", "reasoningTokens"];
|
|
26
|
+
if (!previous || keys.some((key) => (current[key] ?? 0) < (previous[key] ?? 0)))
|
|
27
|
+
return last;
|
|
28
|
+
return Object.fromEntries(keys.map((key) => [key, (current[key] ?? 0) - (previous[key] ?? 0)]));
|
|
29
|
+
}
|
|
30
|
+
export async function runBridge() {
|
|
31
|
+
let context;
|
|
32
|
+
if (process.env.NUDGE_T3_CONTEXT) {
|
|
33
|
+
try {
|
|
34
|
+
context = JSON.parse(await readFile(process.env.NUDGE_T3_CONTEXT, "utf8"));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// A completed job removes its identity. Manual T3 follow-ups still work,
|
|
38
|
+
// using the normal T3 policy and no credentials from the finished job.
|
|
39
|
+
if (error.code !== "ENOENT")
|
|
40
|
+
throw new Error("Cannot read Nudge T3 job context");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const invocation = bundledCodexCommand(...process.argv.slice(2));
|
|
44
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
45
|
+
env: subscriptionEnvironment(process.env), stdio: ["pipe", "pipe", "inherit"],
|
|
46
|
+
});
|
|
47
|
+
const input = createInterface({ input: process.stdin });
|
|
48
|
+
input.on("line", (line) => {
|
|
49
|
+
try {
|
|
50
|
+
child.stdin.write(context ? JSON.stringify(applyRunnerPolicy(JSON.parse(line), context)) + "\n" : line + "\n");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
child.kill();
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
input.on("close", () => child.stdin.end());
|
|
58
|
+
child.stdin.on("error", () => undefined);
|
|
59
|
+
// Keep complete native notifications, including tool calls and assistant deltas.
|
|
60
|
+
// Record usage separately because the public thread read model omits it.
|
|
61
|
+
const output = createInterface({ input: child.stdout });
|
|
62
|
+
let usageWrite = Promise.resolve();
|
|
63
|
+
const baselineByThread = new Map();
|
|
64
|
+
let runUsage = emptyUsage();
|
|
65
|
+
output.on("line", (line) => {
|
|
66
|
+
process.stdout.write(line + "\n");
|
|
67
|
+
if (!context)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
const event = JSON.parse(line);
|
|
71
|
+
if (event.method !== "thread/tokenUsage/updated")
|
|
72
|
+
return;
|
|
73
|
+
const last = event.params?.tokenUsage?.last;
|
|
74
|
+
if (!last || typeof event.params.turnId !== "string")
|
|
75
|
+
return;
|
|
76
|
+
const total = event.params.tokenUsage.total;
|
|
77
|
+
const normalize = (value) => ({
|
|
78
|
+
inputTokens: value.inputTokens ?? 0, outputTokens: value.outputTokens ?? 0,
|
|
79
|
+
cachedInputTokens: value.cachedInputTokens ?? 0, reasoningTokens: value.reasoningOutputTokens ?? 0,
|
|
80
|
+
});
|
|
81
|
+
const current = normalize(total);
|
|
82
|
+
runUsage = addUsage(runUsage, usageDelta(baselineByThread.get(event.params.threadId), current, normalize(last)));
|
|
83
|
+
baselineByThread.set(event.params.threadId, current);
|
|
84
|
+
const target = context.usagePath;
|
|
85
|
+
const serialized = JSON.stringify(runUsage);
|
|
86
|
+
usageWrite = usageWrite.then(() => writeFile(target, serialized, { mode: 0o600 })).catch(() => undefined);
|
|
87
|
+
}
|
|
88
|
+
catch { /* The provider stream is forwarded unchanged. */ }
|
|
89
|
+
});
|
|
90
|
+
for (const signal of ["SIGINT", "SIGTERM"])
|
|
91
|
+
process.on(signal, () => child.kill(signal));
|
|
92
|
+
await new Promise((resolve, reject) => {
|
|
93
|
+
child.once("error", () => reject(new Error("Could not start Codex for T3")));
|
|
94
|
+
child.once("close", (code) => { process.exitCode = code ?? 1; input.close(); resolve(); });
|
|
95
|
+
});
|
|
96
|
+
await usageWrite;
|
|
97
|
+
}
|
|
98
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
99
|
+
runBridge().catch(() => { process.stderr.write("Nudge T3 Codex bridge failed\n"); process.exitCode = 1; });
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=runner-t3-codex-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-t3-codex-bridge.js","sourceRoot":"","sources":["../src/runner-t3-codex-bridge.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAClF,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAoB,MAAM,oBAAoB,CAAC;AAQ5E,MAAM,UAAU,iBAAiB,CAAC,OAA4B,EAAE,OAAsB;IACpF,IAAI,CAAC,CAAC,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC;IAC7G,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IACzF,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACpC,MAAM,CAAC,aAAa,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,UAAU;gBACzE,CAAC,CAAC,OAAO,CAAC,OAAO,KAAK,iBAAiB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACtF,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QACjC,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,mBAAmB,EAAE,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC7H,CAAC;IACD,OAAO,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAiC,EAAE,OAAoB,EAAE,IAAiB;IACnG,MAAM,IAAI,GAAG,CAAC,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,iBAAiB,CAAU,CAAC;IAC9F,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7F,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAgB,CAAC;AACjH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,OAAkC,CAAC;IACvC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,IAAI,CAAC;YAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;QAAC,CAAC;QACnF,OAAO,KAAK,EAAE,CAAC;YACb,yEAAyE;YACzE,uEAAuE;YACvE,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC9G,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,mBAAmB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE;QACvD,GAAG,EAAE,uBAAuB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC;KAC9E,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACxD,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACxB,IAAI,CAAC;YAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAAC,CAAC;QACvH,MAAM,CAAC;YAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzC,iFAAiF;IACjF,yEAAyE;IACzE,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,IAAI,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IACnC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAuB,CAAC;IACxD,IAAI,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,2BAA2B;gBAAE,OAAO;YACzD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC;YAC5C,IAAI,CAAC,IAAI,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,QAAQ;gBAAE,OAAO;YAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC;YAC5C,MAAM,SAAS,GAAG,CAAC,KAA6B,EAAe,EAAE,CAAC,CAAC;gBACjE,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,CAAC;gBAC1E,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,CAAC,EAAE,eAAe,EAAE,KAAK,CAAC,qBAAqB,IAAI,CAAC;aACnG,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACjH,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;YACjC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAC5C,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5G,CAAC;QAAC,MAAM,CAAC,CAAC,iDAAiD,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU;QAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAClG,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC;QAC7E,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IACH,MAAM,UAAU,CAAC;AACnB,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACvD,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7G,CAAC"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { acquireInstallationLock } from "./runner-installation.js";
|
|
9
|
+
import { codexSandboxMode } from "./engines/codex.js";
|
|
10
|
+
import { ensureCodexShadowHome } from "./engines/isolation.js";
|
|
11
|
+
import { estimateCostUsd } from "./engines/pricing.js";
|
|
12
|
+
import { emptyUsage } from "./engines/types.js";
|
|
13
|
+
import { discoverT3, ensureT3JobThread, shellQuote, t3JobThreadId, t3JobTitle, withT3Client } from "./runner-t3.js";
|
|
14
|
+
const DIRECTORY = path.join(os.homedir(), ".nudge", "t3-native");
|
|
15
|
+
const PREFIX = "t3:";
|
|
16
|
+
export function t3ResumeId(environmentId, threadId) {
|
|
17
|
+
return `${PREFIX}${encodeURIComponent(environmentId)}:${encodeURIComponent(threadId)}`;
|
|
18
|
+
}
|
|
19
|
+
export function parseT3ResumeId(value) {
|
|
20
|
+
if (!value?.startsWith(PREFIX))
|
|
21
|
+
return null;
|
|
22
|
+
const parts = value.split(":");
|
|
23
|
+
if (parts.length !== 3 || !parts[1] || !parts[2])
|
|
24
|
+
throw new Error("Invalid T3 conversation reference");
|
|
25
|
+
const threadId = decodeURIComponent(parts[2]);
|
|
26
|
+
if (!/^nudge-[a-f0-9]{32}$/.test(threadId))
|
|
27
|
+
throw new Error("Invalid T3 runner thread ID");
|
|
28
|
+
return { environmentId: decodeURIComponent(parts[1]), threadId };
|
|
29
|
+
}
|
|
30
|
+
// server.updateSettings replaces the provider map. Serialize Nudge's read/merge/
|
|
31
|
+
// write across runner processes and preserve every unrelated (redacted) entry.
|
|
32
|
+
async function settingsLock(use) {
|
|
33
|
+
await mkdir(DIRECTORY, { recursive: true, mode: 0o700 });
|
|
34
|
+
const release = await acquireInstallationLock(path.join(DIRECTORY, "settings"), { timeoutMs: 15_000, retryMs: 100 });
|
|
35
|
+
try {
|
|
36
|
+
return await use();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await release();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function configureProvider(client, threadId, contextPath) {
|
|
43
|
+
const home = await ensureCodexShadowHome();
|
|
44
|
+
let bridge = fileURLToPath(new URL("./runner-t3-codex-bridge.js", import.meta.url));
|
|
45
|
+
const args = [];
|
|
46
|
+
try {
|
|
47
|
+
await access(bridge);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
bridge = fileURLToPath(new URL("./runner-t3-codex-bridge.ts", import.meta.url));
|
|
51
|
+
args.push("--import", createRequire(import.meta.url).resolve("tsx"));
|
|
52
|
+
}
|
|
53
|
+
const executable = path.join(DIRECTORY, "codex");
|
|
54
|
+
await writeFile(executable, `#!/bin/sh\nexec ${[process.execPath, ...args, bridge].map(shellQuote).join(" ")} "$@"\n`, { mode: 0o700 });
|
|
55
|
+
const instanceId = `nudge-${threadId}`;
|
|
56
|
+
await settingsLock(async () => {
|
|
57
|
+
const settings = await client.rpc("server.getSettings");
|
|
58
|
+
const instance = {
|
|
59
|
+
driver: "codex", displayName: "Nudge runner", enabled: true,
|
|
60
|
+
environment: [{ name: "NUDGE_T3_CONTEXT", value: contextPath, sensitive: false }],
|
|
61
|
+
config: { binaryPath: executable, homePath: home.path, shadowHomePath: "", launchArgs: "", customModels: [], enabled: true },
|
|
62
|
+
};
|
|
63
|
+
if (JSON.stringify(settings.providerInstances?.[instanceId]) !== JSON.stringify(instance)) {
|
|
64
|
+
await client.rpc("server.updateSettings", { patch: { providerInstances: { ...settings.providerInstances, [instanceId]: instance } } });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return instanceId;
|
|
68
|
+
}
|
|
69
|
+
export async function runT3Turn(client, threadId, modelSelection, options, pollMs = 1_000) {
|
|
70
|
+
const route = `/api/orchestration/threads/${encodeURIComponent(threadId)}`;
|
|
71
|
+
const detail = async () => (await client.request(route)).thread;
|
|
72
|
+
const before = await detail();
|
|
73
|
+
const previousTurnId = before.latestTurn?.turnId;
|
|
74
|
+
const commandId = randomUUID();
|
|
75
|
+
const messageId = randomUUID();
|
|
76
|
+
const createdAt = new Date().toISOString();
|
|
77
|
+
let dispatched = false;
|
|
78
|
+
let turnId;
|
|
79
|
+
const seenMessages = new Map();
|
|
80
|
+
const seenActivities = new Set((before.activities ?? []).map((a) => a.id));
|
|
81
|
+
let lastContact = Date.now();
|
|
82
|
+
let dispatchError;
|
|
83
|
+
try {
|
|
84
|
+
options.signal.throwIfAborted();
|
|
85
|
+
if (before.latestTurn?.state === "running") {
|
|
86
|
+
// Recover an in-flight turn after a runner restart. Never launch another
|
|
87
|
+
// agent alongside work already owned by this job's native thread.
|
|
88
|
+
turnId = previousTurnId;
|
|
89
|
+
dispatched = true;
|
|
90
|
+
options.log("event", "Reattached to the running T3 Code turn.");
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
dispatched = true; // A timed-out POST may already have committed.
|
|
94
|
+
await client.dispatch({
|
|
95
|
+
type: "thread.turn.start", commandId, threadId,
|
|
96
|
+
message: { messageId, role: "user", text: options.prompt, attachments: [] },
|
|
97
|
+
modelSelection, runtimeMode: runtimeMode(options), interactionMode: "default", createdAt,
|
|
98
|
+
}).catch((error) => { dispatchError = error; });
|
|
99
|
+
}
|
|
100
|
+
for (;;) {
|
|
101
|
+
options.signal.throwIfAborted();
|
|
102
|
+
let thread;
|
|
103
|
+
try {
|
|
104
|
+
thread = await detail();
|
|
105
|
+
lastContact = Date.now();
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
if (Date.now() - lastContact > 30_000)
|
|
109
|
+
throw new Error("Lost connection to T3 Code. The conversation remains in T3; no CLI fallback was started.");
|
|
110
|
+
await delay(pollMs, undefined, { signal: options.signal });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const turn = thread.latestTurn;
|
|
114
|
+
if (!turnId && turn && turn.turnId !== previousTurnId)
|
|
115
|
+
turnId = turn.turnId;
|
|
116
|
+
// The public projection may take a moment to reflect accepted commands.
|
|
117
|
+
if (!turnId) {
|
|
118
|
+
if (thread.session?.status === "error" && thread.session.updatedAt >= createdAt)
|
|
119
|
+
throw new Error(thread.session.lastError || "T3 could not start the provider");
|
|
120
|
+
if (Date.now() - Date.parse(createdAt) > 30_000)
|
|
121
|
+
throw new Error(dispatchError
|
|
122
|
+
? "T3 did not confirm the turn start. Inspect the thread before retrying."
|
|
123
|
+
: "T3 did not start the provider within 30 seconds");
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (turn && turn.turnId !== turnId)
|
|
127
|
+
throw new Error("The T3 conversation advanced to another turn. Inspect its result before retrying the job.");
|
|
128
|
+
for (const activity of thread.activities ?? []) {
|
|
129
|
+
if (seenActivities.has(activity.id))
|
|
130
|
+
continue;
|
|
131
|
+
seenActivities.add(activity.id);
|
|
132
|
+
if (activity.summary)
|
|
133
|
+
options.log("event", activity.summary);
|
|
134
|
+
}
|
|
135
|
+
for (const message of thread.messages ?? []) {
|
|
136
|
+
if (message.role !== "assistant" || message.turnId !== turnId || !message.text)
|
|
137
|
+
continue;
|
|
138
|
+
const previous = seenMessages.get(message.id) ?? "";
|
|
139
|
+
if (message.text !== previous) {
|
|
140
|
+
options.log("stdout", message.text.startsWith(previous) ? message.text.slice(previous.length) : message.text);
|
|
141
|
+
seenMessages.set(message.id, message.text);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (turn?.turnId === turnId && turn.state !== "running") {
|
|
145
|
+
const messages = (thread.messages ?? []).filter((m) => m.role === "assistant" && m.turnId === turnId);
|
|
146
|
+
const summary = messages.find((m) => m.id === turn.assistantMessageId)?.text ?? messages.at(-1)?.text ?? "";
|
|
147
|
+
return {
|
|
148
|
+
summary, usage: emptyUsage(), costBasis: "subscription", resolvedModel: options.model ?? undefined,
|
|
149
|
+
...(turn.state !== "completed" ? { error: thread.session?.lastError || `T3 turn ${turn.state}` } : {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
await delay(pollMs, undefined, { signal: options.signal });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if (dispatched) {
|
|
158
|
+
// Also stop a completed session: the next resume starts with fresh job
|
|
159
|
+
// credentials and cannot retain a stale runner token or sandbox policy.
|
|
160
|
+
if (options.signal.aborted || dispatchError) {
|
|
161
|
+
await client.dispatch({ type: "thread.turn.interrupt", threadId, ...(turnId ? { turnId } : {}), createdAt: new Date().toISOString() }).catch(() => undefined);
|
|
162
|
+
}
|
|
163
|
+
await client.dispatch({ type: "thread.session.stop", threadId, createdAt: new Date().toISOString() })
|
|
164
|
+
.catch(() => options.log("event", "Could not stop the T3 session. Check its thread before starting another run."));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function runtimeMode(options) {
|
|
169
|
+
return options.readOnly ? "approval-required" : codexSandboxMode(options) === "danger-full-access" ? "full-access" : "auto";
|
|
170
|
+
}
|
|
171
|
+
export async function createT3Engine(fallback, job, previousThreadId, warn) {
|
|
172
|
+
const resumed = parseT3ResumeId(previousThreadId);
|
|
173
|
+
if (resumed && (fallback.id !== "codex" || process.platform === "win32")) {
|
|
174
|
+
throw new Error("This T3 conversation requires the Codex runner on macOS or Linux");
|
|
175
|
+
}
|
|
176
|
+
// Raw CLI session IDs are not T3 IDs. Preserve their history via the original
|
|
177
|
+
// engine; newly started native conversations resume through T3 from then on.
|
|
178
|
+
if (!resumed && (fallback.id !== "codex" || previousThreadId || process.platform === "win32"))
|
|
179
|
+
return null;
|
|
180
|
+
let connection;
|
|
181
|
+
try {
|
|
182
|
+
connection = await discoverT3();
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (resumed)
|
|
186
|
+
throw error;
|
|
187
|
+
warn("T3 Code is unavailable; using the local runner.");
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (!connection) {
|
|
191
|
+
if (resumed)
|
|
192
|
+
throw new Error("Open the local T3 Code app to resume this job's conversation");
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (resumed && resumed.environmentId !== connection.environmentId)
|
|
196
|
+
throw new Error("This job belongs to a different T3 Code environment");
|
|
197
|
+
const threadId = resumed?.threadId ?? t3JobThreadId(job.deployment, job.id);
|
|
198
|
+
const reference = t3ResumeId(connection.environmentId, threadId);
|
|
199
|
+
const view = {
|
|
200
|
+
threadId, logPath: "", log() { },
|
|
201
|
+
async finish(status) {
|
|
202
|
+
await withT3Client(connection, (client) => client.dispatch({ type: "thread.meta.update", threadId, title: t3JobTitle(job, status) }))
|
|
203
|
+
.catch(() => warn("T3 thread status could not be updated."));
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const engine = {
|
|
207
|
+
id: fallback.id, displayName: `${fallback.displayName} in T3 Code`, detect: () => fallback.detect(),
|
|
208
|
+
execute: (options) => withT3Client(connection, async (client) => {
|
|
209
|
+
options.signal.throwIfAborted();
|
|
210
|
+
await mkdir(DIRECTORY, { recursive: true, mode: 0o700 });
|
|
211
|
+
// Only opaque paths go into T3 settings. Runner credentials remain in an
|
|
212
|
+
// owner-only temporary context and never appear in titles or prompts.
|
|
213
|
+
const contextPath = path.join(DIRECTORY, `${threadId}.json`);
|
|
214
|
+
const usagePath = path.join(DIRECTORY, `${threadId}.usage.json`);
|
|
215
|
+
if (resumed)
|
|
216
|
+
await client.request(`/api/orchestration/threads/${encodeURIComponent(threadId)}`);
|
|
217
|
+
const instanceId = await configureProvider(client, threadId, contextPath);
|
|
218
|
+
const modelSelection = { instanceId, model: options.model ?? job.model,
|
|
219
|
+
...(options.effort ? { options: [{ id: "reasoningEffort", value: options.effort }] } : {}) };
|
|
220
|
+
await ensureT3JobThread(client, job, { threadId, modelSelection, runtimeMode: runtimeMode(options) });
|
|
221
|
+
options.onThreadId?.(reference);
|
|
222
|
+
options.log("event", "Running in T3 Code. Open the Nudge thread to see messages and tool activity.");
|
|
223
|
+
await writeFile(contextPath, JSON.stringify({ env: options.env, sandbox: codexSandboxMode(options), usagePath }), { mode: 0o600 });
|
|
224
|
+
await rm(usagePath, { force: true });
|
|
225
|
+
try {
|
|
226
|
+
const result = await runT3Turn(client, threadId, modelSelection, options);
|
|
227
|
+
let usage = emptyUsage();
|
|
228
|
+
try {
|
|
229
|
+
usage = JSON.parse(await readFile(usagePath, "utf8"));
|
|
230
|
+
}
|
|
231
|
+
catch { /* Provider may fail before reporting usage. */ }
|
|
232
|
+
const costUsd = estimateCostUsd(options.model ?? job.model, usage);
|
|
233
|
+
return { ...result, threadId: reference, usage, ...(costUsd != null ? { costUsd, costBasis: "estimated" } : {}) };
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
await rm(contextPath, { force: true });
|
|
237
|
+
await rm(usagePath, { force: true });
|
|
238
|
+
}
|
|
239
|
+
}, "24h"),
|
|
240
|
+
};
|
|
241
|
+
return { engine, view };
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=runner-t3-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-t3-engine.js","sourceRoot":"","sources":["../src/runner-t3-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAqE,MAAM,oBAAoB,CAAC;AACnH,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAY,aAAa,EAAE,UAAU,EAAE,YAAY,EAA8B,MAAM,gBAAgB,CAAC;AAG1J,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AACjE,MAAM,MAAM,GAAG,KAAK,CAAC;AAErB,MAAM,UAAU,UAAU,CAAC,aAAqB,EAAE,QAAgB;IAChE,OAAO,GAAG,MAAM,GAAG,kBAAkB,CAAC,aAAa,CAAC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAqB;IACnD,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvG,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAC3F,OAAO,EAAE,aAAa,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;AACnE,CAAC;AAED,iFAAiF;AACjF,+EAA+E;AAC/E,KAAK,UAAU,YAAY,CAAI,GAAqB;IAClD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IACrH,IAAI,CAAC;QAAC,OAAO,MAAM,GAAG,EAAE,CAAC;IAAC,CAAC;YACnB,CAAC;QAAC,MAAM,OAAO,EAAE,CAAC;IAAC,CAAC;AAC9B,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,MAAgB,EAAE,QAAgB,EAAE,WAAmB;IACtF,MAAM,IAAI,GAAG,MAAM,qBAAqB,EAAE,CAAC;IAC3C,IAAI,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACpF,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,CAAC;QAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QACnC,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,6BAA6B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,SAAS,CAAC,UAAU,EAAE,mBAAmB,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxI,MAAM,UAAU,GAAG,SAAS,QAAQ,EAAE,CAAC;IACvC,MAAM,YAAY,CAAC,KAAK,IAAI,EAAE;QAC5B,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG;YACf,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI;YAC3D,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;YACjF,MAAM,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SAC7H,CAAC;QACF,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1F,MAAM,MAAM,CAAC,GAAG,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,EAAE,iBAAiB,EAAE,EAAE,GAAG,QAAQ,CAAC,iBAAiB,EAAE,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QACzI,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAgB,EAAE,QAAgB,EAAE,cAAoB,EAAE,OAAmB,EAC3G,MAAM,GAAG,KAAK;IACd,MAAM,KAAK,GAAG,8BAA8B,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC3E,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,MAAc,CAAC;IACxE,MAAM,MAAM,GAAG,MAAM,MAAM,EAAE,CAAC;IAC9B,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC;IACjD,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,MAA0B,CAAC;IAC/B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,MAAM,cAAc,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzF,IAAI,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,aAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,UAAU,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAC3C,yEAAyE;YACzE,kEAAkE;YAClE,MAAM,GAAG,cAAc,CAAC;YACxB,UAAU,GAAG,IAAI,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,yCAAyC,CAAC,CAAC;QAClE,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,IAAI,CAAC,CAAC,+CAA+C;YAClE,MAAM,MAAM,CAAC,QAAQ,CAAC;gBACpB,IAAI,EAAE,mBAAmB,EAAE,SAAS,EAAE,QAAQ;gBAC9C,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE;gBAC3E,cAAc,EAAE,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS;aACzF,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,GAAG,aAAa,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,CAAC;QACD,SAAS,CAAC;YACR,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAChC,IAAI,MAAY,CAAC;YACjB,IAAI,CAAC;gBAAC,MAAM,GAAG,MAAM,MAAM,EAAE,CAAC;gBAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,CAAC;YAC1D,MAAM,CAAC;gBACL,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,GAAG,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,0FAA0F,CAAC,CAAC;gBACnJ,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC3D,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;YAC/B,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,cAAc;gBAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC5E,wEAAwE;YACxE,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,MAAM,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,SAAS;oBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,iCAAiC,CAAC,CAAC;gBAChK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,aAAa;wBAC5E,CAAC,CAAC,wEAAwE;wBAC1E,CAAC,CAAC,iDAAiD,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,2FAA2F,CAAC,CAAC;gBACjJ,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;oBAC/C,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAAE,SAAS;oBAC9C,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;oBAChC,IAAI,QAAQ,CAAC,OAAO;wBAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC/D,CAAC;gBACD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;oBAC5C,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI;wBAAE,SAAS;oBACzF,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;oBACpD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;wBAC9B,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;wBAC9G,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;oBAC7C,CAAC;gBACH,CAAC;gBACD,IAAI,IAAI,EAAE,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBACxD,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAO,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;oBAC5G,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,kBAAkB,CAAC,EAAE,IAAI,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;oBAClH,OAAO;wBACL,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS;wBAClG,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,IAAI,WAAW,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBACvG,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,UAAU,EAAE,CAAC;YACf,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,aAAa,EAAE,CAAC;gBAC5C,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAChK,CAAC;YACD,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;iBAClG,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,8EAA8E,CAAC,CAAC,CAAC;QACvH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,OAAmB;IACtC,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,oBAAoB,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9H,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,GAAU,EAAE,gBAA+B,EAChG,IAA+B;IAC/B,MAAM,OAAO,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAClD,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IACtF,CAAC;IACD,8EAA8E;IAC9E,6EAA6E;IAC7E,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,OAAO,IAAI,gBAAgB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3G,IAAI,UAAU,CAAC;IACf,IAAI,CAAC;QAAC,UAAU,GAAG,MAAM,UAAU,EAAE,CAAC;IAAC,CAAC;IACxC,OAAO,KAAK,EAAE,CAAC;QAAC,IAAI,OAAO;YAAE,MAAM,KAAK,CAAC;QAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IACjH,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,IAAI,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC7F,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,IAAI,OAAO,CAAC,aAAa,KAAK,UAAU,CAAC,aAAa;QAAE,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC1I,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IACjE,MAAM,IAAI,GAAc;QACtB,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,KAAI,CAAC;QAC/B,KAAK,CAAC,MAAM,CAAC,MAAM;YACjB,MAAM,YAAY,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;iBAClI,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC,CAAC;QACjE,CAAC;KACF,CAAC;IACF,MAAM,MAAM,GAAW;QACrB,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC,WAAW,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE;QACnG,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC9D,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAChC,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACzD,yEAAyE;YACzE,sEAAsE;YACtE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,OAAO,CAAC,CAAC;YAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,aAAa,CAAC,CAAC;YACjE,IAAI,OAAO;gBAAE,MAAM,MAAM,CAAC,OAAO,CAAC,8BAA8B,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAChG,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC1E,MAAM,cAAc,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK;gBACpE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/F,MAAM,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACtG,OAAO,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,8EAA8E,CAAC,CAAC;YACrG,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACnI,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;gBAC1E,IAAI,KAAK,GAAG,UAAU,EAAE,CAAC;gBACzB,IAAI,CAAC;oBAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAgB,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,+CAA+C,CAAC,CAAC;gBACvI,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBACnE,OAAO,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAoB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7H,CAAC;oBAAS,CAAC;gBACT,MAAM,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvC,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;QACH,CAAC,EAAE,KAAK,CAAC;KACV,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { applyRunnerPolicy, usageDelta } from "./runner-t3-codex-bridge.js";
|
|
4
|
+
import { createT3Engine, parseT3ResumeId, runT3Turn, t3ResumeId } from "./runner-t3-engine.js";
|
|
5
|
+
import { T3Client, t3JobThreadId } from "./runner-t3.js";
|
|
6
|
+
const options = () => ({ cwd: "/tmp", prompt: "New work", readOnly: true, signal: new AbortController().signal, log() { } });
|
|
7
|
+
const context = { env: { NUDGE_RUNNER_JOB: "job-a", NUDGE_RUNNER_TOKEN: "private-token" }, sandbox: "read-only", usagePath: "/tmp/unused" };
|
|
8
|
+
class FakeT3 extends T3Client {
|
|
9
|
+
commands = [];
|
|
10
|
+
snapshots;
|
|
11
|
+
commitThenTimeout = false;
|
|
12
|
+
afterRead;
|
|
13
|
+
constructor(snapshots) { super("http://127.0.0.1", "test"); this.snapshots = snapshots; }
|
|
14
|
+
async request() {
|
|
15
|
+
const thread = this.snapshots.length > 1 ? this.snapshots.shift() : this.snapshots[0];
|
|
16
|
+
this.afterRead?.();
|
|
17
|
+
return { thread };
|
|
18
|
+
}
|
|
19
|
+
async dispatch(command) {
|
|
20
|
+
this.commands.push(command);
|
|
21
|
+
if (this.commitThenTimeout && command.type === "thread.turn.start")
|
|
22
|
+
throw new Error("timeout after commit");
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const previous = { latestTurn: { turnId: "old", state: "completed" }, messages: [{ id: "old-answer", role: "assistant", turnId: "old", text: "Old answer" }] };
|
|
27
|
+
const completed = { latestTurn: { turnId: "new", state: "completed", assistantMessageId: "final" }, messages: [
|
|
28
|
+
...previous.messages,
|
|
29
|
+
{ id: "progress", role: "assistant", turnId: "new", text: "Working" },
|
|
30
|
+
{ id: "final", role: "assistant", turnId: "new", text: "Finished new work" },
|
|
31
|
+
] };
|
|
32
|
+
test("native execution submits a real user message and returns only the new turn's final answer", async () => {
|
|
33
|
+
const client = new FakeT3([previous, completed]);
|
|
34
|
+
const result = await runT3Turn(client, "thread", { instanceId: "nudge", model: "model" }, options(), 1);
|
|
35
|
+
assert.equal(result.summary, "Finished new work");
|
|
36
|
+
assert.deepEqual(client.commands.map((c) => c.type), ["thread.turn.start", "thread.session.stop"]);
|
|
37
|
+
assert.deepEqual(client.commands[0].message, { messageId: client.commands[0].message.messageId, role: "user", text: "New work", attachments: [] });
|
|
38
|
+
assert.equal(client.commands[0].threadId, "thread");
|
|
39
|
+
});
|
|
40
|
+
test("an ambiguous start response never starts a duplicate turn", async () => {
|
|
41
|
+
const client = new FakeT3([previous, completed]);
|
|
42
|
+
client.commitThenTimeout = true;
|
|
43
|
+
assert.equal((await runT3Turn(client, "thread", {}, options(), 1)).summary, "Finished new work");
|
|
44
|
+
assert.equal(client.commands.filter((c) => c.type === "thread.turn.start").length, 1);
|
|
45
|
+
});
|
|
46
|
+
test("runner recovery reattaches to an existing running turn without dispatching another", async () => {
|
|
47
|
+
const client = new FakeT3([{ latestTurn: { turnId: "new", state: "running" } }, completed]);
|
|
48
|
+
assert.equal((await runT3Turn(client, "thread", {}, options(), 1)).summary, "Finished new work");
|
|
49
|
+
assert.deepEqual(client.commands.map((c) => c.type), ["thread.session.stop"]);
|
|
50
|
+
});
|
|
51
|
+
test("steering or cancellation interrupts and stops T3 before propagating the abort", async () => {
|
|
52
|
+
const abort = new AbortController();
|
|
53
|
+
const client = new FakeT3([previous, { latestTurn: { turnId: "new", state: "running" } }]);
|
|
54
|
+
let reads = 0;
|
|
55
|
+
client.afterRead = () => { if (++reads === 2)
|
|
56
|
+
abort.abort(); };
|
|
57
|
+
await assert.rejects(runT3Turn(client, "thread", {}, { ...options(), signal: abort.signal }, 1), { name: "AbortError" });
|
|
58
|
+
assert.deepEqual(client.commands.map((c) => c.type), ["thread.turn.start", "thread.turn.interrupt", "thread.session.stop"]);
|
|
59
|
+
});
|
|
60
|
+
test("a pre-aborted job starts no T3 turn", async () => {
|
|
61
|
+
const client = new FakeT3([previous]);
|
|
62
|
+
await assert.rejects(runT3Turn(client, "thread", {}, { ...options(), signal: AbortSignal.abort() }, 1));
|
|
63
|
+
assert.equal(client.commands.length, 0);
|
|
64
|
+
});
|
|
65
|
+
test("provider errors and interruption cannot be reported as success", async () => {
|
|
66
|
+
for (const state of ["error", "interrupted"]) {
|
|
67
|
+
const client = new FakeT3([previous, { ...completed, latestTurn: { ...completed.latestTurn, state }, session: { lastError: "Provider stopped" } }]);
|
|
68
|
+
assert.equal((await runT3Turn(client, "thread", {}, options(), 1)).error, "Provider stopped");
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
test("runner references preserve T3 environment and reject paths or raw CLI IDs", () => {
|
|
72
|
+
const threadId = t3JobThreadId("deployment", "job");
|
|
73
|
+
assert.deepEqual(parseT3ResumeId(t3ResumeId("environment", threadId)), { environmentId: "environment", threadId });
|
|
74
|
+
assert.equal(parseT3ResumeId("codex-session"), null);
|
|
75
|
+
assert.throws(() => parseT3ResumeId("t3:environment:..%2Fprivate"));
|
|
76
|
+
assert.throws(() => parseT3ResumeId("t3:broken"));
|
|
77
|
+
});
|
|
78
|
+
test("a native resume with T3 disabled fails instead of falling back to a new CLI session", async () => {
|
|
79
|
+
const prior = process.env.NUDGE_T3;
|
|
80
|
+
process.env.NUDGE_T3 = "0";
|
|
81
|
+
try {
|
|
82
|
+
const fallback = { id: "codex" };
|
|
83
|
+
assert.equal(await createT3Engine(fallback, {}, null, () => { }), null);
|
|
84
|
+
await assert.rejects(createT3Engine(fallback, {}, t3ResumeId("env", t3JobThreadId("d", "j")), () => { }), /Open the local T3/);
|
|
85
|
+
assert.equal(await createT3Engine(fallback, {}, "original-cli-session", () => { }), null);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (prior === undefined)
|
|
89
|
+
delete process.env.NUDGE_T3;
|
|
90
|
+
else
|
|
91
|
+
process.env.NUDGE_T3 = prior;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
test("the bridge enforces read-only and Nudge identity on both start and resume", () => {
|
|
95
|
+
for (const method of ["thread/start", "thread/resume", "thread/fork"]) {
|
|
96
|
+
const message = applyRunnerPolicy({ id: 7, method, params: { sandbox: "danger-full-access", config: { existing: true } } }, context);
|
|
97
|
+
assert.equal(message.params.sandbox, "read-only");
|
|
98
|
+
assert.equal(message.params.approvalPolicy, "never");
|
|
99
|
+
assert.equal(message.params.config.existing, true);
|
|
100
|
+
assert.deepEqual(message.params.config["mcp_servers.nudge"].env, context.env);
|
|
101
|
+
}
|
|
102
|
+
const turn = applyRunnerPolicy({ method: "turn/start", params: { sandboxPolicy: { type: "dangerFullAccess" } } }, context);
|
|
103
|
+
assert.deepEqual(turn.params.sandboxPolicy, { type: "readOnly" });
|
|
104
|
+
assert.equal(turn.params.approvalPolicy, "never");
|
|
105
|
+
const notification = { method: "initialize", params: { clientInfo: { name: "T3" } } };
|
|
106
|
+
assert.equal(applyRunnerPolicy(notification, context), notification);
|
|
107
|
+
});
|
|
108
|
+
test("task sandbox choices retain workspace-only and explicitly remote-capable execution", () => {
|
|
109
|
+
for (const [sandbox, type] of [["workspace-write", "workspaceWrite"], ["danger-full-access", "dangerFullAccess"]]) {
|
|
110
|
+
assert.deepEqual(applyRunnerPolicy({ method: "turn/start" }, { ...context, sandbox }).params.sandboxPolicy, { type });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
test("usage counts only the new work on resume, deduplicates totals, and handles a reset", () => {
|
|
114
|
+
const total = { inputTokens: 1000, outputTokens: 100 };
|
|
115
|
+
const last = { inputTokens: 50, outputTokens: 5 };
|
|
116
|
+
assert.deepEqual(usageDelta(undefined, total, last), last);
|
|
117
|
+
assert.equal(usageDelta(total, total, last).inputTokens, 0);
|
|
118
|
+
assert.equal(usageDelta(total, { inputTokens: 1100, outputTokens: 110 }, last).inputTokens, 100);
|
|
119
|
+
assert.deepEqual(usageDelta(total, last, last), last);
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=runner-t3-engine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-t3-engine.test.js","sourceRoot":"","sources":["../src/runner-t3-engine.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Invoked in a T3 terminal. The JSONL file is data, never shell input.
|
|
2
|
+
import { open, stat } from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function safeTerminalText(text) {
|
|
5
|
+
// Strip controls including ESC, OSC and C1 so agent output cannot issue
|
|
6
|
+
// terminal/clipboard commands. Preserve readable tabs and line breaks.
|
|
7
|
+
return text.replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
8
|
+
}
|
|
9
|
+
export async function viewRunnerLog(file) {
|
|
10
|
+
let offset = 0;
|
|
11
|
+
let pending = "";
|
|
12
|
+
const decoder = new TextDecoder();
|
|
13
|
+
for (;;) {
|
|
14
|
+
const handle = await open(file, "r");
|
|
15
|
+
let bytesRead;
|
|
16
|
+
try {
|
|
17
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
18
|
+
({ bytesRead } = await handle.read(buffer, 0, buffer.length, offset));
|
|
19
|
+
offset += bytesRead;
|
|
20
|
+
pending += decoder.decode(buffer.subarray(0, bytesRead), { stream: true });
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await handle.close();
|
|
24
|
+
}
|
|
25
|
+
let end;
|
|
26
|
+
while ((end = pending.indexOf("\n")) >= 0) {
|
|
27
|
+
const entry = JSON.parse(pending.slice(0, end));
|
|
28
|
+
pending = pending.slice(end + 1);
|
|
29
|
+
process.stdout.write(safeTerminalText(String(entry.text ?? "")));
|
|
30
|
+
if (entry.done)
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!bytesRead) {
|
|
34
|
+
// A crashed runner must not leave a viewer alive indefinitely.
|
|
35
|
+
if (Date.now() - (await stat(file)).mtimeMs > 24 * 60 * 60 * 1000)
|
|
36
|
+
return;
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (process.argv[1] === fileURLToPath(import.meta.url) && process.argv[2]) {
|
|
42
|
+
viewRunnerLog(process.argv[2]).catch(() => { console.error("Nudge runner log is unavailable."); process.exitCode = 1; });
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=runner-t3-log-viewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-t3-log-viewer.js","sourceRoot":"","sources":["../src/runner-t3-log-viewer.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,wEAAwE;IACxE,uEAAuE;IACvE,OAAO,IAAI,CAAC,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACrC,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;YACvC,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YACtE,MAAM,IAAI,SAAS,CAAC;YACpB,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7E,CAAC;gBAAS,CAAC;YAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QACnC,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAChD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACjE,IAAI,KAAK,CAAC,IAAI;gBAAE,OAAO;QACzB,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,+DAA+D;YAC/D,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;gBAAE,OAAO;YAC1E,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3H,CAAC"}
|