opensquid 0.5.412 → 0.5.426
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/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/functions/event.d.ts.map +1 -1
- package/dist/functions/event.js +30 -0
- package/dist/functions/event.js.map +1 -1
- package/dist/functions/index.d.ts +2 -0
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +2 -0
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/read_rubric.d.ts +27 -0
- package/dist/functions/read_rubric.d.ts.map +1 -0
- package/dist/functions/read_rubric.js +53 -0
- package/dist/functions/read_rubric.js.map +1 -0
- package/dist/functions/rubric_pre_inject.d.ts +22 -0
- package/dist/functions/rubric_pre_inject.d.ts.map +1 -0
- package/dist/functions/rubric_pre_inject.js +58 -0
- package/dist/functions/rubric_pre_inject.js.map +1 -0
- package/dist/functions/shell_parse.d.ts +41 -0
- package/dist/functions/shell_parse.d.ts.map +1 -0
- package/dist/functions/shell_parse.js +185 -0
- package/dist/functions/shell_parse.js.map +1 -0
- package/dist/mcp/server.js +14 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/ralph.d.ts +21 -0
- package/dist/mcp/tools/ralph.d.ts.map +1 -0
- package/dist/mcp/tools/ralph.js +18 -0
- package/dist/mcp/tools/ralph.js.map +1 -0
- package/dist/mcp/tools/workgraph.d.ts +11 -0
- package/dist/mcp/tools/workgraph.d.ts.map +1 -1
- package/dist/mcp/tools/workgraph.js +7 -0
- package/dist/mcp/tools/workgraph.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts.map +1 -1
- package/dist/runtime/bootstrap.js +4 -0
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/runtime/ralph/decision_classifier.d.ts +18 -0
- package/dist/runtime/ralph/decision_classifier.d.ts.map +1 -0
- package/dist/runtime/ralph/decision_classifier.js +72 -0
- package/dist/runtime/ralph/decision_classifier.js.map +1 -0
- package/dist/runtime/ralph/escalate_lap.d.ts +31 -0
- package/dist/runtime/ralph/escalate_lap.d.ts.map +1 -0
- package/dist/runtime/ralph/escalate_lap.js +22 -0
- package/dist/runtime/ralph/escalate_lap.js.map +1 -0
- package/dist/runtime/ralph/escalator.d.ts +34 -0
- package/dist/runtime/ralph/escalator.d.ts.map +1 -0
- package/dist/runtime/ralph/escalator.js +19 -0
- package/dist/runtime/ralph/escalator.js.map +1 -0
- package/dist/runtime/ralph/lap_outcome.d.ts +43 -0
- package/dist/runtime/ralph/lap_outcome.d.ts.map +1 -0
- package/dist/runtime/ralph/lap_outcome.js +119 -0
- package/dist/runtime/ralph/lap_outcome.js.map +1 -0
- package/dist/runtime/ralph/orchestrator.d.ts +71 -0
- package/dist/runtime/ralph/orchestrator.d.ts.map +1 -0
- package/dist/runtime/ralph/orchestrator.js +74 -0
- package/dist/runtime/ralph/orchestrator.js.map +1 -0
- package/dist/runtime/ralph/ralph_template.d.ts +18 -0
- package/dist/runtime/ralph/ralph_template.d.ts.map +1 -0
- package/dist/runtime/ralph/ralph_template.js +61 -0
- package/dist/runtime/ralph/ralph_template.js.map +1 -0
- package/dist/runtime/ralph/supervisor.d.ts +30 -0
- package/dist/runtime/ralph/supervisor.d.ts.map +1 -0
- package/dist/runtime/ralph/supervisor.js +24 -0
- package/dist/runtime/ralph/supervisor.js.map +1 -0
- package/dist/setup/cli/ralph.d.ts +20 -0
- package/dist/setup/cli/ralph.d.ts.map +1 -0
- package/dist/setup/cli/ralph.js +168 -0
- package/dist/setup/cli/ralph.js.map +1 -0
- package/dist/setup/wizard/ralph_writer.d.ts +98 -0
- package/dist/setup/wizard/ralph_writer.d.ts.map +1 -0
- package/dist/setup/wizard/ralph_writer.js +114 -0
- package/dist/setup/wizard/ralph_writer.js.map +1 -0
- package/dist/workgraph/audience.d.ts +12 -0
- package/dist/workgraph/audience.d.ts.map +1 -0
- package/dist/workgraph/audience.js +10 -0
- package/dist/workgraph/audience.js.map +1 -0
- package/dist/workgraph/events.d.ts.map +1 -1
- package/dist/workgraph/events.js +34 -0
- package/dist/workgraph/events.js.map +1 -1
- package/dist/workgraph/store.d.ts.map +1 -1
- package/dist/workgraph/store.js +86 -12
- package/dist/workgraph/store.js.map +1 -1
- package/dist/workgraph/types.d.ts +33 -2
- package/dist/workgraph/types.d.ts.map +1 -1
- package/docs/rubric/author.md +17 -0
- package/docs/rubric/scope.md +16 -0
- package/package.json +3 -2
- package/packs/builtin/coding-flow/skills/entry-and-handoffs/skill.yaml +9 -0
- package/packs/builtin/coding-flow/skills/execute-gate/skill.yaml +9 -10
- package/packs/builtin/coding-flow/skills/scope-lifecycle/skill.yaml +67 -50
- package/packs/builtin/default-discipline/manifest.yaml +6 -3
- package/packs/builtin/default-discipline/skills/workflow/skill.yaml +7 -3
- package/packs/builtin/pack-architect/SKILL.md +10 -0
- package/packs/builtin/pack-architect/skills/skill-yaml-author-walkthrough/skill.yaml +7 -0
- package/packs/builtin/scope-architect/team.yaml +5 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GR.2 — the typed-exit contract for a gated-ralph lap (Inv 5: ONE typed exit).
|
|
3
|
+
*
|
|
4
|
+
* A lap is a headless `claude -p … --output-format json` run of `RALPH.md`. Its envelope is the
|
|
5
|
+
* empirically-verified shape `{ result, is_error, subtype, total_cost_usd, … }` (verified 2026-06-13).
|
|
6
|
+
* The lap signals its outcome by emitting a single greppable line in `result`:
|
|
7
|
+
*
|
|
8
|
+
* RALPH-EXIT: {"kind":"HUMAN_REQUIRED","reason":"SCOPE_FORK","payload":{…}}
|
|
9
|
+
*
|
|
10
|
+
* `parseLapOutcome` is a TOTAL mapping of every envelope shape to a `LapOutcome` — it never throws and
|
|
11
|
+
* never returns a false `SHIPPED`: an unparseable envelope or `is_error` becomes `CRASH`; a clean
|
|
12
|
+
* envelope with no tag becomes `SHIPPED`. The orchestrator (GR.4) switches on the result.
|
|
13
|
+
*
|
|
14
|
+
* Imported by: src/runtime/ralph/orchestrator.ts (GR.4), src/runtime/ralph/supervisor.ts (GR.3).
|
|
15
|
+
*/
|
|
16
|
+
const HUMAN_REQUIRED_REASONS = [
|
|
17
|
+
'IRREVERSIBLE_BOUNDARY',
|
|
18
|
+
'SCOPE_FORK',
|
|
19
|
+
'UNRECOVERABLE_WEDGE',
|
|
20
|
+
'BUDGET',
|
|
21
|
+
'RATE_BUDGET',
|
|
22
|
+
'BOARD_EMPTY',
|
|
23
|
+
];
|
|
24
|
+
const isReason = (v) => typeof v === 'string' && HUMAN_REQUIRED_REASONS.includes(v);
|
|
25
|
+
const TAG = 'RALPH-EXIT:';
|
|
26
|
+
/**
|
|
27
|
+
* Scan the lap's free-text result for the LAST `RALPH-EXIT: {json}` line and validate it into a
|
|
28
|
+
* `LapOutcome`. Returns null when there is no well-formed tag (caller defaults to SHIPPED on a clean
|
|
29
|
+
* exit). Defensive by construction — a malformed tag is treated as "no tag".
|
|
30
|
+
*/
|
|
31
|
+
export function extractTypedExit(resultText) {
|
|
32
|
+
const idx = resultText.lastIndexOf(TAG);
|
|
33
|
+
if (idx === -1)
|
|
34
|
+
return null;
|
|
35
|
+
const after = resultText.slice(idx + TAG.length).trimStart();
|
|
36
|
+
const json = sliceFirstJsonObject(after);
|
|
37
|
+
if (json === null)
|
|
38
|
+
return null;
|
|
39
|
+
let obj;
|
|
40
|
+
try {
|
|
41
|
+
obj = JSON.parse(json);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (obj === null || typeof obj !== 'object')
|
|
47
|
+
return null;
|
|
48
|
+
const rec = obj;
|
|
49
|
+
switch (rec.kind) {
|
|
50
|
+
case 'SHIPPED':
|
|
51
|
+
return { kind: 'SHIPPED' };
|
|
52
|
+
case 'WEDGE':
|
|
53
|
+
return { kind: 'WEDGE' };
|
|
54
|
+
case 'TIMEOUT':
|
|
55
|
+
return { kind: 'TIMEOUT' };
|
|
56
|
+
case 'CRASH':
|
|
57
|
+
return { kind: 'CRASH' };
|
|
58
|
+
case 'HUMAN_REQUIRED':
|
|
59
|
+
if (!isReason(rec.reason))
|
|
60
|
+
return null; // a HUMAN_REQUIRED without a valid reason is malformed
|
|
61
|
+
return {
|
|
62
|
+
kind: 'HUMAN_REQUIRED',
|
|
63
|
+
reason: rec.reason,
|
|
64
|
+
...(typeof rec.item === 'string' ? { item: rec.item } : {}),
|
|
65
|
+
...(rec.payload === undefined ? {} : { payload: rec.payload }),
|
|
66
|
+
};
|
|
67
|
+
default:
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Parse the headless JSON envelope into a total `LapOutcome` + the lap's cost. Never throws. */
|
|
72
|
+
export function parseLapOutcome(stdout) {
|
|
73
|
+
let env;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(stdout);
|
|
76
|
+
if (parsed === null || typeof parsed !== 'object')
|
|
77
|
+
return { outcome: { kind: 'CRASH' }, costUsd: 0 };
|
|
78
|
+
env = parsed;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return { outcome: { kind: 'CRASH' }, costUsd: 0 }; // unparseable envelope = CRASH, never SHIPPED
|
|
82
|
+
}
|
|
83
|
+
const costUsd = typeof env.total_cost_usd === 'number' ? env.total_cost_usd : 0;
|
|
84
|
+
if (env.is_error === true)
|
|
85
|
+
return { outcome: { kind: 'CRASH' }, costUsd };
|
|
86
|
+
const tagged = extractTypedExit(typeof env.result === 'string' ? env.result : '');
|
|
87
|
+
return { outcome: tagged ?? { kind: 'SHIPPED' }, costUsd }; // clean exit, no tag = SHIPPED
|
|
88
|
+
}
|
|
89
|
+
/** Return the first balanced `{…}` JSON object substring starting at `s[0]`, or null. */
|
|
90
|
+
function sliceFirstJsonObject(s) {
|
|
91
|
+
if (!s.startsWith('{'))
|
|
92
|
+
return null;
|
|
93
|
+
let depth = 0;
|
|
94
|
+
let inStr = false;
|
|
95
|
+
let esc = false;
|
|
96
|
+
for (let i = 0; i < s.length; i++) {
|
|
97
|
+
const c = s[i];
|
|
98
|
+
if (inStr) {
|
|
99
|
+
if (esc)
|
|
100
|
+
esc = false;
|
|
101
|
+
else if (c === '\\')
|
|
102
|
+
esc = true;
|
|
103
|
+
else if (c === '"')
|
|
104
|
+
inStr = false;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (c === '"')
|
|
108
|
+
inStr = true;
|
|
109
|
+
else if (c === '{')
|
|
110
|
+
depth++;
|
|
111
|
+
else if (c === '}') {
|
|
112
|
+
depth--;
|
|
113
|
+
if (depth === 0)
|
|
114
|
+
return s.slice(0, i + 1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=lap_outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lap_outcome.js","sourceRoot":"","sources":["../../../src/runtime/ralph/lap_outcome.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GR.4 — the gated-ralph orchestrator: a thin, non-LLM loop that composes GR.1–3.
|
|
3
|
+
*
|
|
4
|
+
* One pass: read the oldest ready item (GR.1 `listReady`) → atomically claim it (GR.1 CAS) → run a
|
|
5
|
+
* supervised lap (GR.3 `superviseLap` over an injected lap-runner that wraps `claude -p RALPH.md` +
|
|
6
|
+
* GR.2 `parseLapOutcome`) → act on the typed `LapOutcome` (GR.2) → repeat until BOARD_EMPTY or a
|
|
7
|
+
* stopping escalation. No new state store (claim/wedge are work-graph ops); no new gate logic (the lap
|
|
8
|
+
* is gated by the same coding-flow); ONE uniform escalation (GR.3 `escalateLap`).
|
|
9
|
+
*
|
|
10
|
+
* Dependencies are INJECTED (`RalphDeps`) so the loop is pure composition and unit-testable without the
|
|
11
|
+
* notification stack or a real `claude` subprocess — the CLI (`ralph.ts`) wires the real store, the
|
|
12
|
+
* `claude -p` lap-runner, and `escalateSeverity`.
|
|
13
|
+
*
|
|
14
|
+
* The S1 spike PROVED spawn-a-lap is safe (a nested `--dangerously-skip-permissions` lap's ungated
|
|
15
|
+
* commit was blocked by the gate), so this loop spawns laps directly rather than via Alternative F.
|
|
16
|
+
*/
|
|
17
|
+
import type { Issue, WorkGraphStore, ClaimAudience } from '../../workgraph/types.js';
|
|
18
|
+
import type { HumanRequiredReason } from './lap_outcome.js';
|
|
19
|
+
import type { LapResult, SuperviseOpts } from './supervisor.js';
|
|
20
|
+
import { type LapEscalator } from './escalate_lap.js';
|
|
21
|
+
import type { DecisionVerdict } from './decision_classifier.js';
|
|
22
|
+
export interface RalphConfig {
|
|
23
|
+
/** Auth mode from CONFIG (Inv 11; no runtime auto-detect). API → dollar budget; subscription → W. */
|
|
24
|
+
authMode: 'api' | 'subscription';
|
|
25
|
+
/** API dollar bound — the running sum of the verified `total_cost_usd` (Inv 11). API mode only. */
|
|
26
|
+
maxBudgetUsd: number;
|
|
27
|
+
/** TTL for the per-item claim (GR.1). */
|
|
28
|
+
claimTtlSec: number;
|
|
29
|
+
/** Stop after one item (the `--once` flag). */
|
|
30
|
+
once: boolean;
|
|
31
|
+
/** GR.3 supervisor opts (retry cap, backoff, heartbeat). */
|
|
32
|
+
supervise: SuperviseOpts;
|
|
33
|
+
}
|
|
34
|
+
/** Why the loop stopped. NB: the per-item residual reasons (IRREVERSIBLE_BOUNDARY / SCOPE_FORK /
|
|
35
|
+
* UNRECOVERABLE_WEDGE) never STOP the loop — they park the item and the loop takes the next. Only the
|
|
36
|
+
* RESOURCE pauses + an explicit `--once` end the run. */
|
|
37
|
+
export type RalphStop = 'BOARD_EMPTY' | 'BUDGET' | 'RATE_BUDGET' | 'once';
|
|
38
|
+
export interface RalphResult {
|
|
39
|
+
stopped: RalphStop;
|
|
40
|
+
spent: number;
|
|
41
|
+
closed: string[];
|
|
42
|
+
parked: {
|
|
43
|
+
id: string;
|
|
44
|
+
reason: HumanRequiredReason;
|
|
45
|
+
}[];
|
|
46
|
+
}
|
|
47
|
+
export interface RalphDeps {
|
|
48
|
+
wg: WorkGraphStore;
|
|
49
|
+
/** Env-derived claim audience (GR.1 — never caller input). */
|
|
50
|
+
claimAudience: () => ClaimAudience;
|
|
51
|
+
/** Run ONE lap for an item → its typed outcome + cost. The CLI wraps `claude -p RALPH.md` + parseLapOutcome. */
|
|
52
|
+
runLap: (item: Issue) => Promise<LapResult>;
|
|
53
|
+
/** The undroppable escalation transport (GR.3) — the CLI wires `escalateSeverity`. */
|
|
54
|
+
escalate: LapEscalator;
|
|
55
|
+
}
|
|
56
|
+
export declare function runRalphLoop(cfg: RalphConfig, deps: RalphDeps): Promise<RalphResult>;
|
|
57
|
+
/** GR.2's recordMisclassification, injected (kept testable / decoupled). */
|
|
58
|
+
export type RecordMisclassification = (sessionId: string, expected: DecisionVerdict, got: DecisionVerdict, decision: string, nowIso: string) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* `opensquid loop resolve <itemId> --misclassified` — the human-override INPUT (post-hoc residual-shrink
|
|
61
|
+
* path; NOT the hot loop). The human marks a parked escalation as principle-settleable: we record the
|
|
62
|
+
* misclassification (the heuristic said ESCALATE, the correct verdict was DECIDE) and un-wedge the item so
|
|
63
|
+
* it re-enters `ready` for another lap. The SOLE caller of GR.2's recordMisclassification.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveParked(itemId: string, deps: {
|
|
66
|
+
wg: WorkGraphStore;
|
|
67
|
+
recordMisclassification: RecordMisclassification;
|
|
68
|
+
sessionId: string;
|
|
69
|
+
nowIso: string;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/runtime/ralph/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,qGAAqG;IACrG,QAAQ,EAAE,KAAK,GAAG,cAAc,CAAC;IACjC,mGAAmG;IACnG,YAAY,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,IAAI,EAAE,OAAO,CAAC;IACd,4DAA4D;IAC5D,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED;;yDAEyD;AACzD,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAA;KAAE,EAAE,CAAC;CACvD;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,cAAc,CAAC;IACnB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,aAAa,CAAC;IACnC,gHAAgH;IAChH,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5C,sFAAsF;IACtF,QAAQ,EAAE,YAAY,CAAC;CACxB;AAOD,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CA0D1F;AAED,4EAA4E;AAC5E,MAAM,MAAM,uBAAuB,GAAG,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IACJ,EAAE,EAAE,cAAc,CAAC;IACnB,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { superviseLap } from './supervisor.js';
|
|
2
|
+
import { escalateLap } from './escalate_lap.js';
|
|
3
|
+
/** Resource pauses END the run; everything else is per-item decision-residual that parks + continues. */
|
|
4
|
+
const RESOURCE_PAUSES = ['BUDGET', 'RATE_BUDGET', 'BOARD_EMPTY'];
|
|
5
|
+
const isResourcePause = (r) => RESOURCE_PAUSES.includes(r);
|
|
6
|
+
export async function runRalphLoop(cfg, deps) {
|
|
7
|
+
const { wg } = deps;
|
|
8
|
+
const closed = [];
|
|
9
|
+
const parked = [];
|
|
10
|
+
let spent = 0;
|
|
11
|
+
// THE single uniform stop-layer (Inv 5): every reason chat-escalates through ONE path — no per-trigger
|
|
12
|
+
// code paths (rejects Alt D). Two SEPARATE, explicit decisions ride alongside it: (a) wedge-mark ONLY the
|
|
13
|
+
// per-item residual (IRREVERSIBLE_BOUNDARY/SCOPE_FORK/UNRECOVERABLE_WEDGE) — a resource pause
|
|
14
|
+
// (BUDGET/RATE_BUDGET/BOARD_EMPTY) is TRANSIENT, so the item stays in `ready` to retry after the
|
|
15
|
+
// window/budget resets (wedge-marking it would PERMANENTLY park healthy work); (b) continue-vs-stop is
|
|
16
|
+
// isResourcePause at the call sites.
|
|
17
|
+
const parkAndEscalate = async (reason, item) => {
|
|
18
|
+
if (item !== undefined && !isResourcePause(reason)) {
|
|
19
|
+
await wg.wedgeMark(item.id, reason); // a marked item SKIPS on re-attempt (residual only — GR.3)
|
|
20
|
+
parked.push({ id: item.id, reason });
|
|
21
|
+
}
|
|
22
|
+
// GR.3 — undroppable. Omit `item` entirely when absent (exactOptionalPropertyTypes: no explicit undefined).
|
|
23
|
+
await escalateLap(reason, item === undefined ? { escalate: deps.escalate } : { item: item.id, escalate: deps.escalate });
|
|
24
|
+
};
|
|
25
|
+
for (;;) {
|
|
26
|
+
const ready = await wg.listReady(); // GR.1 ordering, claim+wedge aware (live-claimed items excluded)
|
|
27
|
+
if (ready.length === 0) {
|
|
28
|
+
await parkAndEscalate('BOARD_EMPTY'); // resource pause → escalate + STOP
|
|
29
|
+
return { stopped: 'BOARD_EMPTY', spent, closed, parked };
|
|
30
|
+
}
|
|
31
|
+
const item = ready[0]; // oldest-first (shipped listReady ORDER BY created_at; no priority column)
|
|
32
|
+
if (item === undefined)
|
|
33
|
+
continue; // unreachable (length checked) — narrows for strict indexing
|
|
34
|
+
const { won } = await wg.claimIssue(item.id, deps.claimAudience(), cfg.claimTtlSec); // GR.1 atomic CAS
|
|
35
|
+
if (!won)
|
|
36
|
+
continue; // another runner/harness won it — it now carries a live claim, excluded next pass
|
|
37
|
+
const outcome = await superviseLap(() => deps.runLap(item), cfg.supervise); // GR.3 → LapResult
|
|
38
|
+
spent += outcome.costUsd; // GR.3 propagates costUsd across retries
|
|
39
|
+
if (outcome.kind === 'SHIPPED') {
|
|
40
|
+
await wg.updateIssue(item.id, { status: 'closed' });
|
|
41
|
+
closed.push(item.id);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Non-SHIPPED → the ONE uniform path. HUMAN_REQUIRED carries its reason; WEDGE (and the
|
|
45
|
+
// post-supervision CRASH/TIMEOUT the type allows but superviseLap never returns — it exhausts
|
|
46
|
+
// them to HUMAN_REQUIRED{UNRECOVERABLE_WEDGE}) map to UNRECOVERABLE_WEDGE.
|
|
47
|
+
const reason = outcome.kind === 'HUMAN_REQUIRED' ? outcome.reason : 'UNRECOVERABLE_WEDGE';
|
|
48
|
+
await parkAndEscalate(reason, item);
|
|
49
|
+
if (isResourcePause(reason))
|
|
50
|
+
return { stopped: reason, spent, closed, parked }; // e.g. lap-emitted RATE_BUDGET
|
|
51
|
+
// else (IRREVERSIBLE_BOUNDARY / SCOPE_FORK / UNRECOVERABLE_WEDGE): parked, take the next item
|
|
52
|
+
}
|
|
53
|
+
if (cfg.authMode === 'api' && spent > cfg.maxBudgetUsd) {
|
|
54
|
+
await parkAndEscalate('BUDGET'); // Inv 11 API bound (verified total_cost_usd) → resource pause → STOP
|
|
55
|
+
return { stopped: 'BUDGET', spent, closed, parked };
|
|
56
|
+
}
|
|
57
|
+
if (cfg.once)
|
|
58
|
+
return { stopped: 'once', spent, closed, parked };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `opensquid loop resolve <itemId> --misclassified` — the human-override INPUT (post-hoc residual-shrink
|
|
63
|
+
* path; NOT the hot loop). The human marks a parked escalation as principle-settleable: we record the
|
|
64
|
+
* misclassification (the heuristic said ESCALATE, the correct verdict was DECIDE) and un-wedge the item so
|
|
65
|
+
* it re-enters `ready` for another lap. The SOLE caller of GR.2's recordMisclassification.
|
|
66
|
+
*/
|
|
67
|
+
export async function resolveParked(itemId, deps) {
|
|
68
|
+
const item = await deps.wg.getIssue(itemId);
|
|
69
|
+
if (item?.wedgeReason === undefined)
|
|
70
|
+
throw new Error(`resolveParked: not a parked item: ${itemId}`);
|
|
71
|
+
await deps.recordMisclassification(deps.sessionId, 'DECIDE', 'ESCALATE', item.title, deps.nowIso);
|
|
72
|
+
await deps.wg.clearWedge(itemId); // un-wedge → re-enters listReady
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../../src/runtime/ralph/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GR.4 — the RALPH.md per-lap directive (the "Ralph constant").
|
|
3
|
+
*
|
|
4
|
+
* This is the STABLE prompt every lap runs (Brodie's Ralph: the same prompt, fresh context, every
|
|
5
|
+
* iteration — Inv 1 dumb-loop + Inv 2 fresh-context). It is NOT the orchestrator's logic; it is the
|
|
6
|
+
* instruction the spawned `claude -p` lap reads. The orchestrator stays a thin non-LLM loop; ALL the
|
|
7
|
+
* intelligence lives in the lap running this directive against the reloaded wedge-gated lessons.
|
|
8
|
+
*
|
|
9
|
+
* The one hard contract between lap and orchestrator: the lap MUST end by emitting a single greppable
|
|
10
|
+
* `RALPH-EXIT: {json}` line whose JSON is a `LapOutcome` (parsed by GR.2 `extractTypedExit`). Everything
|
|
11
|
+
* else (resume, lean load, the 7-phase flow, DECIDE-vs-ESCALATE) is the same discipline a human-driven
|
|
12
|
+
* session follows — the lap is gated identically (the gate is harness-agnostic, GDC).
|
|
13
|
+
*
|
|
14
|
+
* Exported as a string constant (not a shipped file) so the wizard (`ralph_writer.ts`) writes it to
|
|
15
|
+
* `~/.opensquid/RALPH.md` idempotently — no build-time file-copy-to-dist concern.
|
|
16
|
+
*/
|
|
17
|
+
export declare const RALPH_MD = "# RALPH.md \u2014 the gated-ralph per-lap directive\n\nYou are ONE lap of an autonomous gated loop. A thin orchestrator handed you exactly one already-claimed\nwork-item id via `--item <id>`. Your context is FRESH \u2014 nothing carried over from the previous lap\nexcept the durable disk (the work-graph + the wedge-gated lesson store). That is by design: you are the\ndumb loop's smart body (Inv 1/2). Do the item, then exit with a typed verdict. Do not try to do the\nwhole board \u2014 the orchestrator takes the next item.\n\n## What to do\n\n1. **Resume + load lean.** Read the item (`workgraph_get`) and recall only the lessons/memories relevant\n to it (scoped recall). Do NOT bulk-load the whole store \u2014 fresh context is the point.\n2. **Run the flow.** Drive SCOPE \u2192 AUTHOR \u2192 the 7-phase exactly as an interactive session would. The gate\n is identical for you (it is harness-agnostic). You CANNOT route around it \u2014 `--no-verify` is futile\n (the PreToolUse + git-owned gates both hold), and that is the safety floor that lets you run unattended.\n3. **DECIDE, don't ask.** Surface decisions are yours to settle by the locked principles (rename, format,\n file location, refactor \u2014 Simplicity). Decide and proceed. Permission-fishing is drift, not diligence.\n4. **ESCALATE only the genuine residual.** Stop and emit `HUMAN_REQUIRED` ONLY for: an irreversible /\n outward boundary you cannot cross (npm publish, OTP, force-push, prod deploy, drop table) \u2192\n `IRREVERSIBLE_BOUNDARY`; a genuine product/UX fork the principles cannot settle \u2192 `SCOPE_FORK`. These\n are the things only the human can own. Everything else, you own.\n5. **Ship gated.** Land the work only through the flow (tests + gates green, commit, push if configured).\n Flush any durable lessons learned.\n\n## How to exit (the ONE hard contract)\n\nEnd your run by printing EXACTLY ONE line of the form:\n\n```\nRALPH-EXIT: {\"kind\":\"SHIPPED\"}\n```\n\nThe JSON must be one of:\n\n- `{\"kind\":\"SHIPPED\"}` \u2014 the item is done, gated, committed.\n- `{\"kind\":\"HUMAN_REQUIRED\",\"reason\":\"IRREVERSIBLE_BOUNDARY\",\"payload\":{...}}` \u2014 parked for the human;\n `reason` is one of IRREVERSIBLE_BOUNDARY | SCOPE_FORK | UNRECOVERABLE_WEDGE | BUDGET | RATE_BUDGET |\n BOARD_EMPTY (you will normally only emit the first two; the orchestrator owns the resource reasons).\n- `{\"kind\":\"WEDGE\"}` \u2014 you are genuinely stuck and a fresh lap on the SAME item would not help (the\n orchestrator wedge-marks it so it is not re-attempted, and escalates UNRECOVERABLE_WEDGE).\n\nIf you crash or time out, say nothing special \u2014 the orchestrator's supervisor treats a missing/erroring\nexit as a transient CRASH/TIMEOUT and bounds the retries itself. A clean run with NO tag = `SHIPPED`.\n";
|
|
18
|
+
//# sourceMappingURL=ralph_template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ralph_template.d.ts","sourceRoot":"","sources":["../../../src/runtime/ralph/ralph_template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,QAAQ,ixFA2CpB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GR.4 — the RALPH.md per-lap directive (the "Ralph constant").
|
|
3
|
+
*
|
|
4
|
+
* This is the STABLE prompt every lap runs (Brodie's Ralph: the same prompt, fresh context, every
|
|
5
|
+
* iteration — Inv 1 dumb-loop + Inv 2 fresh-context). It is NOT the orchestrator's logic; it is the
|
|
6
|
+
* instruction the spawned `claude -p` lap reads. The orchestrator stays a thin non-LLM loop; ALL the
|
|
7
|
+
* intelligence lives in the lap running this directive against the reloaded wedge-gated lessons.
|
|
8
|
+
*
|
|
9
|
+
* The one hard contract between lap and orchestrator: the lap MUST end by emitting a single greppable
|
|
10
|
+
* `RALPH-EXIT: {json}` line whose JSON is a `LapOutcome` (parsed by GR.2 `extractTypedExit`). Everything
|
|
11
|
+
* else (resume, lean load, the 7-phase flow, DECIDE-vs-ESCALATE) is the same discipline a human-driven
|
|
12
|
+
* session follows — the lap is gated identically (the gate is harness-agnostic, GDC).
|
|
13
|
+
*
|
|
14
|
+
* Exported as a string constant (not a shipped file) so the wizard (`ralph_writer.ts`) writes it to
|
|
15
|
+
* `~/.opensquid/RALPH.md` idempotently — no build-time file-copy-to-dist concern.
|
|
16
|
+
*/
|
|
17
|
+
export const RALPH_MD = `# RALPH.md — the gated-ralph per-lap directive
|
|
18
|
+
|
|
19
|
+
You are ONE lap of an autonomous gated loop. A thin orchestrator handed you exactly one already-claimed
|
|
20
|
+
work-item id via \`--item <id>\`. Your context is FRESH — nothing carried over from the previous lap
|
|
21
|
+
except the durable disk (the work-graph + the wedge-gated lesson store). That is by design: you are the
|
|
22
|
+
dumb loop's smart body (Inv 1/2). Do the item, then exit with a typed verdict. Do not try to do the
|
|
23
|
+
whole board — the orchestrator takes the next item.
|
|
24
|
+
|
|
25
|
+
## What to do
|
|
26
|
+
|
|
27
|
+
1. **Resume + load lean.** Read the item (\`workgraph_get\`) and recall only the lessons/memories relevant
|
|
28
|
+
to it (scoped recall). Do NOT bulk-load the whole store — fresh context is the point.
|
|
29
|
+
2. **Run the flow.** Drive SCOPE → AUTHOR → the 7-phase exactly as an interactive session would. The gate
|
|
30
|
+
is identical for you (it is harness-agnostic). You CANNOT route around it — \`--no-verify\` is futile
|
|
31
|
+
(the PreToolUse + git-owned gates both hold), and that is the safety floor that lets you run unattended.
|
|
32
|
+
3. **DECIDE, don't ask.** Surface decisions are yours to settle by the locked principles (rename, format,
|
|
33
|
+
file location, refactor — Simplicity). Decide and proceed. Permission-fishing is drift, not diligence.
|
|
34
|
+
4. **ESCALATE only the genuine residual.** Stop and emit \`HUMAN_REQUIRED\` ONLY for: an irreversible /
|
|
35
|
+
outward boundary you cannot cross (npm publish, OTP, force-push, prod deploy, drop table) →
|
|
36
|
+
\`IRREVERSIBLE_BOUNDARY\`; a genuine product/UX fork the principles cannot settle → \`SCOPE_FORK\`. These
|
|
37
|
+
are the things only the human can own. Everything else, you own.
|
|
38
|
+
5. **Ship gated.** Land the work only through the flow (tests + gates green, commit, push if configured).
|
|
39
|
+
Flush any durable lessons learned.
|
|
40
|
+
|
|
41
|
+
## How to exit (the ONE hard contract)
|
|
42
|
+
|
|
43
|
+
End your run by printing EXACTLY ONE line of the form:
|
|
44
|
+
|
|
45
|
+
\`\`\`
|
|
46
|
+
RALPH-EXIT: {"kind":"SHIPPED"}
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
The JSON must be one of:
|
|
50
|
+
|
|
51
|
+
- \`{"kind":"SHIPPED"}\` — the item is done, gated, committed.
|
|
52
|
+
- \`{"kind":"HUMAN_REQUIRED","reason":"IRREVERSIBLE_BOUNDARY","payload":{...}}\` — parked for the human;
|
|
53
|
+
\`reason\` is one of IRREVERSIBLE_BOUNDARY | SCOPE_FORK | UNRECOVERABLE_WEDGE | BUDGET | RATE_BUDGET |
|
|
54
|
+
BOARD_EMPTY (you will normally only emit the first two; the orchestrator owns the resource reasons).
|
|
55
|
+
- \`{"kind":"WEDGE"}\` — you are genuinely stuck and a fresh lap on the SAME item would not help (the
|
|
56
|
+
orchestrator wedge-marks it so it is not re-attempted, and escalates UNRECOVERABLE_WEDGE).
|
|
57
|
+
|
|
58
|
+
If you crash or time out, say nothing special — the orchestrator's supervisor treats a missing/erroring
|
|
59
|
+
exit as a transient CRASH/TIMEOUT and bounds the retries itself. A clean run with NO tag = \`SHIPPED\`.
|
|
60
|
+
`;
|
|
61
|
+
//# sourceMappingURL=ralph_template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ralph_template.js","sourceRoot":"","sources":["../../../src/runtime/ralph/ralph_template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CvB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GR.3 — the lap-supervisor: bounded respawn + observable heartbeat (Inv 6: no silent death, no
|
|
3
|
+
* unbounded retry).
|
|
4
|
+
*
|
|
5
|
+
* `superviseLap` runs a lap, retrying ONLY on CRASH/TIMEOUT (every other outcome — SHIPPED,
|
|
6
|
+
* HUMAN_REQUIRED, WEDGE — is terminal and returned as-is). Retries are BOUNDED by `maxRetries`; on
|
|
7
|
+
* exhaustion it returns a typed `HUMAN_REQUIRED{UNRECOVERABLE_WEDGE}` rather than looping forever or
|
|
8
|
+
* dying quietly. The `heartbeat` callback is the OBSERVABLE liveness tick (the orchestrator passes a
|
|
9
|
+
* lease-refresh, the `live_session_lease` freshness model) — liveness is a signal, not a hope.
|
|
10
|
+
*
|
|
11
|
+
* Cost propagates: each attempt bills `costUsd`, accumulated across retries, so GR.4 can sum the Inv 11
|
|
12
|
+
* running budget even when a crash-then-ship spent on both attempts.
|
|
13
|
+
*
|
|
14
|
+
* Imported by: src/runtime/ralph/orchestrator.ts (GR.4).
|
|
15
|
+
*/
|
|
16
|
+
import type { LapOutcome } from './lap_outcome.js';
|
|
17
|
+
export type LapResult = LapOutcome & {
|
|
18
|
+
costUsd: number;
|
|
19
|
+
};
|
|
20
|
+
export interface SuperviseOpts {
|
|
21
|
+
maxRetries: number;
|
|
22
|
+
/** Backoff before the n-th retry (n is 0-based attempt index just completed). */
|
|
23
|
+
backoffMs: (attempt: number) => number;
|
|
24
|
+
/** Observable liveness tick, fired once per attempt (e.g. refreshLease). */
|
|
25
|
+
heartbeat: () => void;
|
|
26
|
+
/** Injectable sleep (tests pass a no-op); defaults to a real timer. */
|
|
27
|
+
sleep?: (ms: number) => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function superviseLap(run: () => Promise<LapResult>, opts: SuperviseOpts): Promise<LapResult>;
|
|
30
|
+
//# sourceMappingURL=supervisor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../../src/runtime/ralph/supervisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,uEAAuE;IACvE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAID,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAC7B,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,SAAS,CAAC,CAmBpB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2
|
+
export async function superviseLap(run, opts) {
|
|
3
|
+
const sleep = opts.sleep ?? realSleep;
|
|
4
|
+
let costUsd = 0;
|
|
5
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
6
|
+
opts.heartbeat();
|
|
7
|
+
let out = null;
|
|
8
|
+
try {
|
|
9
|
+
out = await run();
|
|
10
|
+
costUsd += out.costUsd;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
out = null; // a thrown run is treated as CRASH (retryable)
|
|
14
|
+
}
|
|
15
|
+
if (out !== null && out.kind !== 'CRASH' && out.kind !== 'TIMEOUT') {
|
|
16
|
+
return { ...out, costUsd }; // terminal: SHIPPED / HUMAN_REQUIRED / WEDGE
|
|
17
|
+
}
|
|
18
|
+
if (attempt < opts.maxRetries)
|
|
19
|
+
await sleep(opts.backoffMs(attempt));
|
|
20
|
+
}
|
|
21
|
+
// bounded → escalate, never silent
|
|
22
|
+
return { kind: 'HUMAN_REQUIRED', reason: 'UNRECOVERABLE_WEDGE', costUsd };
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/runtime/ralph/supervisor.ts"],"names":[],"mappings":"AA6BA,MAAM,SAAS,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEvF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAA6B,EAC7B,IAAmB;IAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,CAAC;IACtC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,IAAI,GAAG,GAAqB,IAAI,CAAC;QACjC,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC;YAClB,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,IAAI,CAAC,CAAC,+CAA+C;QAC7D,CAAC;QACD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACnE,OAAO,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,6CAA6C;QAC3E,CAAC;QACD,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU;YAAE,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,mCAAmC;IACnC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { runOneShotCli } from '../../runtime/spawn_lifecycle.js';
|
|
3
|
+
import type { Issue } from '../../workgraph/types.js';
|
|
4
|
+
import { type RalphConfig } from '../../runtime/ralph/orchestrator.js';
|
|
5
|
+
import type { LapResult } from '../../runtime/ralph/supervisor.js';
|
|
6
|
+
import { type ChatSend } from '../../runtime/ralph/escalator.js';
|
|
7
|
+
import { type RalphConfigFile } from '../wizard/ralph_writer.js';
|
|
8
|
+
/** Hydrate the persisted scalar config into the orchestrator's runtime `RalphConfig` (closures rebuilt). */
|
|
9
|
+
export declare function buildRalphConfig(file: RalphConfigFile, opts: {
|
|
10
|
+
once: boolean;
|
|
11
|
+
maxBudgetUsd?: number;
|
|
12
|
+
}): RalphConfig;
|
|
13
|
+
/** Build the per-lap runner: spawn `claude -p RALPH.md --item <id>`, parse the typed exit. A deadline
|
|
14
|
+
* overrun (group-SIGKILL) becomes the typed TIMEOUT, NOT a CRASH; a genuine spawn failure rethrows → CRASH. */
|
|
15
|
+
export declare function makeSpawnLap(cfg: RalphConfig, file: RalphConfigFile, runCli?: typeof runOneShotCli): (item: Issue) => Promise<LapResult>;
|
|
16
|
+
/** The real chat transport: a one-shot UDS JSON-RPC `send` to the chat-daemon (the live Telegram path
|
|
17
|
+
* `chat_send` uses). `ok:false` on any unreachable/error so the escalator stays undroppable. */
|
|
18
|
+
export declare const daemonChatSend: ChatSend;
|
|
19
|
+
export declare function registerRalph(program: Command): Command;
|
|
20
|
+
//# sourceMappingURL=ralph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ralph.d.ts","sourceRoot":"","sources":["../../../src/setup/cli/ralph.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAGjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAChF,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAElF,4GAA4G;AAC5G,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,WAAW,CAYb;AAED;+GAC+G;AAC/G,wBAAgB,YAAY,CAC1B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,OAAO,aAA6B,GAC3C,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,SAAS,CAAC,CA6BrC;AAED;gGACgG;AAChG,eAAO,MAAM,cAAc,EAAE,QAwCzB,CAAC;AAEL,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAqEvD"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GR.4 — the `opensquid loop` CLI: the user-invoked entry that ASSEMBLES the orchestrator's injected deps
|
|
3
|
+
* and runs the gated-ralph loop. A thin wire — all logic lives in the unit-tested pieces it composes
|
|
4
|
+
* (runRalphLoop, chatEscalator, parseLapOutcome, the GR.1–3 store ops). The loop is a CLI COMMAND, not a
|
|
5
|
+
* daemon (opensquid stays an MCP server / tool provider; the agent loop runs inside the spawned harness).
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* opensquid loop [--once] [--max-budget-usd <n>] — run the loop (read ready → claim → lap → repeat)
|
|
9
|
+
* opensquid loop resolve <itemId> --misclassified — the human-override residual-shrink path (GR.4)
|
|
10
|
+
*
|
|
11
|
+
* Imports from: commander, node:net, ../../runtime/paths.js, ../../runtime/spawn_lifecycle.js,
|
|
12
|
+
* ../../runtime/ralph/*, ../../workgraph/*, ../wizard/ralph_writer.js.
|
|
13
|
+
*/
|
|
14
|
+
import { connect } from 'node:net';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { OPENSQUID_HOME, chatDaemonSockPath } from '../../runtime/paths.js';
|
|
17
|
+
import { runOneShotCli } from '../../runtime/spawn_lifecycle.js';
|
|
18
|
+
import { workGraphStore } from '../../workgraph/store.js';
|
|
19
|
+
import { claimAudience } from '../../workgraph/audience.js';
|
|
20
|
+
import { runRalphLoop, resolveParked } from '../../runtime/ralph/orchestrator.js';
|
|
21
|
+
import { parseLapOutcome } from '../../runtime/ralph/lap_outcome.js';
|
|
22
|
+
import { recordMisclassification } from '../../runtime/ralph/decision_classifier.js';
|
|
23
|
+
import { chatEscalator } from '../../runtime/ralph/escalator.js';
|
|
24
|
+
import { readRalphConfig } from '../wizard/ralph_writer.js';
|
|
25
|
+
/** Hydrate the persisted scalar config into the orchestrator's runtime `RalphConfig` (closures rebuilt). */
|
|
26
|
+
export function buildRalphConfig(file, opts) {
|
|
27
|
+
return {
|
|
28
|
+
authMode: file.authMode,
|
|
29
|
+
maxBudgetUsd: opts.maxBudgetUsd ?? file.maxBudgetUsd,
|
|
30
|
+
claimTtlSec: file.claimTtlSec,
|
|
31
|
+
once: opts.once,
|
|
32
|
+
supervise: {
|
|
33
|
+
maxRetries: file.maxRetries,
|
|
34
|
+
backoffMs: (attempt) => file.backoffBaseMs * 2 ** attempt, // exponential from the base
|
|
35
|
+
heartbeat: () => undefined, // a future lease-refresh; liveness tick is a no-op for the CLI run
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Build the per-lap runner: spawn `claude -p RALPH.md --item <id>`, parse the typed exit. A deadline
|
|
40
|
+
* overrun (group-SIGKILL) becomes the typed TIMEOUT, NOT a CRASH; a genuine spawn failure rethrows → CRASH. */
|
|
41
|
+
export function makeSpawnLap(cfg, file, runCli = runOneShotCli) {
|
|
42
|
+
return async (item) => {
|
|
43
|
+
let stdout;
|
|
44
|
+
try {
|
|
45
|
+
stdout = await runCli({
|
|
46
|
+
cli: file.harness.cli, // Inv 10 — harness is a parameter
|
|
47
|
+
args: [
|
|
48
|
+
'-p',
|
|
49
|
+
file.harness.ralphMdPath,
|
|
50
|
+
'--item',
|
|
51
|
+
item.id,
|
|
52
|
+
'--output-format',
|
|
53
|
+
'json',
|
|
54
|
+
'--max-budget-usd',
|
|
55
|
+
String(cfg.maxBudgetUsd),
|
|
56
|
+
'--dangerously-skip-permissions',
|
|
57
|
+
],
|
|
58
|
+
prompt: '',
|
|
59
|
+
timeoutMs: file.wallClockMs,
|
|
60
|
+
markSubagent: true,
|
|
61
|
+
timeoutError: () => Object.assign(new Error('lap timeout'), { __timeout: true }),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
if (e.__timeout === true)
|
|
66
|
+
return { kind: 'TIMEOUT', costUsd: 0 };
|
|
67
|
+
throw e; // genuine spawn/IO failure → superviseLap maps it to CRASH
|
|
68
|
+
}
|
|
69
|
+
const { outcome, costUsd } = parseLapOutcome(stdout);
|
|
70
|
+
return { ...outcome, costUsd };
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** The real chat transport: a one-shot UDS JSON-RPC `send` to the chat-daemon (the live Telegram path
|
|
74
|
+
* `chat_send` uses). `ok:false` on any unreachable/error so the escalator stays undroppable. */
|
|
75
|
+
export const daemonChatSend = (params) => new Promise((resolve) => {
|
|
76
|
+
const sock = connect(chatDaemonSockPath());
|
|
77
|
+
let buf = '';
|
|
78
|
+
const done = (r) => {
|
|
79
|
+
try {
|
|
80
|
+
sock.end();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* already closed */
|
|
84
|
+
}
|
|
85
|
+
resolve(r);
|
|
86
|
+
};
|
|
87
|
+
const timer = setTimeout(() => done({ ok: false, reason: 'chat-daemon RPC timeout (5s)' }), 5000);
|
|
88
|
+
sock.on('error', (e) => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
done({ ok: false, reason: `chat-daemon unreachable: ${e.message}` });
|
|
91
|
+
});
|
|
92
|
+
sock.on('data', (d) => {
|
|
93
|
+
buf += d.toString('utf8');
|
|
94
|
+
const nl = buf.indexOf('\n');
|
|
95
|
+
if (nl === -1)
|
|
96
|
+
return;
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
try {
|
|
99
|
+
const res = JSON.parse(buf.slice(0, nl));
|
|
100
|
+
done(res.error ? { ok: false, reason: res.error.message ?? 'send error' } : { ok: true });
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
done({ ok: false, reason: e instanceof Error ? e.message : 'bad RPC response' });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
sock.write(JSON.stringify({
|
|
107
|
+
jsonrpc: '2.0',
|
|
108
|
+
id: `ralph-${String(Date.now())}`,
|
|
109
|
+
method: 'send',
|
|
110
|
+
params: { channel: params.channel, text: params.text },
|
|
111
|
+
}) + '\n');
|
|
112
|
+
});
|
|
113
|
+
export function registerRalph(program) {
|
|
114
|
+
const loop = program
|
|
115
|
+
.command('loop')
|
|
116
|
+
.description('Run the gated-ralph autonomous loop (composes the work-graph + the coding-flow gate)');
|
|
117
|
+
loop
|
|
118
|
+
.option('--once', 'process a single ready item then stop', false)
|
|
119
|
+
.option('--max-budget-usd <n>', 'API-mode dollar budget for this run (overrides config)')
|
|
120
|
+
.action(async (opts) => {
|
|
121
|
+
const file = await readRalphConfig();
|
|
122
|
+
if (file === null) {
|
|
123
|
+
process.stderr.write('🦑 loop OFF: no ~/.opensquid/ralph.config.json. Run the wizard first.\n');
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const cfg = buildRalphConfig(file, {
|
|
128
|
+
once: opts.once === true,
|
|
129
|
+
...(opts.maxBudgetUsd === undefined ? {} : { maxBudgetUsd: Number(opts.maxBudgetUsd) }),
|
|
130
|
+
});
|
|
131
|
+
const wg = workGraphStore({
|
|
132
|
+
dbUrl: `file:${join(OPENSQUID_HOME(), 'workgraph.db')}`,
|
|
133
|
+
sourceDir: join(OPENSQUID_HOME(), 'store', 'issues'),
|
|
134
|
+
});
|
|
135
|
+
await wg.init();
|
|
136
|
+
const result = await runRalphLoop(cfg, {
|
|
137
|
+
wg,
|
|
138
|
+
claimAudience,
|
|
139
|
+
runLap: makeSpawnLap(cfg, file),
|
|
140
|
+
escalate: chatEscalator({ send: daemonChatSend, channel: 'project:telegram' }),
|
|
141
|
+
});
|
|
142
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
143
|
+
});
|
|
144
|
+
loop
|
|
145
|
+
.command('resolve <itemId>')
|
|
146
|
+
.description('Resolve a parked HUMAN_REQUIRED item (the human-override residual-shrink path)')
|
|
147
|
+
.option('--misclassified', 'the escalation was principle-settleable — record + un-wedge for another lap', false)
|
|
148
|
+
.action(async (itemId, opts) => {
|
|
149
|
+
if (opts.misclassified !== true) {
|
|
150
|
+
process.stderr.write('Nothing to do: pass --misclassified to record + un-wedge the item.\n');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const wg = workGraphStore({
|
|
154
|
+
dbUrl: `file:${join(OPENSQUID_HOME(), 'workgraph.db')}`,
|
|
155
|
+
sourceDir: join(OPENSQUID_HOME(), 'store', 'issues'),
|
|
156
|
+
});
|
|
157
|
+
await wg.init();
|
|
158
|
+
await resolveParked(itemId, {
|
|
159
|
+
wg,
|
|
160
|
+
recordMisclassification,
|
|
161
|
+
sessionId: process.env.CLAUDE_SESSION_ID ?? '<cli>',
|
|
162
|
+
nowIso: new Date().toISOString(),
|
|
163
|
+
});
|
|
164
|
+
process.stdout.write(`resolved ${itemId}: misclassification recorded, item un-wedged → back in ready.\n`);
|
|
165
|
+
});
|
|
166
|
+
return loop;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=ralph.js.map
|