@yagni-app/code 0.1.0 → 0.2.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.
- package/README.md +65 -11
- package/dist/claudeCompat.d.ts +109 -0
- package/dist/claudeCompat.js +260 -0
- package/dist/claudePlugins.d.ts +109 -0
- package/dist/claudePlugins.js +336 -0
- package/dist/cli.d.ts +1 -5
- package/dist/cli.js +79 -20
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/crashReport.d.ts +135 -0
- package/dist/crashReport.js +291 -0
- package/dist/credentials.js +2 -1
- package/dist/distribution.d.ts +15 -0
- package/dist/distribution.js +45 -0
- package/dist/doctor.d.ts +27 -0
- package/dist/doctor.js +81 -3
- package/dist/extension/advisor.d.ts +84 -0
- package/dist/extension/advisor.js +101 -0
- package/dist/extension/askAdvisorTool.d.ts +83 -0
- package/dist/extension/askAdvisorTool.js +194 -0
- package/dist/extension/askYagniTool.d.ts +39 -0
- package/dist/extension/askYagniTool.js +63 -0
- package/dist/extension/bless.d.ts +45 -0
- package/dist/extension/bless.js +87 -0
- package/dist/extension/boostCommand.d.ts +144 -0
- package/dist/extension/boostCommand.js +263 -0
- package/dist/extension/branding.d.ts +79 -0
- package/dist/extension/branding.js +156 -0
- package/dist/extension/chipEditor.d.ts +117 -0
- package/dist/extension/chipEditor.js +373 -0
- package/dist/extension/claudeRules.d.ts +54 -0
- package/dist/extension/claudeRules.js +180 -0
- package/dist/extension/config.d.ts +173 -0
- package/dist/extension/config.js +194 -0
- package/dist/extension/costHud.d.ts +186 -0
- package/dist/extension/costHud.js +293 -0
- package/dist/extension/crashReport.d.ts +89 -0
- package/dist/extension/crashReport.js +241 -0
- package/dist/extension/decisionCapture.d.ts +52 -0
- package/dist/extension/decisionCapture.js +66 -0
- package/dist/extension/decisions.d.ts +83 -0
- package/dist/extension/decisions.js +200 -0
- package/dist/extension/diagnostics.d.ts +41 -0
- package/dist/extension/diagnostics.js +110 -0
- package/dist/extension/index.d.ts +155 -0
- package/dist/extension/index.js +597 -0
- package/dist/extension/initDone.d.ts +28 -0
- package/dist/extension/initDone.js +66 -0
- package/dist/extension/initPass.d.ts +170 -0
- package/dist/extension/initPass.js +394 -0
- package/dist/extension/mcpTools.d.ts +57 -0
- package/dist/extension/mcpTools.js +132 -0
- package/dist/extension/nextWorkTool.d.ts +51 -0
- package/dist/extension/nextWorkTool.js +80 -0
- package/dist/extension/permission.d.ts +91 -0
- package/dist/extension/permission.js +236 -0
- package/dist/extension/pipeline/activity.d.ts +37 -0
- package/dist/extension/pipeline/activity.js +151 -0
- package/dist/extension/pipeline/activityFeed.d.ts +59 -0
- package/dist/extension/pipeline/activityFeed.js +175 -0
- package/dist/extension/pipeline/budget.d.ts +48 -0
- package/dist/extension/pipeline/budget.js +68 -0
- package/dist/extension/pipeline/checkpoint.d.ts +31 -0
- package/dist/extension/pipeline/checkpoint.js +176 -0
- package/dist/extension/pipeline/eval.d.ts +205 -0
- package/dist/extension/pipeline/eval.js +226 -0
- package/dist/extension/pipeline/events.d.ts +56 -0
- package/dist/extension/pipeline/events.js +147 -0
- package/dist/extension/pipeline/findings.d.ts +42 -0
- package/dist/extension/pipeline/findings.js +144 -0
- package/dist/extension/pipeline/finish.d.ts +128 -0
- package/dist/extension/pipeline/finish.js +307 -0
- package/dist/extension/pipeline/goCommand.d.ts +146 -0
- package/dist/extension/pipeline/goCommand.js +1085 -0
- package/dist/extension/pipeline/goCompareCommand.d.ts +58 -0
- package/dist/extension/pipeline/goCompareCommand.js +203 -0
- package/dist/extension/pipeline/goFlags.d.ts +40 -0
- package/dist/extension/pipeline/goFlags.js +46 -0
- package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
- package/dist/extension/pipeline/goStatusCommands.js +188 -0
- package/dist/extension/pipeline/invocation.d.ts +45 -0
- package/dist/extension/pipeline/invocation.js +64 -0
- package/dist/extension/pipeline/orchestrator.d.ts +131 -0
- package/dist/extension/pipeline/orchestrator.js +645 -0
- package/dist/extension/pipeline/personas.d.ts +44 -0
- package/dist/extension/pipeline/personas.js +248 -0
- package/dist/extension/pipeline/resilience.d.ts +85 -0
- package/dist/extension/pipeline/resilience.js +166 -0
- package/dist/extension/pipeline/resume.d.ts +18 -0
- package/dist/extension/pipeline/resume.js +106 -0
- package/dist/extension/pipeline/runCostTable.d.ts +37 -0
- package/dist/extension/pipeline/runCostTable.js +165 -0
- package/dist/extension/pipeline/runRegistry.d.ts +112 -0
- package/dist/extension/pipeline/runRegistry.js +202 -0
- package/dist/extension/pipeline/runSession.d.ts +152 -0
- package/dist/extension/pipeline/runSession.js +167 -0
- package/dist/extension/pipeline/runState.d.ts +177 -0
- package/dist/extension/pipeline/runState.js +275 -0
- package/dist/extension/pipeline/runner.d.ts +98 -0
- package/dist/extension/pipeline/runner.js +310 -0
- package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
- package/dist/extension/pipeline/scrubSecrets.js +32 -0
- package/dist/extension/pipeline/stages.d.ts +53 -0
- package/dist/extension/pipeline/stages.js +115 -0
- package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
- package/dist/extension/pipeline/ticketResolution.js +75 -0
- package/dist/extension/pipeline/types.d.ts +439 -0
- package/dist/extension/pipeline/types.js +48 -0
- package/dist/extension/pipeline/verify.d.ts +277 -0
- package/dist/extension/pipeline/verify.js +763 -0
- package/dist/extension/pipeline/workspace.d.ts +32 -0
- package/dist/extension/pipeline/workspace.js +73 -0
- package/dist/extension/pipeline/worktree.d.ts +107 -0
- package/dist/extension/pipeline/worktree.js +202 -0
- package/dist/extension/provider.d.ts +17 -0
- package/dist/extension/provider.js +33 -0
- package/dist/extension/recall.d.ts +93 -0
- package/dist/extension/recall.js +190 -0
- package/dist/extension/recordContextTool.d.ts +38 -0
- package/dist/extension/recordContextTool.js +85 -0
- package/dist/extension/recordDecisionTool.d.ts +52 -0
- package/dist/extension/recordDecisionTool.js +102 -0
- package/dist/extension/repoDocs.d.ts +81 -0
- package/dist/extension/repoDocs.js +260 -0
- package/dist/extension/rerouteNotice.d.ts +34 -0
- package/dist/extension/rerouteNotice.js +79 -0
- package/dist/extension/resilientFetch.d.ts +60 -0
- package/dist/extension/resilientFetch.js +133 -0
- package/dist/extension/reviewTool.d.ts +34 -0
- package/dist/extension/reviewTool.js +81 -0
- package/dist/extension/sessionRuns.d.ts +45 -0
- package/dist/extension/sessionRuns.js +77 -0
- package/dist/extension/spool.d.ts +92 -0
- package/dist/extension/spool.js +266 -0
- package/dist/extension/stateHome.d.ts +2 -0
- package/dist/extension/stateHome.js +6 -0
- package/dist/extension/subagents.d.ts +145 -0
- package/dist/extension/subagents.js +326 -0
- package/dist/extension/surface.d.ts +10 -0
- package/dist/extension/surface.js +12 -0
- package/dist/extension/todos.d.ts +110 -0
- package/dist/extension/todos.js +217 -0
- package/dist/extension/tokenProvider.d.ts +93 -0
- package/dist/extension/tokenProvider.js +234 -0
- package/dist/launch.d.ts +25 -3
- package/dist/launch.js +27 -9
- package/dist/login.d.ts +7 -0
- package/dist/login.js +3 -1
- package/dist/paths.d.ts +13 -4
- package/dist/paths.js +17 -5
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +5 -2
- package/dist/upgrade.d.ts +97 -0
- package/dist/upgrade.js +284 -0
- package/package.json +11 -15
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/go` run-session adapter.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the device-token `/api/yagni-code/runs*` endpoints so a YAGNI Code
|
|
5
|
+
* pipeline run becomes a tracked `runs` row visible on the Work page: open the
|
|
6
|
+
* run (`start`), stream each pipeline stage (`stage`), bank an in-loop
|
|
7
|
+
* correction (`correction`), and close it with an honest outcome (`finish`).
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL invariant: recording must NEVER break the pipeline. Every call is
|
|
10
|
+
* fail-soft — it never throws. `start` resolves to a typed outcome that
|
|
11
|
+
* distinguishes a tracked run from an untracked one (flag off, unauthorized, or
|
|
12
|
+
* the backend unreachable/erroring) so the caller can surface a notice instead
|
|
13
|
+
* of failing silently; the post-start verbs (`stage`/`correction`/`finish`) stay
|
|
14
|
+
* silent no-ops. `/go` runs to completion regardless of the recording outcome.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors the fetch-seam shape of {@link makeRecordEngineeringContextTool}
|
|
17
|
+
* (`baseUrl` + injectable `getToken` + `fetchImpl`) so it is unit-testable
|
|
18
|
+
* without a network and reuses the same auth convention.
|
|
19
|
+
*/
|
|
20
|
+
/** Options for {@link makeRunSession}. */
|
|
21
|
+
export interface RunSessionOptions {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
getToken: () => string | undefined;
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
/**
|
|
26
|
+
* Backoff sleep + jitter seams, threaded into {@link resilientFetch} so the
|
|
27
|
+
* retry path is deterministic and instant under test. Default to the real
|
|
28
|
+
* timer / Math.random in production.
|
|
29
|
+
*/
|
|
30
|
+
sleep?: (ms: number) => Promise<void>;
|
|
31
|
+
random?: () => number;
|
|
32
|
+
/**
|
|
33
|
+
* Opt-in debug sink for diagnosing a recording failure (the fail-soft path
|
|
34
|
+
* otherwise collapses 402/5xx/2xx-bad-body/unreachable into one silent
|
|
35
|
+
* "error"). Only ever receives a token-free line: the path, the numeric HTTP
|
|
36
|
+
* status, a capped slice of the SERVER response body, or a network error
|
|
37
|
+
* message. NEVER the request headers (the bearer token) or the request body.
|
|
38
|
+
* Injected for deterministic tests; defaults to an env-gated stderr writer.
|
|
39
|
+
*/
|
|
40
|
+
debug?: (line: string) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Environment seam for the debug gate, matching `config.ts`/`verify.ts`. The
|
|
43
|
+
* debug sink only fires when `YAGNI_CODE_DEBUG` is set here (default
|
|
44
|
+
* `process.env`), so existing callers that inject neither `debug` nor `env`
|
|
45
|
+
* stay silent unless the operator opts in.
|
|
46
|
+
*/
|
|
47
|
+
env?: NodeJS.ProcessEnv;
|
|
48
|
+
}
|
|
49
|
+
/** The handle returned by a successful `start` (or supplied via `bind`). */
|
|
50
|
+
export interface RunSessionHandle {
|
|
51
|
+
runId: string;
|
|
52
|
+
workItemId: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The result of a successful `start`: the handle plus the server's governance
|
|
56
|
+
* verdict (`gated` = the work item needs approval in the app before it shows as
|
|
57
|
+
* executing, so /go should warn rather than silently proceed).
|
|
58
|
+
*/
|
|
59
|
+
export interface RunStartResult extends RunSessionHandle {
|
|
60
|
+
gated: boolean;
|
|
61
|
+
gatedReason?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Why a run could not be recorded, derived from the endpoint's HTTP status so
|
|
65
|
+
* the caller can show an actionable notice:
|
|
66
|
+
* - `disabled` — 404: the `yagni_code.work_tracking` flag is off for this
|
|
67
|
+
* workspace (the write endpoints 404 to hide themselves).
|
|
68
|
+
* - `unauthorized` — 401/403: the device token is missing/expired or lacks the
|
|
69
|
+
* `yagni_code:write` scope (re-run `yagni login`).
|
|
70
|
+
* - `error` — anything else: an over-budget 402, a 5xx, a 2xx with an
|
|
71
|
+
* unrecognized body, or an unreachable backend / thrown
|
|
72
|
+
* network error.
|
|
73
|
+
*/
|
|
74
|
+
export type RunTrackingFailure = "disabled" | "unauthorized" | "error";
|
|
75
|
+
/**
|
|
76
|
+
* The outcome of opening a run. `tracked: true` carries the handle + gate
|
|
77
|
+
* verdict; `tracked: false` carries the reason so `/go` can warn that this run
|
|
78
|
+
* won't appear on the Work page. `start` never returns null and never throws.
|
|
79
|
+
*/
|
|
80
|
+
export type RunStartOutcome = ({
|
|
81
|
+
tracked: true;
|
|
82
|
+
} & RunStartResult) | {
|
|
83
|
+
tracked: false;
|
|
84
|
+
reason: RunTrackingFailure;
|
|
85
|
+
};
|
|
86
|
+
/** Arguments to open a run. */
|
|
87
|
+
export interface StartArgs {
|
|
88
|
+
arg: string;
|
|
89
|
+
repo?: string;
|
|
90
|
+
branch?: string;
|
|
91
|
+
}
|
|
92
|
+
/** A pipeline stage boundary event. */
|
|
93
|
+
export interface StageArgs {
|
|
94
|
+
stage: string;
|
|
95
|
+
phase: "start" | "finish";
|
|
96
|
+
round?: number;
|
|
97
|
+
findings?: number;
|
|
98
|
+
}
|
|
99
|
+
/** An in-loop correction captured during the run. */
|
|
100
|
+
export interface CorrectionArgs {
|
|
101
|
+
stage?: string;
|
|
102
|
+
question: string;
|
|
103
|
+
decision: string;
|
|
104
|
+
rationale?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The /go PLAN, recorded onto the work item (a `plan_recorded` activity beat) so
|
|
108
|
+
* it is clear what the run set out to do. `body` is the plan markdown; the server
|
|
109
|
+
* scrubs secrets + caps it before persisting.
|
|
110
|
+
*/
|
|
111
|
+
export interface RecordPlanArgs {
|
|
112
|
+
stage: string;
|
|
113
|
+
body: string;
|
|
114
|
+
}
|
|
115
|
+
/** How a run ended; maps to the run-status enum server-side. */
|
|
116
|
+
export interface FinishArgs {
|
|
117
|
+
outcome: "completed" | "stopped" | "failed";
|
|
118
|
+
stopReason?: string;
|
|
119
|
+
prUrl?: string;
|
|
120
|
+
prNumber?: number;
|
|
121
|
+
branch?: string;
|
|
122
|
+
commitSha?: string;
|
|
123
|
+
rounds?: number;
|
|
124
|
+
/**
|
|
125
|
+
* Short "what was done" recap; the server records it as a `run_outcome`
|
|
126
|
+
* activity (the end-of-run beat on the Work-page timeline). Optional + additive:
|
|
127
|
+
* `finish` already spreads the whole args object, so it flows through untouched.
|
|
128
|
+
*/
|
|
129
|
+
outcomeSummary?: string;
|
|
130
|
+
}
|
|
131
|
+
/** The run-session object returned by {@link makeRunSession}. */
|
|
132
|
+
export interface RunSession {
|
|
133
|
+
/** Adopt an externally-known handle (used by tests + cloud callers). */
|
|
134
|
+
bind(h: RunSessionHandle): void;
|
|
135
|
+
/** Open the run; resolves to a tracked outcome (handle + gate verdict) or an untracked reason. Never throws. */
|
|
136
|
+
start(b: StartArgs): Promise<RunStartOutcome>;
|
|
137
|
+
/** Stream a stage boundary; no-op until a handle exists. */
|
|
138
|
+
stage(b: StageArgs): Promise<void>;
|
|
139
|
+
/** Bank an in-loop correction; no-op until a handle exists. */
|
|
140
|
+
correction(b: CorrectionArgs): Promise<void>;
|
|
141
|
+
/** Record the /go plan onto the work item; no-op until a handle exists. */
|
|
142
|
+
recordPlan(b: RecordPlanArgs): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Bump the run's server-side last-activity heartbeat so a long-but-alive run is
|
|
145
|
+
* not reaped as stale. No-op until a handle exists; never throws.
|
|
146
|
+
*/
|
|
147
|
+
heartbeat(): Promise<void>;
|
|
148
|
+
/** Close the run with an outcome; no-op until a handle exists. */
|
|
149
|
+
finish(b: FinishArgs): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
export declare function makeRunSession(opts: RunSessionOptions): RunSession;
|
|
152
|
+
//# sourceMappingURL=runSession.d.ts.map
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/go` run-session adapter.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the device-token `/api/yagni-code/runs*` endpoints so a YAGNI Code
|
|
5
|
+
* pipeline run becomes a tracked `runs` row visible on the Work page: open the
|
|
6
|
+
* run (`start`), stream each pipeline stage (`stage`), bank an in-loop
|
|
7
|
+
* correction (`correction`), and close it with an honest outcome (`finish`).
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL invariant: recording must NEVER break the pipeline. Every call is
|
|
10
|
+
* fail-soft — it never throws. `start` resolves to a typed outcome that
|
|
11
|
+
* distinguishes a tracked run from an untracked one (flag off, unauthorized, or
|
|
12
|
+
* the backend unreachable/erroring) so the caller can surface a notice instead
|
|
13
|
+
* of failing silently; the post-start verbs (`stage`/`correction`/`finish`) stay
|
|
14
|
+
* silent no-ops. `/go` runs to completion regardless of the recording outcome.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors the fetch-seam shape of {@link makeRecordEngineeringContextTool}
|
|
17
|
+
* (`baseUrl` + injectable `getToken` + `fetchImpl`) so it is unit-testable
|
|
18
|
+
* without a network and reuses the same auth convention.
|
|
19
|
+
*/
|
|
20
|
+
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { DEFAULT_FETCH_POLICY, METERED_POST_FETCH_POLICY, resilientFetch, } from "../resilientFetch.js";
|
|
22
|
+
/** Map a failed POST's HTTP status onto a user-facing tracking-failure reason. */
|
|
23
|
+
function failureReason(status) {
|
|
24
|
+
if (status === 404)
|
|
25
|
+
return "disabled";
|
|
26
|
+
if (status === 401 || status === 403)
|
|
27
|
+
return "unauthorized";
|
|
28
|
+
return "error";
|
|
29
|
+
}
|
|
30
|
+
/** Cap a debug body excerpt so a giant HTML/JSON error never floods the channel. */
|
|
31
|
+
const MAX_DEBUG_BODY = 200;
|
|
32
|
+
/**
|
|
33
|
+
* A token-free, capped `: <body>` suffix for a failed-response debug line. Reads
|
|
34
|
+
* the body only on the `!res.ok` branch (where `post` never reads it otherwise,
|
|
35
|
+
* so there is no double-consume with the ok-path `res.json()`), and swallows any
|
|
36
|
+
* read error so observability never breaks the fail-soft contract.
|
|
37
|
+
*/
|
|
38
|
+
async function bodyTail(res) {
|
|
39
|
+
try {
|
|
40
|
+
const t = (await res.text()).trim();
|
|
41
|
+
if (!t)
|
|
42
|
+
return "";
|
|
43
|
+
return `: ${t.length > MAX_DEBUG_BODY ? `${t.slice(0, MAX_DEBUG_BODY)} ...` : t}`;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function makeRunSession(opts) {
|
|
50
|
+
const f = opts.fetchImpl ?? fetch;
|
|
51
|
+
let handle = null;
|
|
52
|
+
// Opt-in observability: the default sink writes to stderr, and the `YAGNI_CODE_DEBUG`
|
|
53
|
+
// gate governs an injected sink too, so callers that pass neither stay silent. The
|
|
54
|
+
// resolved sink is wrapped so it can NEVER throw out of `debug`: an injected sink
|
|
55
|
+
// that throws must not breach the fail-soft never-throw contract (in the `!res.ok`
|
|
56
|
+
// branch an unguarded throw would land in the surrounding catch and re-fire debug,
|
|
57
|
+
// escaping `post` and rejecting the recording call).
|
|
58
|
+
const sink = opts.debug ??
|
|
59
|
+
((line) => {
|
|
60
|
+
try {
|
|
61
|
+
process.stderr.write(`${line}\n`);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* never break recording */
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const debug = (line) => {
|
|
68
|
+
try {
|
|
69
|
+
sink(line);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* observability never breaks recording */
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const debugOn = !!(opts.env ?? process.env).YAGNI_CODE_DEBUG;
|
|
76
|
+
// A run-open (`start`) is the one recording call worth retrying: if it is lost,
|
|
77
|
+
// the WHOLE run goes untracked. It is made replay-safe by an idempotency key (the
|
|
78
|
+
// server dedups a re-sent create), so it uses the retrying policy. Every other
|
|
79
|
+
// verb is append-only / terminal and stays single-attempt (METERED) so a recorded
|
|
80
|
+
// judgment (a correction) is never duplicated by a client replay.
|
|
81
|
+
const post = async (path, body, postOpts = {}) => {
|
|
82
|
+
try {
|
|
83
|
+
const res = await resilientFetch(`${opts.baseUrl}/api/yagni-code/${path}`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
authorization: `Bearer ${opts.getToken() ?? ""}`,
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
}, {
|
|
91
|
+
fetchImpl: f,
|
|
92
|
+
policy: postOpts.retry ? DEFAULT_FETCH_POLICY : METERED_POST_FETCH_POLICY,
|
|
93
|
+
...(opts.sleep ? { sleep: opts.sleep } : {}),
|
|
94
|
+
...(opts.random ? { random: opts.random } : {}),
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
if (debugOn)
|
|
98
|
+
debug(`[yagni-code] run-record ${path} -> HTTP ${res.status}${await bodyTail(res)}`);
|
|
99
|
+
return { ok: false, status: res.status };
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, data: (await res.json()) };
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// fail-soft: recording must never break the pipeline (no status = unreachable
|
|
105
|
+
// after retries).
|
|
106
|
+
if (debugOn) {
|
|
107
|
+
debug(`[yagni-code] run-record ${path} unreachable: ${err instanceof Error ? err.message : String(err)}`);
|
|
108
|
+
}
|
|
109
|
+
return { ok: false };
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
bind(h) {
|
|
114
|
+
handle = h;
|
|
115
|
+
},
|
|
116
|
+
async start(b) {
|
|
117
|
+
// One idempotency key per open attempt: resilientFetch replays the SAME
|
|
118
|
+
// request body across retries, so a create whose 201 was lost to a blip is
|
|
119
|
+
// deduped by the server (startCodeRun.findRunByIdempotencyKey) instead of
|
|
120
|
+
// opening a second run + a second free-text work item.
|
|
121
|
+
const r = await post("runs", { ...b, idempotencyKey: randomUUID() }, { retry: true });
|
|
122
|
+
if (!r.ok)
|
|
123
|
+
return { tracked: false, reason: failureReason(r.status) };
|
|
124
|
+
const data = r.data;
|
|
125
|
+
if (typeof data.runId === "string" && typeof data.workItemId === "string") {
|
|
126
|
+
handle = { runId: data.runId, workItemId: data.workItemId };
|
|
127
|
+
return {
|
|
128
|
+
tracked: true,
|
|
129
|
+
runId: handle.runId,
|
|
130
|
+
workItemId: handle.workItemId,
|
|
131
|
+
gated: data.gated === true,
|
|
132
|
+
gatedReason: typeof data.gatedReason === "string" ? data.gatedReason : undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// A 2xx with an unrecognized body: we can't bind a handle, so it's untracked.
|
|
136
|
+
if (debugOn)
|
|
137
|
+
debug("[yagni-code] run-record runs -> 2xx but body had no runId/workItemId");
|
|
138
|
+
return { tracked: false, reason: "error" };
|
|
139
|
+
},
|
|
140
|
+
async stage(b) {
|
|
141
|
+
if (!handle)
|
|
142
|
+
return;
|
|
143
|
+
await post(`runs/${handle.runId}/stages`, { ...b, workItemId: handle.workItemId });
|
|
144
|
+
},
|
|
145
|
+
async correction(b) {
|
|
146
|
+
if (!handle)
|
|
147
|
+
return;
|
|
148
|
+
await post(`runs/${handle.runId}/correction`, { ...b, workItemId: handle.workItemId });
|
|
149
|
+
},
|
|
150
|
+
async recordPlan(b) {
|
|
151
|
+
if (!handle)
|
|
152
|
+
return;
|
|
153
|
+
await post(`runs/${handle.runId}/plan`, { ...b, workItemId: handle.workItemId });
|
|
154
|
+
},
|
|
155
|
+
async heartbeat() {
|
|
156
|
+
if (!handle)
|
|
157
|
+
return;
|
|
158
|
+
await post(`runs/${handle.runId}/heartbeat`, { workItemId: handle.workItemId });
|
|
159
|
+
},
|
|
160
|
+
async finish(b) {
|
|
161
|
+
if (!handle)
|
|
162
|
+
return;
|
|
163
|
+
await post(`runs/${handle.runId}/finish`, { ...b, workItemId: handle.workItemId });
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=runSession.js.map
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headless model behind every `/go` progress display.
|
|
3
|
+
*
|
|
4
|
+
* `RunState` folds the structured `PipelineProgress` signals, curated
|
|
5
|
+
* `ActivityLine`s and raw assistant `JsonEvent` usage into one small model that
|
|
6
|
+
* both surfaces render from: the terminal's boxed ANSI panel (via
|
|
7
|
+
* `ActivityFeed`, which is now a pure renderer over this) and the desktop's
|
|
8
|
+
* Agents tab (via `toJSON()`, pushed as one line over `setWidget`).
|
|
9
|
+
*
|
|
10
|
+
* It owns no I/O and no clock — the caller injects `at` — so it is fully
|
|
11
|
+
* deterministic in tests, exactly like the feed it replaces.
|
|
12
|
+
*
|
|
13
|
+
* Two properties are load-bearing:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Lens events never touch their parent stage.** A `stage_start` /
|
|
16
|
+
* `stage_done` carrying a `lens` mutates only that lens's agent. Without
|
|
17
|
+
* this the per-lens signals would mark the terminal's `review` row done
|
|
18
|
+
* while two lenses were still running.
|
|
19
|
+
*
|
|
20
|
+
* 2. **Per-agent action lists only.** The terminal's flat, arrival-ordered ring
|
|
21
|
+
* buffer stays in `ActivityFeed`: it exists to fit pi's widget cap, which is
|
|
22
|
+
* a property of that renderer rather than of the run. Keeping it there means
|
|
23
|
+
* the panel's cross-lens interleaving is preserved by code that never moved.
|
|
24
|
+
*/
|
|
25
|
+
import type { ActivityLine, FeedStageId, JsonEvent, PipelineProgress, ReviewLens, StageTag, StopReason } from "./types.js";
|
|
26
|
+
/** The fixed stage order shown by both surfaces (v1 stages + the post-loop FINISH row). */
|
|
27
|
+
export declare const STAGE_ORDER: FeedStageId[];
|
|
28
|
+
/**
|
|
29
|
+
* Per-agent action cap. Generous compared to the terminal's `MAX_VISIBLE_ACTIONS`
|
|
30
|
+
* because the desktop pane scrolls: the TUI's 4-row ceiling is a widget-height
|
|
31
|
+
* constraint, not a truth about how much happened.
|
|
32
|
+
*/
|
|
33
|
+
export declare const AGENT_ACTION_WINDOW = 40;
|
|
34
|
+
export type AgentStatus = "pending" | "active" | "done" | "failed";
|
|
35
|
+
export interface AgentAction {
|
|
36
|
+
toolCallId?: string;
|
|
37
|
+
text: string;
|
|
38
|
+
state: "running" | "done" | "error";
|
|
39
|
+
}
|
|
40
|
+
export interface AgentUsage {
|
|
41
|
+
input: number;
|
|
42
|
+
output: number;
|
|
43
|
+
cacheRead: number;
|
|
44
|
+
cacheWrite: number;
|
|
45
|
+
cost: number;
|
|
46
|
+
turns: number;
|
|
47
|
+
}
|
|
48
|
+
export interface AgentNode {
|
|
49
|
+
/**
|
|
50
|
+
* Stable across paints: the stage id, `review:<lens>:r<round>` for a lens
|
|
51
|
+
* child, or `fix:r<round>` for a per-round fix execution.
|
|
52
|
+
*/
|
|
53
|
+
id: string;
|
|
54
|
+
stageId: FeedStageId;
|
|
55
|
+
lens?: ReviewLens;
|
|
56
|
+
round?: number;
|
|
57
|
+
status: AgentStatus;
|
|
58
|
+
summary: string;
|
|
59
|
+
startedAt?: number;
|
|
60
|
+
endedAt?: number;
|
|
61
|
+
actions: AgentAction[];
|
|
62
|
+
usage: AgentUsage;
|
|
63
|
+
/** Per-lens finding counts, attached when the round's parse loop resolves this lens. */
|
|
64
|
+
findings?: number;
|
|
65
|
+
blocking?: number;
|
|
66
|
+
/** Why this lens could not fully carry the round (spec §3e). */
|
|
67
|
+
degraded?: "failed" | "unparseable";
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One completed round's finding tally, kept as history rather than overwritten:
|
|
71
|
+
* the trajectory (blocking 6 → 5 → 5) is what tells whether the loop converged.
|
|
72
|
+
*/
|
|
73
|
+
export interface RoundTally {
|
|
74
|
+
round: number;
|
|
75
|
+
findings: number;
|
|
76
|
+
blocking: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The wire record. Deliberately carries NO local-only fields — no worktree paths,
|
|
80
|
+
* no pids, no absolute paths — so it stays portable to a non-local executor at
|
|
81
|
+
* zero cost. The tab does not need them.
|
|
82
|
+
*/
|
|
83
|
+
export interface GoRunState {
|
|
84
|
+
v: 1;
|
|
85
|
+
runId: string;
|
|
86
|
+
ticket: string;
|
|
87
|
+
round: number;
|
|
88
|
+
findingsTotal: number;
|
|
89
|
+
findingsBlocking: number;
|
|
90
|
+
rounds: RoundTally[];
|
|
91
|
+
startedAt: number;
|
|
92
|
+
endedAt?: number;
|
|
93
|
+
stopReason?: StopReason;
|
|
94
|
+
agents: AgentNode[];
|
|
95
|
+
/**
|
|
96
|
+
* The backend's run UUID (`handle.runId` from `POST /runs`, goCommand.ts's
|
|
97
|
+
* `openFreshRun`) — NOT `runId` above, which is the local 8-char short id
|
|
98
|
+
* (`runId.slice(0, 8)`) used for widget/status keys. `/api/yagni-code/spend`
|
|
99
|
+
* joins on the server UUID, so the desktop needs this to ever widen its
|
|
100
|
+
* spend query with a /go run's own dispatches. Absent until `openFreshRun`
|
|
101
|
+
* resolves (a beat after the run starts), and absent for good on an
|
|
102
|
+
* untracked run (flag off, unauthorized, or unreachable — see
|
|
103
|
+
* `UNTRACKED_NOTICE` in goCommand.ts): there is no server id to give it.
|
|
104
|
+
*/
|
|
105
|
+
serverRunId?: string;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Stable agent id: a lens child is keyed by its lens AND round, and fix — the
|
|
109
|
+
* only lens-less stage that re-executes per round — by its round, so each
|
|
110
|
+
* round's fix keeps its own clock and usage instead of accumulating tokens on a
|
|
111
|
+
* timer that resets (the "181k tok in 59s" artifact). Review stage-level events
|
|
112
|
+
* stay on the bare `review` node: they mark the round boundary, and the lens
|
|
113
|
+
* children carry the round's real work.
|
|
114
|
+
*/
|
|
115
|
+
export declare function agentId(stageId: FeedStageId, lens?: ReviewLens, round?: number): string;
|
|
116
|
+
export interface RunStateInit {
|
|
117
|
+
runId: string;
|
|
118
|
+
ticket: string;
|
|
119
|
+
startedAt: number;
|
|
120
|
+
}
|
|
121
|
+
export declare class RunState {
|
|
122
|
+
private readonly runId;
|
|
123
|
+
private readonly ticket;
|
|
124
|
+
private readonly startedAt;
|
|
125
|
+
private endedAt;
|
|
126
|
+
private stopReason;
|
|
127
|
+
private serverRunId;
|
|
128
|
+
private round;
|
|
129
|
+
private findingsTotal;
|
|
130
|
+
private findingsBlocking;
|
|
131
|
+
private readonly rounds;
|
|
132
|
+
private readonly agents;
|
|
133
|
+
constructor(init: RunStateInit);
|
|
134
|
+
/** The lens-less stage agent, or undefined for an unknown stage. */
|
|
135
|
+
private stageAgent;
|
|
136
|
+
/**
|
|
137
|
+
* The agent for a tag, created on demand. Lens children are inserted directly
|
|
138
|
+
* after their parent stage and any existing siblings, so the array order still
|
|
139
|
+
* reads top-to-bottom for the desktop and the lens-less filter still yields
|
|
140
|
+
* STAGE_ORDER for the terminal.
|
|
141
|
+
*/
|
|
142
|
+
private ensureAgent;
|
|
143
|
+
/** Fold a structured progress signal. `at` is only read by the desktop surface. */
|
|
144
|
+
applyProgress(p: PipelineProgress, at?: number): void;
|
|
145
|
+
/**
|
|
146
|
+
* Fold one curated line. Narration becomes the emitting agent's summary;
|
|
147
|
+
* actions ride BOTH the agent's own list and the terminal's flat ring buffer.
|
|
148
|
+
*/
|
|
149
|
+
applyLine(line: ActivityLine, tag: StageTag, at?: number): void;
|
|
150
|
+
/** Fold an assistant turn's usage into the emitting agent. Ignores everything else. */
|
|
151
|
+
applyUsage(ev: JsonEvent, tag: StageTag, at?: number): void;
|
|
152
|
+
/** Stamp the run's end. Separate from `done` because FINISH runs after the pipeline returns. */
|
|
153
|
+
markEnded(at: number): void;
|
|
154
|
+
/**
|
|
155
|
+
* Record the backend's run UUID once `openFreshRun` resolves (goCommand.ts,
|
|
156
|
+
* a beat after the run starts — never known at construction). The caller is
|
|
157
|
+
* responsible for repainting immediately after this so the desktop learns
|
|
158
|
+
* it as soon as possible rather than on the next debounced paint.
|
|
159
|
+
*/
|
|
160
|
+
setServerRunId(id: string): void;
|
|
161
|
+
/**
|
|
162
|
+
* Stage rows for the panel: one row per stage, in order. Lens children are the
|
|
163
|
+
* desktop's business alone, and a looped stage that re-executes per round
|
|
164
|
+
* (fix) paints its LATEST round's node so the row shows the live execution,
|
|
165
|
+
* not a stale bare placeholder.
|
|
166
|
+
*/
|
|
167
|
+
stageAgents(): AgentNode[];
|
|
168
|
+
header(): {
|
|
169
|
+
ticket: string;
|
|
170
|
+
round: number;
|
|
171
|
+
findingsTotal: number;
|
|
172
|
+
findingsBlocking: number;
|
|
173
|
+
};
|
|
174
|
+
/** Named `toJSON` so `JSON.stringify(run)` yields the wire shape directly. */
|
|
175
|
+
toJSON(): GoRunState;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=runState.d.ts.map
|