@yagni-app/code-staging 1.1.4-staging.1392.1 → 1.1.4-staging.1404.1

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.
@@ -17,7 +17,11 @@ export function _setYagniCodeHomeForTest(dir) {
17
17
  homeOverride = dir;
18
18
  }
19
19
  export function credentialsDir() {
20
- return homeOverride ?? join(homedir(), DISTRIBUTION.stateDirName);
20
+ // `YAGNI_CODE_HOME` env override (mirrors the extension's stateHome):
21
+ // the hermetic-e2e lane — a sandboxed run seeds a temp home with copied
22
+ // credentials and never writes to the real `~/.yagni-code`, so e2e needs
23
+ // no unsandboxed prompts and cannot corrupt the live profile state.
24
+ return homeOverride ?? process.env.YAGNI_CODE_HOME ?? join(homedir(), DISTRIBUTION.stateDirName);
21
25
  }
22
26
  export function credentialsPath() {
23
27
  return join(credentialsDir(), "credentials.json");
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Background jobs: a Claude-Code-style mechanism for
3
+ * long-running commands that must never block the conversation.
4
+ *
5
+ * A background job is a `bash` call with `run_in_background: true`. The tool
6
+ * returns IMMEDIATELY with a task id + an on-disk output path; the process
7
+ * keeps running with piped stdio while the agent (and the user) continue
8
+ * talking. On exit, a `<task-notification>` arrives as a synthetic user
9
+ * message via pi.sendUserMessage — the model reads the output file with the
10
+ * normal Read tool. The user sees a footer pill (`1 shell · shift+↓ to
11
+ * view`, the label owned by bgJobsPanel.ts) and manages jobs through the
12
+ * /bg panel (view output, kill).
13
+ *
14
+ * Architecture notes (the invariants that matter):
15
+ *
16
+ * - ONE registry is the single source of truth: pill, panel and
17
+ * notifications all read it — no derived counters to drift stale.
18
+ * - The `notified` latch is check-and-set inside one synchronous
19
+ * transition function, so a kill racing a natural exit produces exactly
20
+ * one terminal status and at most one notification.
21
+ * - `killJob` flips the state to `killed` synchronously BEFORE the signal,
22
+ * so the exit handler sees `killed` and never rewrites it — the same
23
+ * ordering trick Claude Code uses.
24
+ * - Output goes to a per-session file written by THIS process (the child
25
+ * keeps pipes, not file handles), capped at 64MB with a marker line.
26
+ * - The stall watchdog ports Claude Code's: 45s of no output growth PLUS a
27
+ * prompt-looking tail (`(y/n)` etc.) fires ONE statusless notification
28
+ * telling the model to kill the task. Statusless because a `<status>`
29
+ * tag reads as terminal and would falsely close the job.
30
+ * - Module-level state like sessionRuns.ts: one CLI process is one driver
31
+ * session. Subagents are separate pi processes; their own
32
+ * session_shutdown → killAll covers them (no agentId scoping needed).
33
+ */
34
+ import { spawn } from "node:child_process";
35
+ export type BgJobStatus = "running" | "completed" | "failed" | "killed";
36
+ export interface BgJob {
37
+ id: string;
38
+ command: string;
39
+ description: string;
40
+ cwd: string;
41
+ status: BgJobStatus;
42
+ startedAt: number;
43
+ endedAt?: number;
44
+ exitCode?: number;
45
+ /** Signal that killed the process when status is "killed". */
46
+ killedBy?: "user" | "timeout" | "shutdown";
47
+ outputFile: string;
48
+ outputBytes: number;
49
+ /** True once the completion notification has been enqueued (the latch). */
50
+ notified: boolean;
51
+ /** True once the stall watchdog has fired (fires at most once per job). */
52
+ stallNotified: boolean;
53
+ /** True once the no-progress lane has fired (the deadlocked-silent ping). */
54
+ noProgressNotified: boolean;
55
+ /** Whether the composed bash path sandboxed this job (a start-time field). */
56
+ sandboxed: boolean;
57
+ /** True once output capture hit an I/O failure (disk full etc.) — the
58
+ * output file is incomplete; surfaced in the notification + panel. */
59
+ captureDegraded: boolean;
60
+ }
61
+ /** How a caller wants to be told about registry changes (pill repaints). */
62
+ export type BgJobsListener = () => void;
63
+ export interface StartBgJobInput {
64
+ /** The already-sandbox-wrapped command string (same prewrap as foreground). */
65
+ command: string;
66
+ /** The shell binary to run it with (the composed bash's shellPath). */
67
+ shellPath: string;
68
+ cwd: string;
69
+ description?: string;
70
+ /** Seconds; when set, the job is killed and reported as timed out. */
71
+ timeoutSec?: number;
72
+ env?: NodeJS.ProcessEnv;
73
+ /** Sandbox decision of the composed bash path, for the WAL trail. */
74
+ sandboxed: boolean;
75
+ }
76
+ export interface BgJobsHandle {
77
+ id: string;
78
+ outputFile: string;
79
+ }
80
+ /** Test seam: wipe all state (mirrors _resetSessionRunsForTest). */
81
+ export declare function _resetBgJobsForTest(): void;
82
+ export declare function _setSpawnForTest(fn: typeof spawn | null): void;
83
+ export declare function _setNowForTest(fn: (() => number) | null): void;
84
+ /** Wiring: how a notification reaches the session (pi.sendUserMessage wrapper). */
85
+ export declare function setNotifySink(notify: (text: string) => Promise<void> | void): void;
86
+ export declare function setUiNotify(notify: (message: string, kind: "info" | "warning" | "error") => void): void;
87
+ export declare function setBgJobOutcomeReporter(fn: ((status: string, sandboxed: boolean) => void) | null): void;
88
+ /** Test seam for the output-file dir (mirrors _setErrorSinkHomeForTest). */
89
+ export declare function _setBgJobsHomeForTest(dir: string | null): void;
90
+ export declare function subscribeBgJobs(listener: BgJobsListener): () => void;
91
+ export declare function listBgJobs(): BgJob[];
92
+ export declare function getBgJob(id: string): BgJob | undefined;
93
+ export declare function runningBgJobCount(): number;
94
+ export declare function escapeXml(s: string): string;
95
+ /** PURE: the summary line Claude Code uses for bash completions. */
96
+ export declare function bgJobSummary(job: BgJob): string;
97
+ /** PURE: the completion notification. tool-use-id deliberately omitted (v1). */
98
+ export declare function bgJobNotification(job: BgJob): string;
99
+ /** PURE: does the tail look like an interactive prompt? (Claude Code's patterns.) */
100
+ export declare const PROMPT_PATTERNS: RegExp[];
101
+ export declare function looksLikePrompt(tail: string): boolean;
102
+ /**
103
+ * One stall-watchdog evaluation. Exported for tests: the interval calls it;
104
+ * tests drive it directly with a controlled clock, no real 5s wait.
105
+ */
106
+ export declare function _checkStallOnce(id: string): Promise<void>;
107
+ /**
108
+ * Test seam: wait for every in-flight output-file write to settle, so a
109
+ * teardown can rmSync the output dir deterministically (no ENOTEMPTY race
110
+ * with a pending append). Bounded by `deadlineMs` — a wedged fs write
111
+ * degrades to today's fire-and-forget behavior rather than hanging a test.
112
+ */
113
+ export declare function _drainBgJobsAppendsForTest(deadlineMs?: number): Promise<void>;
114
+ export declare function startBackgroundJob(input: StartBgJobInput, sessionId: string): Promise<BgJobsHandle>;
115
+ /**
116
+ * Kill a running job. Synchronous state flip FIRST (so the panel repaints
117
+ * immediately and the exit handler never rewrites `killed`), then the signal.
118
+ */
119
+ export declare function killJob(id: string, by?: BgJob["killedBy"]): boolean;
120
+ /**
121
+ * The win32 kill lane: taskkill the whole tree. Routed through the
122
+ * spawnFn seam (testable like the rest of the engine), with stderr captured
123
+ * so the log carries taskkill's own cause ("Access is denied." vs "not
124
+ * found"), and close codes distinguished — code === null means taskkill
125
+ * itself died to a signal, which is not "exit code null".
126
+ */
127
+ export declare function spawnTaskKill(taskId: string, pid: number): void;
128
+ /** Kill everything still running (session_shutdown + process-exit sweep). */
129
+ export declare function killAllBgJobs(): number;
130
+ /** PURE-ish (reads the registry): the tool result text for a bg launch. */
131
+ export declare function bgLaunchResult(handle: BgJobsHandle, timeoutSec?: number): string;
132
+ //# sourceMappingURL=bgJobs.d.ts.map