tickmarkr 2.8.4 → 2.8.6
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/adapters/claude-code.js +3 -2
- package/dist/adapters/codex-commit-check.d.ts +4 -3
- package/dist/adapters/codex-commit-check.js +44 -29
- package/dist/adapters/codex.d.ts +2 -2
- package/dist/adapters/codex.js +38 -32
- package/dist/adapters/cursor-agent.js +2 -1
- package/dist/adapters/grok.js +2 -1
- package/dist/adapters/kimi.js +11 -9
- package/dist/adapters/model-lints.js +4 -3
- package/dist/adapters/opencode.js +2 -1
- package/dist/adapters/pi.js +3 -2
- package/dist/adapters/qwen.js +2 -1
- package/dist/adapters/registry.js +2 -1
- package/dist/adapters/types.d.ts +10 -1
- package/dist/adapters/types.js +18 -3
- package/dist/cli/commands/approve.js +9 -28
- package/dist/cli/commands/doctor.d.ts +49 -5
- package/dist/cli/commands/doctor.js +956 -94
- package/dist/cli/commands/fleet.js +11 -3
- package/dist/cli/commands/init.js +27 -19
- package/dist/cli/commands/status.js +9 -6
- package/dist/cli/commands/verify.js +12 -3
- package/dist/compile/gsd.js +4 -3
- package/dist/compile/index.js +4 -1
- package/dist/compile/native.js +8 -6
- package/dist/config/config.d.ts +15 -0
- package/dist/config/config.js +67 -5
- package/dist/drivers/host-env.d.ts +3 -0
- package/dist/drivers/host-env.js +47 -0
- package/dist/drivers/orca.js +3 -1
- package/dist/drivers/subprocess.d.ts +1 -1
- package/dist/drivers/subprocess.js +4 -3
- package/dist/gates/baseline.d.ts +1 -1
- package/dist/gates/baseline.js +4 -3
- package/dist/gates/cache.js +2 -4
- package/dist/graph/schema.js +3 -1
- package/dist/run/daemon.js +54 -4
- package/dist/run/git-trust.d.ts +11 -0
- package/dist/run/git-trust.js +1 -1
- package/dist/run/git.d.ts +13 -1
- package/dist/run/git.js +34 -14
- package/dist/run/journal.d.ts +15 -1
- package/dist/run/journal.js +83 -17
- package/dist/run/lock.d.ts +5 -0
- package/dist/run/lock.js +202 -9
- package/dist/run/protocol.d.ts +1 -1
- package/dist/tui/cockpit/live-store.js +12 -2
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +65 -21
- package/skills/tickmarkr-loop/SKILL.md +65 -21
- package/skills/tickmarkr-overseer/SKILL.md +88 -32
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
5
6
|
import { parseWorkerResult } from "./prompt.js";
|
|
6
7
|
import { channelsFromConfig, declareInputBox, MODEL_ID_RE, promptFitsArgv, shq, TokenUsageSchema } from "./types.js";
|
|
7
8
|
// SPEND-01/SPEND-11: claude writes a per-session JSONL to ~/.claude/projects/<slug>/ where slug is the
|
|
@@ -188,7 +189,7 @@ export function probeClaudeAliasIdentity(cwd, alias) {
|
|
|
188
189
|
"--strict-mcp-config",
|
|
189
190
|
"--mcp-config", '{"mcpServers":{}}',
|
|
190
191
|
"--output-format", "text",
|
|
191
|
-
], { cwd, encoding: "utf8", timeout: 60000, stdio: ["ignore", "pipe", "pipe"] });
|
|
192
|
+
], { cwd, encoding: "utf8", timeout: 60000, stdio: ["ignore", "pipe", "pipe"], env: withoutHostSession() });
|
|
192
193
|
if (r.error || r.status !== 0)
|
|
193
194
|
return undefined;
|
|
194
195
|
const identity = (r.stdout || "").trim();
|
|
@@ -202,7 +203,7 @@ export function resolveClaudeAliasIdentity(cwd, alias, probe = probeClaudeAliasI
|
|
|
202
203
|
return readClaudeAliasIdentity(cwd, alias) ?? probe(cwd, alias);
|
|
203
204
|
}
|
|
204
205
|
export function probeVersion(bin) {
|
|
205
|
-
const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 10000 });
|
|
206
|
+
const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 10000, env: withoutHostSession() });
|
|
206
207
|
if (r.error || r.status !== 0)
|
|
207
208
|
return { installed: false, authed: false, models: [] };
|
|
208
209
|
return {
|
|
@@ -10,6 +10,7 @@ export interface CodexCommitProbe {
|
|
|
10
10
|
hostileRoot: string;
|
|
11
11
|
token: string;
|
|
12
12
|
signal?: AbortSignal;
|
|
13
|
+
repoRoot?: string;
|
|
13
14
|
}
|
|
14
15
|
/** Runs the probe script inside the Codex sandbox and resolves its stdout. Injected in tests. */
|
|
15
16
|
export type CodexSandbox = (probe: CodexCommitProbe) => Promise<string>;
|
|
@@ -17,10 +18,10 @@ export interface CodexCommitResult {
|
|
|
17
18
|
status: CodexCommitStatus;
|
|
18
19
|
detail: string;
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
+
/** Queue row 2 (D-2112, D-2114): the fixture uses the same literal grant as a worker. */
|
|
21
22
|
export declare const codexSandboxArgs: (p: CodexCommitProbe, grant?: string) => string[];
|
|
22
|
-
/**
|
|
23
|
-
export declare const codexSandboxFor: (grant
|
|
23
|
+
/** Queue row 2 (D-2112): tests may supply a mutant; production renders each fixture. */
|
|
24
|
+
export declare const codexSandboxFor: (grant?: string) => CodexSandbox;
|
|
24
25
|
export declare const codexSandbox: CodexSandbox;
|
|
25
26
|
export declare function probeCodexCommit(repoRoot: string, sandbox?: CodexSandbox, signal?: AbortSignal): Promise<CodexCommitResult>;
|
|
26
27
|
/** A persisted verdict outside the closed table (an older or newer doctor.json) reads unknown, never allowed. */
|
|
@@ -4,18 +4,20 @@ import { existsSync, mkdtempSync, readFileSync, rmdirSync, rmSync } from "node:f
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { basename, dirname, join } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { assertGitTrust, GitTrustRefusal
|
|
8
|
-
import {
|
|
7
|
+
import { assertGitTrust, GitTrustRefusal } from "../run/git-trust.js";
|
|
8
|
+
import { shGitOk } from "../run/git.js";
|
|
9
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
10
|
+
import { renderCodexGitGrant, codex } from "./codex.js";
|
|
9
11
|
import { shq } from "./types.js";
|
|
10
12
|
const exec = promisify(execFile);
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
const hostGit = async (cwd,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
13
|
+
// v2.6.7 T4 → queue row 2 item b (D-2112): every fixture git — setup, validation and cleanup — is tickmarkr's own git
|
|
14
|
+
// (shGitOk): the uncached trust check, the forced inert fsmonitor and hooks and, in a linked checkout, the pin, so
|
|
15
|
+
// seat-rewritten metadata can neither run a config program or hook nor answer for the probe's commit. A trust refusal
|
|
16
|
+
// rejects with GitTrustRefusal naming the path and is reported as an unknown verdict, never folded into another answer.
|
|
17
|
+
const hostGit = async (cwd, args, own) => (await shGitOk(["git", ...args].map(shq).join(" "), cwd, own)).trim();
|
|
18
|
+
/** An ordinary git failure is `fallback`; a trust refusal propagates. */
|
|
19
|
+
const unlessRefused = (fallback) => (error) => { if (error instanceof GitTrustRefusal)
|
|
20
|
+
throw error; return fallback; };
|
|
19
21
|
/** The fixture checkout's trust after the sandbox returned or threw: a refusal is reported with its path. */
|
|
20
22
|
const fixtureRefusal = (worktree) => {
|
|
21
23
|
try {
|
|
@@ -34,14 +36,14 @@ const SCRIPT = `printf %s "$2" > "$1" && echo control=ok || echo control=fail
|
|
|
34
36
|
git add -- "$1" && git -c core.hooksPath=/dev/null -c core.logAllRefUpdates=true -c commit.gpgsign=false -c user.name=tickmarkr -c user.email=probe@tickmarkr.invalid commit -q -m "tickmarkr-probe $2" && echo commit=ok || echo commit=fail
|
|
35
37
|
(set -C; printf %s "$2" > "$3") 2>/dev/null && echo hook=written || echo hook=denied
|
|
36
38
|
(set -C; printf %s "$2" > "$4") 2>/dev/null && echo root=written || echo root=denied`;
|
|
37
|
-
/**
|
|
38
|
-
export const codexSandboxArgs = (p, grant =
|
|
39
|
+
/** Queue row 2 (D-2112, D-2114): the fixture uses the same literal grant as a worker. */
|
|
40
|
+
export const codexSandboxArgs = (p, grant = renderCodexGitGrant({ cwd: p.worktree, branch: p.branch, repoRoot: p.repoRoot ?? p.commonDir })) => [
|
|
39
41
|
"-c",
|
|
40
42
|
`codex sandbox -c 'sandbox_mode="workspace-write"' ${grant} -- sh -c ${shq(SCRIPT)} sh ${[p.control, p.token, p.hostileHook, p.hostileRoot].map(shq).join(" ")}`,
|
|
41
43
|
];
|
|
42
|
-
/**
|
|
43
|
-
export const codexSandboxFor = (grant) => async (p) => (await exec("sh", codexSandboxArgs(p, grant), { cwd: p.worktree, timeout: 20_000, signal: p.signal })).stdout;
|
|
44
|
-
export const codexSandbox = codexSandboxFor(
|
|
44
|
+
/** Queue row 2 (D-2112): tests may supply a mutant; production renders each fixture. */
|
|
45
|
+
export const codexSandboxFor = (grant) => async (p) => (await exec("sh", codexSandboxArgs(p, grant), { cwd: p.worktree, timeout: 20_000, signal: p.signal, env: withoutHostSession() })).stdout;
|
|
46
|
+
export const codexSandbox = codexSandboxFor();
|
|
45
47
|
const holds = (path, token) => {
|
|
46
48
|
try {
|
|
47
49
|
return readFileSync(path, "utf8") === token;
|
|
@@ -66,18 +68,28 @@ const contradictory = (out) => {
|
|
|
66
68
|
}
|
|
67
69
|
return false;
|
|
68
70
|
};
|
|
69
|
-
const tipOf = async (repo, ref) => hostGit(repo, "rev-parse", "--verify", "--quiet", `${ref}^{commit}`).catch((
|
|
71
|
+
const tipOf = async (repo, ref) => hostGit(repo, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]).catch(unlessRefused(undefined));
|
|
70
72
|
// This probe's commit and nothing else: one parent (the fixture base), its token in the subject and the control.
|
|
71
73
|
const isProbeCommit = async (repo, commit, base, token) => {
|
|
72
74
|
try {
|
|
73
|
-
return await hostGit(repo, "log", "-1", "--format=%P %s", commit) === `${base} tickmarkr-probe ${token}`
|
|
74
|
-
&& await hostGit(repo, "cat-file", "blob", `${commit}:${CONTROL}`) === token;
|
|
75
|
+
return await hostGit(repo, ["log", "-1", "--format=%P %s", commit]) === `${base} tickmarkr-probe ${token}`
|
|
76
|
+
&& await hostGit(repo, ["cat-file", "blob", `${commit}:${CONTROL}`]) === token;
|
|
75
77
|
}
|
|
76
|
-
catch {
|
|
77
|
-
return false;
|
|
78
|
+
catch (e) {
|
|
79
|
+
return unlessRefused(false)(e);
|
|
78
80
|
}
|
|
79
81
|
};
|
|
80
82
|
export async function probeCodexCommit(repoRoot, sandbox = codexSandbox, signal) {
|
|
83
|
+
// queue row 2 item b: cleanup stays best effort for ordinary git failures, but a trust refusal there is never
|
|
84
|
+
// folded into the verdict already reached — it is reported as unknown naming its path (an escape still dominates)
|
|
85
|
+
let refusal;
|
|
86
|
+
const result = await probeFixture(repoRoot, sandbox, signal, (e) => { refusal ??= e; });
|
|
87
|
+
return refusal && result.status !== "escape" ? unknown(`fixture git refused (${refusal.message})`) : result;
|
|
88
|
+
}
|
|
89
|
+
async function probeFixture(repoRoot, sandbox, signal, refused) {
|
|
90
|
+
/** A cleanup git's ordinary failure is `fallback`; its trust refusal is reported to the caller, then `fallback`. */
|
|
91
|
+
const cleanup = (fallback) => (e) => { if (e instanceof GitTrustRefusal)
|
|
92
|
+
refused(e); return fallback; };
|
|
81
93
|
const token = randomUUID();
|
|
82
94
|
const parent = mkdtempSync(join(tmpdir(), "tickmarkr-codex-commit-"));
|
|
83
95
|
// git names the fixture's metadata directory after the checkout's basename; a unique one makes that
|
|
@@ -90,21 +102,22 @@ export async function probeCodexCommit(repoRoot, sandbox = codexSandbox, signal)
|
|
|
90
102
|
let hostile = [];
|
|
91
103
|
let sandboxed = false;
|
|
92
104
|
try {
|
|
93
|
-
const commonDir = await
|
|
105
|
+
const commonDir = await hostGit(repoRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
94
106
|
lock = join(commonDir, "worktrees", basename(worktree), "index.lock");
|
|
95
107
|
const hostileHook = join(commonDir, "hooks", branch), hostileRoot = join(commonDir, branch);
|
|
96
108
|
hostile = [hostileHook, hostileRoot];
|
|
97
109
|
if (hostile.some((m) => existsSync(m)))
|
|
98
110
|
return unknown("a foreign file already holds a hostile probe path");
|
|
99
|
-
base = await
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
base = await hostGit(repoRoot, ["rev-parse", "--verify", "HEAD^{commit}"]);
|
|
112
|
+
// the add declares the branch capability: a result refused after its child ran is rolled back (checkout and branch)
|
|
113
|
+
await hostGit(repoRoot, ["worktree", "add", "-b", branch, worktree, base], { createsWorktree: "branch", rollback: { dir: worktree, branch: { name: branch } } });
|
|
114
|
+
if (join(await hostGit(worktree, ["rev-parse", "--absolute-git-dir"]), "index.lock") !== lock)
|
|
102
115
|
return unknown("the fixture worktree metadata is not where git was expected to put it");
|
|
103
116
|
const control = join(worktree, CONTROL);
|
|
104
117
|
if (existsSync(lock))
|
|
105
118
|
return unknown("a foreign index.lock already holds the fixture worktree");
|
|
106
119
|
sandboxed = true;
|
|
107
|
-
const out = await sandbox({ worktree, commonDir, branch, control, lock, hostileHook, hostileRoot, token, signal });
|
|
120
|
+
const out = await sandbox({ worktree, commonDir, branch, control, lock, hostileHook, hostileRoot, token, signal, repoRoot });
|
|
108
121
|
// An escape on disk dominates; every other verdict needs consistent receipts AND disk — and trusted metadata.
|
|
109
122
|
const escape = escaped(hostile, token);
|
|
110
123
|
if (escape)
|
|
@@ -144,6 +157,8 @@ export async function probeCodexCommit(repoRoot, sandbox = codexSandbox, signal)
|
|
|
144
157
|
const refused = sandboxed ? fixtureRefusal(worktree) : undefined;
|
|
145
158
|
if (refused)
|
|
146
159
|
return refused;
|
|
160
|
+
if (e instanceof GitTrustRefusal)
|
|
161
|
+
return unknown(`fixture git refused (${e.message})`);
|
|
147
162
|
return unknown(signal?.aborted ? "probe cancelled" : `probe did not run (${(e instanceof Error ? e.message : String(e)).split("\n")[0]})`);
|
|
148
163
|
}
|
|
149
164
|
finally {
|
|
@@ -158,7 +173,7 @@ export async function probeCodexCommit(repoRoot, sandbox = codexSandbox, signal)
|
|
|
158
173
|
// prune — that would take an earlier probe's preserved foreign lock (or anyone's prunable worktree).
|
|
159
174
|
const foreign = lock !== undefined && existsSync(lock);
|
|
160
175
|
if (lock && !foreign) {
|
|
161
|
-
await hostGit(repoRoot, "worktree", "remove", "--force", worktree).catch((
|
|
176
|
+
await hostGit(repoRoot, ["worktree", "remove", "--force", worktree]).catch(cleanup(undefined));
|
|
162
177
|
// a failed `worktree add` leaves metadata git no longer removes by path: drop this probe's own directory
|
|
163
178
|
rmSync(dirname(lock), { recursive: true, force: true });
|
|
164
179
|
try {
|
|
@@ -169,9 +184,9 @@ export async function probeCodexCommit(repoRoot, sandbox = codexSandbox, signal)
|
|
|
169
184
|
// The fixture branch and its reflog go only while the tip is still this probe's (the base or its own
|
|
170
185
|
// commit), and update-ref's old-value check refuses if anyone moves it between this read and the delete.
|
|
171
186
|
// Metadata preserved for a foreign lock keeps its branch too: that checkout's HEAD still names it.
|
|
172
|
-
const tip = base === undefined || foreign ? undefined : await tipOf(repoRoot, ref);
|
|
173
|
-
if (tip && base && (tip === base || await isProbeCommit(repoRoot, tip, base, token))) {
|
|
174
|
-
await hostGit(repoRoot, "update-ref", "-d", ref, tip).catch((
|
|
187
|
+
const tip = base === undefined || foreign ? undefined : await tipOf(repoRoot, ref).catch(cleanup(undefined));
|
|
188
|
+
if (tip && base && (tip === base || await isProbeCommit(repoRoot, tip, base, token).catch(cleanup(false)))) {
|
|
189
|
+
await hostGit(repoRoot, ["update-ref", "-d", ref, tip]).catch(cleanup(undefined));
|
|
175
190
|
}
|
|
176
191
|
rmSync(parent, { recursive: true, force: true });
|
|
177
192
|
}
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { type ModelRetirement, type TrustDialog, type TrustVerdict, type WorkerAdapter } from "./types.js";
|
|
1
|
+
import { type ModelRetirement, type TrustDialog, type TrustVerdict, type WorkerAdapter, type WorkerLaunchContext } from "./types.js";
|
|
2
2
|
export declare function readCodexModelsCache(path?: string): {
|
|
3
3
|
models: string[];
|
|
4
4
|
fetchedAt?: string;
|
|
5
5
|
retirements?: Record<string, ModelRetirement | null>;
|
|
6
6
|
hidden?: string[];
|
|
7
7
|
};
|
|
8
|
-
export declare
|
|
8
|
+
export declare function renderCodexGitGrant(ctx?: Partial<WorkerLaunchContext>): string;
|
|
9
9
|
export declare const CODEX_TRUST_DIALOG: TrustDialog;
|
|
10
10
|
export declare const CODEX_INPUT_BOX: import("./types.js").InputBox;
|
|
11
11
|
export declare function seedCodexTrust(repoRoot: string, configPath?: string): TrustVerdict;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
import { checkedAuthority } from "../run/git-trust.js";
|
|
4
5
|
import { probeVersion } from "./claude-code.js";
|
|
5
6
|
import { parseWorkerResult } from "./prompt.js";
|
|
6
7
|
import { channelsFromConfig, declareInputBox, MODEL_ID_RE, promptFitsArgv, shq, TokenUsageSchema } from "./types.js";
|
|
@@ -100,32 +101,37 @@ function codexRetirement(upgrade) {
|
|
|
100
101
|
// the successor id comes from an external file and is rendered in lints — same charset gate as slugs
|
|
101
102
|
return typeof u.model === "string" && MODEL_ID_RE.test(u.model) ? { retiresAt: u.retirement_at, successor: u.model } : undefined;
|
|
102
103
|
}
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
104
|
+
// Queue row 2 item a (D-2112, D-2114): tickmarkr renders literal roots from the checked authority,
|
|
105
|
+
// never a launch-shell git lookup. The worker's common dir must be the run repository's trusted one.
|
|
106
|
+
// Closed table: gitdir only when distinct, common objects, and the assigned branch's ref/reflog leaf
|
|
107
|
+
// only while its HEAD still names that branch. Ordinary checkouts omit gitdir; packed-refs stays denied.
|
|
108
|
+
// Any refusal, absent context or forbidden byte yields []; permitted bytes survive TOML and shq exactly.
|
|
109
|
+
// Queue row 151: sibling refs inside the assigned leaf remain writable.
|
|
110
|
+
export function renderCodexGitGrant(ctx) {
|
|
111
|
+
let roots = [];
|
|
112
|
+
try {
|
|
113
|
+
if (!ctx?.cwd || !ctx.branch || !ctx.repoRoot)
|
|
114
|
+
throw new Error("missing launch context");
|
|
115
|
+
const authority = checkedAuthority(ctx.cwd);
|
|
116
|
+
const run = checkedAuthority(ctx.repoRoot);
|
|
117
|
+
if (!authority || !run || authority.common !== run.common)
|
|
118
|
+
throw new Error("foreign git authority");
|
|
119
|
+
const { gitdir, common } = authority;
|
|
120
|
+
roots = [...(gitdir === common ? [] : [gitdir]), `${common}/objects`];
|
|
121
|
+
const head = readFileSync(join(gitdir, "HEAD"), "utf8");
|
|
122
|
+
if (head === `ref: refs/heads/${ctx.branch}\n` || head === `ref: refs/heads/${ctx.branch}`) {
|
|
123
|
+
const ref = `refs/heads/${ctx.branch}`;
|
|
124
|
+
const leaf = ref.slice(0, ref.lastIndexOf("/"));
|
|
125
|
+
roots.push(`${common}/${leaf}`, `${common}/logs/${leaf}`);
|
|
126
|
+
}
|
|
127
|
+
if (roots.some(root => /[\x00-\x1f\x7f"\\]/.test(root)))
|
|
128
|
+
roots = [];
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
roots = [];
|
|
132
|
+
}
|
|
133
|
+
return `-c ${shq(`sandbox_workspace_write.writable_roots=[${roots.map(root => `"${root}"`).join(",")}]`)}`;
|
|
134
|
+
}
|
|
129
135
|
// OBS-125: codex 0.144.x gates enabled hooks behind PER-PROJECT-PATH hook trust — a NEW gate distinct
|
|
130
136
|
// from the directory trust seedCodexTrust seeds. Every worker runs in a fresh ephemeral worktree, an
|
|
131
137
|
// untrusted project, so codex re-prompts "Hooks need review" and the interactive worker stalls forever
|
|
@@ -275,7 +281,7 @@ export function codexMcpSuppressionFlags(configPath) {
|
|
|
275
281
|
}
|
|
276
282
|
return flags.join(" ");
|
|
277
283
|
}
|
|
278
|
-
//
|
|
284
|
+
// Queue row 2 (D-2112): non-worker seats omit the grant; workers carry the rendered argument.
|
|
279
285
|
const codexExec = (promptFile, model, effort, grant) => `codex exec --sandbox workspace-write ${CODEX_HOOK_TRUST} ${codexMcpSuppressionFlags()}${grant}${codexEffortFlag(effort)} --model ${shq(model)} - < ${shq(promptFile)}`;
|
|
280
286
|
export const codex = {
|
|
281
287
|
id: "codex",
|
|
@@ -286,7 +292,7 @@ export const codex = {
|
|
|
286
292
|
probe: async () => probeVersion("codex"),
|
|
287
293
|
channels: (cfg) => channelsFromConfig("codex", cfg),
|
|
288
294
|
// v1.65 T3: every flag the command builder below hardcodes (incl. codexMcpSuppressionFlags' -c/
|
|
289
|
-
// --disable and
|
|
295
|
+
// --disable and renderCodexGitGrant's -c) — all listed by top-level `codex --help`, verified 2026-07-22.
|
|
290
296
|
hardcodedFlags: { binary: "codex", flags: ["--sandbox", "-a", "-s", "--model", "-c", "--disable", "--dangerously-bypass-hook-trust"] },
|
|
291
297
|
// --sandbox workspace-write is the autonomous sandbox mode (codex v0.144.1+)
|
|
292
298
|
// MCP suppression built per dispatch (config can change between runs) — see codexMcpSuppressionFlags.
|
|
@@ -304,11 +310,11 @@ export const codex = {
|
|
|
304
310
|
// autonomous approval policy (exec has no approvals to configure).
|
|
305
311
|
// OBS-930 (Linux): the inlined prompt is ONE argv string and Linux caps one at 131072 bytes, so a
|
|
306
312
|
// prompt over promptArgvCeiling() returns null → worker-mode-fallback → the headless form (types.ts).
|
|
307
|
-
interactiveCommand: (promptFile, model, effort) => promptFitsArgv(promptFile)
|
|
308
|
-
? `codex -a never -s workspace-write ${CODEX_HOOK_TRUST} ${codexMcpSuppressionFlags()} ${
|
|
313
|
+
interactiveCommand: (promptFile, model, effort, ctx) => promptFitsArgv(promptFile)
|
|
314
|
+
? `codex -a never -s workspace-write ${CODEX_HOOK_TRUST} ${codexMcpSuppressionFlags()} ${renderCodexGitGrant(ctx)}${codexEffortFlag(effort)} --model ${shq(model)} "$(cat ${shq(promptFile)})"`
|
|
309
315
|
: null,
|
|
310
|
-
invoke(task,
|
|
311
|
-
return { command: codexExec(ctx.promptFile, a.model, a.effort, ` ${
|
|
316
|
+
invoke(task, cwd, a, ctx) {
|
|
317
|
+
return { command: codexExec(ctx.promptFile, a.model, a.effort, ` ${renderCodexGitGrant({ ...ctx, cwd })}`) };
|
|
312
318
|
},
|
|
313
319
|
parse: parseWorkerResult,
|
|
314
320
|
// v1.22 T5: seed [projects."<repoRoot>"] trust_level="trusted" so fresh worktrees never stall on
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
2
3
|
import { probeVersion } from "./claude-code.js";
|
|
3
4
|
import { parseWorkerResult } from "./prompt.js";
|
|
4
5
|
import { channelsFromConfig, MODEL_ID_RE, shq } from "./types.js";
|
|
@@ -58,7 +59,7 @@ export const cursorAgent = {
|
|
|
58
59
|
// network (1.35s wall vs 0.34s CPU, RESEARCH A1); 15s timeout + fail-open covers offline.
|
|
59
60
|
// Live-verified 2026-07-10, cursor-agent 2026.07.08.
|
|
60
61
|
listModels: async () => {
|
|
61
|
-
const r = spawnSync("cursor-agent", ["--list-models"], { encoding: "utf8", timeout: 15000 });
|
|
62
|
+
const r = spawnSync("cursor-agent", ["--list-models"], { encoding: "utf8", timeout: 15000, env: withoutHostSession() });
|
|
62
63
|
return r.error || r.status !== 0 ? [] : parseCursorModels(r.stdout || "");
|
|
63
64
|
},
|
|
64
65
|
};
|
package/dist/adapters/grok.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
5
6
|
import { probeVersion } from "./claude-code.js";
|
|
6
7
|
import { parseWorkerResult } from "./prompt.js";
|
|
7
8
|
import { channelsFromConfig, MODEL_ID_RE, shq } from "./types.js";
|
|
@@ -130,7 +131,7 @@ export const grok = {
|
|
|
130
131
|
// Advisory/doctor-only — never in the dispatch or auth path (types.ts:56-60).
|
|
131
132
|
// Live-verified 2026-07-11 format, grok 0.2.93, 40-RESEARCH F-1.
|
|
132
133
|
listModels: async () => {
|
|
133
|
-
const r = spawnSync("grok", ["models"], { encoding: "utf8", timeout: 15000 });
|
|
134
|
+
const r = spawnSync("grok", ["models"], { encoding: "utf8", timeout: 15000, env: withoutHostSession() });
|
|
134
135
|
return r.error || r.status !== 0 ? [] : parseGrokModels(r.stdout || "");
|
|
135
136
|
},
|
|
136
137
|
};
|
package/dist/adapters/kimi.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
5
6
|
import { probeVersion } from "./claude-code.js";
|
|
6
7
|
import { parseWorkerResult } from "./prompt.js";
|
|
7
8
|
import { runInteractiveSeed } from "../run/interactive-seed.js";
|
|
@@ -191,7 +192,7 @@ const kimiTuiLaunch = (model) => {
|
|
|
191
192
|
let neverAsk = false;
|
|
192
193
|
try {
|
|
193
194
|
const help = spawnSync("kimi", ["--help"], {
|
|
194
|
-
encoding: "utf8", timeout: KIMI_HELP_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"],
|
|
195
|
+
encoding: "utf8", timeout: KIMI_HELP_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"], env: withoutHostSession(),
|
|
195
196
|
});
|
|
196
197
|
neverAsk = !help.error && help.status === 0 && kimiHasNeverAskOption(help.stdout);
|
|
197
198
|
}
|
|
@@ -287,8 +288,12 @@ const KIMI_SEED = {
|
|
|
287
288
|
// cursor glyph; prose never does. Enter accepts the highlighted "Trust this folder" option, and the
|
|
288
289
|
// two captures above are the reason the cursor+label pair is the fingerprint rather than either
|
|
289
290
|
// prompt's heading: the same gate ships two different headings.
|
|
291
|
+
// Queue row 198: kimi 2.2.0 renders that gate as one modal whose selected option is "Trust and continue"
|
|
292
|
+
// (KIMI_TRUST_PANE in types.ts), so the 0.29.0 pair above no longer appears and the seat waited out its
|
|
293
|
+
// readiness budget on an unanswered modal. The declaration follows the installed kimi, which updates
|
|
294
|
+
// itself; a kimi older than 2.2.0 renders the 0.29.0 option, which this declaration no longer answers.
|
|
290
295
|
export const KIMI_TRUST_DIALOG = {
|
|
291
|
-
fingerprint: "❯ Trust
|
|
296
|
+
fingerprint: "❯ Trust and continue",
|
|
292
297
|
key: "Enter",
|
|
293
298
|
};
|
|
294
299
|
// v1.69 T7 / v1.71 T2: thin wrapper over the daemon's generic dispatch path for kimi-only tests.
|
|
@@ -317,13 +322,10 @@ export const kimi = {
|
|
|
317
322
|
// v1.65 T3: every flag the command builders below hardcode (-S is resumeCommand's) — verified in
|
|
318
323
|
// `kimi --help` 2026-07-22.
|
|
319
324
|
hardcodedFlags: { binary: "kimi", flags: ["-p", "--model", "--output-format", "-S"] },
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
// reports `readiness pattern not seen` with zero keypresses. Answering it during the readiness
|
|
325
|
-
// wait is a change to src/run/interactive-seed.ts — WHEN and HOW OFTEN a declaration is answered
|
|
326
|
-
// is T19's half of this contract, and src/run may not appear in this task's diff.
|
|
325
|
+
// The seed's readiness wait answers this declaration once per slot before the banner appears
|
|
326
|
+
// (src/run/interactive-seed.ts awaitReadiness); the daemon's post-seed trust loop is the second
|
|
327
|
+
// consumer. Queue row 198: a declaration whose fingerprint the installed kimi no longer renders is
|
|
328
|
+
// never answered, and the seat waits out its readiness budget on the modal (row 199).
|
|
327
329
|
trustDialog: KIMI_TRUST_DIALOG,
|
|
328
330
|
// KIMI-02 headless. -p prompt mode + explicit model, NO permission flag: kimi 0.26.0 rejects
|
|
329
331
|
// -p combined with -y/--auto at argument parse time ("Cannot combine --prompt with --yolo",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
import { DEFAULT_CONFIG, TIER_RANK } from "../config/config.js";
|
|
5
4
|
import { filesGlob } from "../graph/files-glob.js";
|
|
5
|
+
import { gitSync } from "../run/git.js";
|
|
6
6
|
import { piModelVendor } from "./pi.js";
|
|
7
7
|
import { buildTaskPrompt } from "./prompt.js";
|
|
8
8
|
import { channelKey, MODEL_ID_RE } from "./types.js";
|
|
@@ -249,7 +249,8 @@ const GLOB_CHARS = /[*?{[]/;
|
|
|
249
249
|
// and `-z` is load-bearing correctness, not economy: without it git quotes any non-ASCII path, so
|
|
250
250
|
// every Arabic/UTF-8 tracked file read as absent.
|
|
251
251
|
function baseTreeAtHead(dir) {
|
|
252
|
-
|
|
252
|
+
// queue row 2 item b: protected git; a trust refusal throws instead of reading as "no base tree"
|
|
253
|
+
const git = (...args) => gitSync(dir, args);
|
|
253
254
|
const top = git("rev-parse", "--show-toplevel");
|
|
254
255
|
const tree = git("ls-tree", "--full-tree", "-r", "-l", "-z", "HEAD");
|
|
255
256
|
if (top.status !== 0 || tree.status !== 0 || typeof top.stdout !== "string" || typeof tree.stdout !== "string")
|
|
@@ -287,7 +288,7 @@ function measureBytes(tree, rel) {
|
|
|
287
288
|
return covered ? total : undefined;
|
|
288
289
|
}
|
|
289
290
|
function absentContextReason(root, path) {
|
|
290
|
-
if (
|
|
291
|
+
if (gitSync(root, ["check-ignore", "-q", "--", path]).status === 0)
|
|
291
292
|
return "gitignored";
|
|
292
293
|
return existsSync(join(root, path)) ? "untracked" : "absent";
|
|
293
294
|
}
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
5
6
|
import { probeVersion } from "./claude-code.js";
|
|
6
7
|
import { parseWorkerResult } from "./prompt.js";
|
|
7
8
|
import { channelsFromConfig, MODEL_ID_RE, OPENCODE_BUSY_FRAME_MARKERS, shq, TokenUsageSchema } from "./types.js";
|
|
@@ -52,7 +53,7 @@ export const opencode = {
|
|
|
52
53
|
// opencode reads its own cache offline; --refresh adds an avoidable network dependency (RESEARCH
|
|
53
54
|
// anti-pattern). Live-verified 2026-07-10, opencode 1.17.15.
|
|
54
55
|
listModels: async () => {
|
|
55
|
-
const r = spawnSync("opencode", ["models"], { encoding: "utf8", timeout: 15000 });
|
|
56
|
+
const r = spawnSync("opencode", ["models"], { encoding: "utf8", timeout: 15000, env: withoutHostSession() });
|
|
56
57
|
return r.error || r.status !== 0 ? [] : parseOpencodeModels(r.stdout || "");
|
|
57
58
|
},
|
|
58
59
|
// SPEND-09: opencode 1.x writes per-message token deltas to SQLite opencode.db (NOT the dead 0.7.x
|
package/dist/adapters/pi.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
5
6
|
import { probeVersion } from "./claude-code.js";
|
|
6
7
|
import { parseWorkerResult } from "./prompt.js";
|
|
7
8
|
import { channelsFromConfig, MODEL_ID_RE, shq, TokenUsageSchema } from "./types.js";
|
|
@@ -142,7 +143,7 @@ export const pi = {
|
|
|
142
143
|
// The store is local and independently useful: preserve its advisory even when the network/auth
|
|
143
144
|
// capability call below fails. A failed model listing must not erase evidence of a substitution.
|
|
144
145
|
const drift = servedModelNote();
|
|
145
|
-
const r = spawnSync("pi", ["--list-models"], { encoding: "utf8", timeout: 15000 });
|
|
146
|
+
const r = spawnSync("pi", ["--list-models"], { encoding: "utf8", timeout: 15000, env: withoutHostSession() });
|
|
146
147
|
if (r.error || r.status !== 0) {
|
|
147
148
|
return drift ? { ...h, note: `${h.note ? `${h.note}; ` : ""}${drift}` } : h;
|
|
148
149
|
}
|
|
@@ -183,7 +184,7 @@ export const pi = {
|
|
|
183
184
|
// v1.5 MODEL-01: fail OPEN to [] (detection is advisory — unlike gates' fail-closed posture).
|
|
184
185
|
// spawnSync mirrors probeVersion; 15s timeout. Live-verified 2026-07-10, pi 0.80.3.
|
|
185
186
|
listModels: async () => {
|
|
186
|
-
const r = spawnSync("pi", ["--list-models"], { encoding: "utf8", timeout: 15000 });
|
|
187
|
+
const r = spawnSync("pi", ["--list-models"], { encoding: "utf8", timeout: 15000, env: withoutHostSession() });
|
|
187
188
|
return r.error || r.status !== 0 ? [] : parsePiModels(r.stdout || "");
|
|
188
189
|
},
|
|
189
190
|
collectUsage(cwd, sinceMs) {
|
package/dist/adapters/qwen.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
2
3
|
import { parseWorkerResult } from "./prompt.js";
|
|
3
4
|
import { channelsFromConfig, shq, } from "./types.js";
|
|
4
5
|
export const QWEN_VERSION_IDENTITY = /^\d+\.\d+\.\d+/;
|
|
@@ -149,7 +150,7 @@ export function parseQwenResult(raw, nonce) {
|
|
|
149
150
|
return { ...parseWorkerResult(decoded.assistantText, nonce), raw };
|
|
150
151
|
}
|
|
151
152
|
function probeQwen() {
|
|
152
|
-
const result = spawnSync("qwen", ["--version"], { encoding: "utf8", timeout: 10_000 });
|
|
153
|
+
const result = spawnSync("qwen", ["--version"], { encoding: "utf8", timeout: 10_000, env: withoutHostSession() });
|
|
153
154
|
if (result.error || result.status !== 0)
|
|
154
155
|
return { installed: false, authed: false, models: [] };
|
|
155
156
|
const version = (result.stdout || result.stderr).trim().split("\n")[0] ?? "";
|
|
@@ -10,6 +10,7 @@ import { sh } from "../run/git.js";
|
|
|
10
10
|
import { FakeAdapter } from "./fake.js";
|
|
11
11
|
import { parseWorkerResult } from "./prompt.js";
|
|
12
12
|
import { sealHerdrEnv } from "../drivers/subprocess.js";
|
|
13
|
+
import { withoutHostSession } from "../drivers/host-env.js";
|
|
13
14
|
import { catalogEntries, isNativeCliDrive, projectCliEntries, SHIPPED_CLI_CATALOG, } from "./catalog.js";
|
|
14
15
|
import { channelKey, channelsFromConfig, modelAuthed, MODEL_ID_RE, MODEL_PROBE_ERRORS, QUOTA_PHRASE_RE, shq, STATUS_LABEL, } from "./types.js";
|
|
15
16
|
// Compatibility projection for callers/tests that only need shipped advisory names. The literal
|
|
@@ -180,7 +181,7 @@ export function formatCandidateCliRow({ binary, path }) {
|
|
|
180
181
|
// so no drift verdict piles onto the existing auth report.
|
|
181
182
|
export function probeHelpText(bin) {
|
|
182
183
|
try {
|
|
183
|
-
const r = spawnSync(bin, ["--help"], { encoding: "utf8", timeout: 10000 });
|
|
184
|
+
const r = spawnSync(bin, ["--help"], { encoding: "utf8", timeout: 10000, env: withoutHostSession() });
|
|
184
185
|
if (r.error || r.status !== 0)
|
|
185
186
|
return undefined;
|
|
186
187
|
return `${r.stdout || ""}\n${r.stderr || ""}`;
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -118,6 +118,7 @@ export declare const CLAUDE_TRUST_PANE: string;
|
|
|
118
118
|
export declare const CODEX_TRUST_PANE: string;
|
|
119
119
|
export declare const CURSOR_TRUST_PANE = "Workspace Trust Required\nTrust this folder?";
|
|
120
120
|
export declare const KIMI_TRUST_PANE: string;
|
|
121
|
+
export declare const KIMI_TRUST_PANE_029: string;
|
|
121
122
|
export declare const KIMI_MCP_TRUST_PANE: string;
|
|
122
123
|
export declare const RECORDED_TRUST_PANES: readonly string[];
|
|
123
124
|
export declare const APPROVED_TRUST_FINGERPRINTS: readonly string[];
|
|
@@ -186,6 +187,12 @@ export declare class SettledTrailerTracker {
|
|
|
186
187
|
constructor(markers: readonly BusyFrameMarker[]);
|
|
187
188
|
sample(paneText: string, trailer: boolean): boolean;
|
|
188
189
|
}
|
|
190
|
+
/** Queue row 2 item a (D-2112, D-2114): context owned by the task dispatcher. */
|
|
191
|
+
export interface WorkerLaunchContext {
|
|
192
|
+
cwd: string;
|
|
193
|
+
branch: string;
|
|
194
|
+
repoRoot: string;
|
|
195
|
+
}
|
|
189
196
|
export interface WorkerAdapter {
|
|
190
197
|
id: string;
|
|
191
198
|
vendor: string;
|
|
@@ -196,13 +203,15 @@ export interface WorkerAdapter {
|
|
|
196
203
|
headlessCommand(promptFile: string, model: string, effort?: Effort): string;
|
|
197
204
|
harnessBannerRows?: readonly string[];
|
|
198
205
|
paneReviewBuffersOutput?: boolean;
|
|
199
|
-
interactiveCommand(promptFile: string, model: string, effort?: Effort): string | null;
|
|
206
|
+
interactiveCommand(promptFile: string, model: string, effort?: Effort, ctx?: WorkerLaunchContext): string | null;
|
|
200
207
|
interactiveSeed?: InteractiveSeed;
|
|
201
208
|
resumeCommand?(sessionId: string, promptFile: string, model: string, effort?: Effort): string;
|
|
202
209
|
sessionIdFrom?(output: string): string | undefined;
|
|
203
210
|
resumeUnknownContext?: boolean;
|
|
204
211
|
invoke(task: Task, cwd: string, a: Assignment, ctx: {
|
|
205
212
|
promptFile: string;
|
|
213
|
+
branch?: string;
|
|
214
|
+
repoRoot?: string;
|
|
206
215
|
}): Invocation;
|
|
207
216
|
parse(output: string, nonce: string): WorkerResult;
|
|
208
217
|
listModels?(): Promise<string[]>;
|
package/dist/adapters/types.js
CHANGED
|
@@ -59,11 +59,25 @@ export const CODEX_TRUST_PANE = [
|
|
|
59
59
|
].join("\n");
|
|
60
60
|
// v1.22 T5 / OBS-19, cursor-agent's per-worktree dialog.
|
|
61
61
|
export const CURSOR_TRUST_PANE = "Workspace Trust Required\nTrust this folder?";
|
|
62
|
+
// Queue row 198, kimi 2.2.0's folder-trust modal on a fresh task worktree (run 1394 T3, read off the
|
|
63
|
+
// Orca screen before its one Enter). The worktree path is shortened as above; every other row is as
|
|
64
|
+
// rendered. 2.2.0 renamed the selected option, so the 0.29.0 fingerprint below never matched it and the
|
|
65
|
+
// seat sat on this modal for 21 minutes.
|
|
66
|
+
export const KIMI_TRUST_PANE = [
|
|
67
|
+
"Trust this folder?",
|
|
68
|
+
"↑↓ navigate · Enter select · Esc exit",
|
|
69
|
+
"/Users/…/.tickmarkr/worktrees.noindex/tickmarkr-run-20261017-122301-…1394--T3",
|
|
70
|
+
"No project integrations or instructions to activate.",
|
|
71
|
+
"Trust is remembered for this folder, including future project config changes.",
|
|
72
|
+
"Tool approvals follow your permission settings.",
|
|
73
|
+
" ❯ Trust and continue",
|
|
74
|
+
" Exit",
|
|
75
|
+
].join("\n");
|
|
62
76
|
// OBS-358, live pane wW:p2TB of run-20260805-121252 — the dialog that cost 30 minutes — quoted from
|
|
63
77
|
// the record holding all five lines, .overseer/REPAIR-v186/GATE-T28-1.md (OBSERVATIONS.md abridges
|
|
64
78
|
// it to the first three). "← highlighted" is the observer's annotation of the selected row, kept
|
|
65
|
-
// exactly as recorded rather than tidied away.
|
|
66
|
-
export const
|
|
79
|
+
// exactly as recorded rather than tidied away. kimi 0.29.0; superseded by the 2.2.0 capture above.
|
|
80
|
+
export const KIMI_TRUST_PANE_029 = [
|
|
67
81
|
"Trust this folder?",
|
|
68
82
|
" /Users/…/.tickmarkr/worktrees.noindex/tickmarkr-run-20260805-121252--T29",
|
|
69
83
|
"❯ Trust this folder ← highlighted",
|
|
@@ -77,7 +91,7 @@ export const KIMI_MCP_TRUST_PANE = [
|
|
|
77
91
|
" ❯ Trust this folder / Don't trust",
|
|
78
92
|
].join("\n");
|
|
79
93
|
export const RECORDED_TRUST_PANES = [
|
|
80
|
-
CLAUDE_TRUST_PANE, CODEX_TRUST_PANE, CURSOR_TRUST_PANE, KIMI_TRUST_PANE, KIMI_MCP_TRUST_PANE,
|
|
94
|
+
CLAUDE_TRUST_PANE, CODEX_TRUST_PANE, CURSOR_TRUST_PANE, KIMI_TRUST_PANE, KIMI_TRUST_PANE_029, KIMI_MCP_TRUST_PANE,
|
|
81
95
|
];
|
|
82
96
|
// v1.89 T1 / OBS-414 round 4 — EXACT, not "leading bytes of a recorded line". Round 3 accepted any
|
|
83
97
|
// prefix of a captured line, and every prefix of a trust prompt is also a substring of unrelated
|
|
@@ -96,6 +110,7 @@ export const APPROVED_TRUST_FINGERPRINTS = [
|
|
|
96
110
|
"Do you trust the contents of this directory?", // codex 0.144.6, OBS-137
|
|
97
111
|
"Workspace Trust Required", // cursor-agent, OBS-19
|
|
98
112
|
"❯ Trust this folder", // kimi 0.29.0, OBS-358 + OBS-406 — the cursor glyph is load-bearing
|
|
113
|
+
"❯ Trust and continue", // kimi 2.2.0, queue row 198 — the same gate with its option renamed
|
|
99
114
|
];
|
|
100
115
|
const isApprovedFingerprint = (fingerprint) => APPROVED_TRUST_FINGERPRINTS.includes(fingerprint)
|
|
101
116
|
&& RECORDED_TRUST_PANES.some((pane) => pane.includes(fingerprint));
|