@gajae-code/coding-agent 0.4.5 → 0.5.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/CHANGELOG.md +62 -0
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/harness.d.ts +3 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +7 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +30 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +14 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +8 -1
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/harness-control-plane/types.d.ts +4 -0
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +16 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +3 -1
- package/dist/types/session/blob-store.d.ts +59 -4
- package/dist/types/session/session-manager.d.ts +24 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/receipt.d.ts +1 -0
- package/dist/types/task/types.d.ts +7 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/tools/subagent.d.ts +6 -0
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli.ts +9 -4
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +43 -5
- package/src/commands/launch.ts +2 -2
- package/src/commands/session.ts +3 -1
- package/src/config/file-lock-gc.ts +181 -0
- package/src/config/file-lock.ts +14 -0
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +264 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +513 -26
- package/src/cursor.ts +16 -2
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/export/html/index.ts +13 -9
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +417 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +58 -7
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +46 -29
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +211 -8
- package/src/gjc-runtime/tmux-common.ts +29 -0
- package/src/gjc-runtime/tmux-gc.ts +176 -0
- package/src/gjc-runtime/tmux-sessions.ts +68 -12
- package/src/gjc-runtime/ultragoal-runtime.ts +517 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +89 -27
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +93 -0
- package/src/harness-control-plane/types.ts +4 -0
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +14 -8
- package/src/main.ts +7 -2
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +370 -181
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +34 -42
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +187 -39
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +279 -257
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
- package/src/sdk.ts +46 -5
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +179 -25
- package/src/session/blob-store.ts +148 -6
- package/src/session/session-manager.ts +311 -60
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +78 -6
- package/src/task/receipt.ts +5 -0
- package/src/task/render.ts +21 -1
- package/src/task/types.ts +8 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/ask.ts +56 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/resolve.ts +93 -18
- package/src/tools/subagent-render.ts +9 -0
- package/src/tools/subagent.ts +26 -2
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/tool-choice.ts +45 -16
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gjc gc` runtime — a global, liveness-only, dry-run-by-default garbage
|
|
3
|
+
* collector for stale GJC session/PID records.
|
|
4
|
+
*
|
|
5
|
+
* Design (see .gjc/plans/ralplan/2026-06-13-1347-954f/pending-approval.md):
|
|
6
|
+
* - This module is an ORCHESTRATOR only. It owns the shared PID probe, the
|
|
7
|
+
* report/exit-code policy, and text/JSON rendering. It must NOT parse private
|
|
8
|
+
* store layouts directly; every store is reached through an injectable
|
|
9
|
+
* `GcStoreAdapter` that lives next to its store owner.
|
|
10
|
+
* - Liveness-only and fail-closed: only `ESRCH` (no such process) is `dead`
|
|
11
|
+
* (removable). `process.kill(pid, 0)` success, `EPERM`, and any unknown probe
|
|
12
|
+
* error all mean KEEP — a live process is never signalled or killed.
|
|
13
|
+
* - Dry-run by default: nothing is deleted unless `--prune`/`--force`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { buildGcReportText } from "./gc-render";
|
|
17
|
+
|
|
18
|
+
export type GcStore = "harness_leases" | "team_workers" | "file_locks" | "tmux_sessions" | "registry_entries";
|
|
19
|
+
|
|
20
|
+
export const GC_STORES: readonly GcStore[] = [
|
|
21
|
+
"harness_leases",
|
|
22
|
+
"team_workers",
|
|
23
|
+
"file_locks",
|
|
24
|
+
"tmux_sessions",
|
|
25
|
+
"registry_entries",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/** Why a probed pid is kept instead of treated as dead. */
|
|
29
|
+
export type GcPidKeepReason = "alive" | "eperm" | "unknown";
|
|
30
|
+
|
|
31
|
+
export interface GcPidProbeResult {
|
|
32
|
+
/** `dead` only on ESRCH; `keep` for alive/eperm/unknown (fail-closed). */
|
|
33
|
+
status: "dead" | "keep";
|
|
34
|
+
reason?: GcPidKeepReason;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Single shared liveness contract threaded through every classifier + prune path. */
|
|
39
|
+
export type GcPidProbe = (pid: number) => GcPidProbeResult;
|
|
40
|
+
|
|
41
|
+
export type GcPidStatus = "dead" | "alive" | "eperm" | "unknown" | "none";
|
|
42
|
+
|
|
43
|
+
export type GcAction = "none" | "would_remove" | "removed" | "remove_failed" | "skipped";
|
|
44
|
+
|
|
45
|
+
export interface GcRecord {
|
|
46
|
+
store: GcStore;
|
|
47
|
+
/** Stable identifier: session id, lock dir path, worker id, tmux name, registry session id. */
|
|
48
|
+
id: string;
|
|
49
|
+
path?: string;
|
|
50
|
+
root?: string;
|
|
51
|
+
pid?: number;
|
|
52
|
+
pid_status?: GcPidStatus;
|
|
53
|
+
/** Store-specific classification label (e.g. "dead", "live", "unclassified", "terminal_lifecycle"). */
|
|
54
|
+
status: string;
|
|
55
|
+
stale: boolean;
|
|
56
|
+
removable: boolean;
|
|
57
|
+
action: GcAction;
|
|
58
|
+
reason: string;
|
|
59
|
+
detail?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
removed?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface GcError {
|
|
65
|
+
store: GcStore;
|
|
66
|
+
scope: string;
|
|
67
|
+
message: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface GcCollectResult {
|
|
71
|
+
records: GcRecord[];
|
|
72
|
+
errors: GcError[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface GcPruneOutcome {
|
|
76
|
+
removed: boolean;
|
|
77
|
+
error?: string;
|
|
78
|
+
/** Set when a removable record was skipped at prune time (e.g. TOCTOU became live). */
|
|
79
|
+
skipped?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface GcContext {
|
|
83
|
+
probe: GcPidProbe;
|
|
84
|
+
force: boolean;
|
|
85
|
+
env: NodeJS.ProcessEnv;
|
|
86
|
+
cwd: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A store-owned GC adapter. `collect` discovers + classifies (using the shared
|
|
91
|
+
* probe) without mutating anything. `prune` removes a single record, and MUST
|
|
92
|
+
* re-validate / re-probe immediately before any destructive action.
|
|
93
|
+
*/
|
|
94
|
+
export interface GcStoreAdapter {
|
|
95
|
+
store: GcStore;
|
|
96
|
+
collect(ctx: GcContext): Promise<GcCollectResult>;
|
|
97
|
+
prune(record: GcRecord, ctx: GcContext): Promise<GcPruneOutcome>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface GcCounts {
|
|
101
|
+
discovered: number;
|
|
102
|
+
stale: number;
|
|
103
|
+
alive: number;
|
|
104
|
+
eperm: number;
|
|
105
|
+
unknown: number;
|
|
106
|
+
terminal_lifecycle: number;
|
|
107
|
+
unclassified: number;
|
|
108
|
+
would_remove: number;
|
|
109
|
+
removed: number;
|
|
110
|
+
failed: number;
|
|
111
|
+
errors: number;
|
|
112
|
+
by_store: Record<
|
|
113
|
+
GcStore,
|
|
114
|
+
{ discovered: number; stale: number; would_remove: number; removed: number; failed: number }
|
|
115
|
+
>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface GcReport {
|
|
119
|
+
dry_run: boolean;
|
|
120
|
+
stores: Record<GcStore, GcRecord[]>;
|
|
121
|
+
counts: GcCounts;
|
|
122
|
+
errors: GcError[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface GcRunResult {
|
|
126
|
+
stdout: string;
|
|
127
|
+
stderr: string;
|
|
128
|
+
status: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The shared, fail-closed PID probe. ESRCH => dead/removable; success => alive;
|
|
133
|
+
* EPERM => kept (owned by another user); any other error => kept as unknown.
|
|
134
|
+
*/
|
|
135
|
+
export const gcPidProbe: GcPidProbe = (pid: number): GcPidProbeResult => {
|
|
136
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
137
|
+
return { status: "keep", reason: "unknown", error: `invalid_pid:${pid}` };
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
process.kill(pid, 0);
|
|
141
|
+
return { status: "keep", reason: "alive" };
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
144
|
+
if (code === "ESRCH") return { status: "dead" };
|
|
145
|
+
if (code === "EPERM") return { status: "keep", reason: "eperm" };
|
|
146
|
+
return { status: "keep", reason: "unknown", error: code ?? String(error) };
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/** Map a `GcPidProbe` onto the harness lease probe shape (`"alive"|"dead"|"eperm"`). */
|
|
151
|
+
export function gcProbeToLeasePidStatus(probe: GcPidProbe): (pid: number) => "alive" | "dead" | "eperm" {
|
|
152
|
+
return (pid: number) => {
|
|
153
|
+
const result = probe(pid);
|
|
154
|
+
if (result.status === "dead") return "dead";
|
|
155
|
+
// EPERM stays eperm; unknown maps to alive so classifyLeaseStatus keeps it.
|
|
156
|
+
return result.reason === "eperm" ? "eperm" : "alive";
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Translate a probe result into a record-friendly pid status label. */
|
|
161
|
+
export function gcPidStatusLabel(result: GcPidProbeResult): Exclude<GcPidStatus, "none"> {
|
|
162
|
+
if (result.status === "dead") return "dead";
|
|
163
|
+
return result.reason ?? "alive";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function emptyByStore(): GcCounts["by_store"] {
|
|
167
|
+
const by = {} as GcCounts["by_store"];
|
|
168
|
+
for (const store of GC_STORES) {
|
|
169
|
+
by[store] = { discovered: 0, stale: 0, would_remove: 0, removed: 0, failed: 0 };
|
|
170
|
+
}
|
|
171
|
+
return by;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function emptyStores(): Record<GcStore, GcRecord[]> {
|
|
175
|
+
const stores = {} as Record<GcStore, GcRecord[]>;
|
|
176
|
+
for (const store of GC_STORES) stores[store] = [];
|
|
177
|
+
return stores;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function computeCounts(stores: Record<GcStore, GcRecord[]>, errors: GcError[]): GcCounts {
|
|
181
|
+
const counts: GcCounts = {
|
|
182
|
+
discovered: 0,
|
|
183
|
+
stale: 0,
|
|
184
|
+
alive: 0,
|
|
185
|
+
eperm: 0,
|
|
186
|
+
unknown: 0,
|
|
187
|
+
terminal_lifecycle: 0,
|
|
188
|
+
unclassified: 0,
|
|
189
|
+
would_remove: 0,
|
|
190
|
+
removed: 0,
|
|
191
|
+
failed: 0,
|
|
192
|
+
errors: errors.length,
|
|
193
|
+
by_store: emptyByStore(),
|
|
194
|
+
};
|
|
195
|
+
for (const store of GC_STORES) {
|
|
196
|
+
for (const record of stores[store]) {
|
|
197
|
+
counts.discovered++;
|
|
198
|
+
counts.by_store[store].discovered++;
|
|
199
|
+
if (record.stale) {
|
|
200
|
+
counts.stale++;
|
|
201
|
+
counts.by_store[store].stale++;
|
|
202
|
+
}
|
|
203
|
+
if (record.pid_status === "alive") counts.alive++;
|
|
204
|
+
else if (record.pid_status === "eperm") counts.eperm++;
|
|
205
|
+
else if (record.pid_status === "unknown") counts.unknown++;
|
|
206
|
+
if (record.status === "terminal_lifecycle") counts.terminal_lifecycle++;
|
|
207
|
+
if (record.status === "unclassified") counts.unclassified++;
|
|
208
|
+
if (record.action === "would_remove") {
|
|
209
|
+
counts.would_remove++;
|
|
210
|
+
counts.by_store[store].would_remove++;
|
|
211
|
+
}
|
|
212
|
+
if (record.action === "removed") {
|
|
213
|
+
counts.removed++;
|
|
214
|
+
counts.by_store[store].removed++;
|
|
215
|
+
}
|
|
216
|
+
if (record.action === "remove_failed") {
|
|
217
|
+
counts.failed++;
|
|
218
|
+
counts.by_store[store].failed++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return counts;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface ParsedGcArgs {
|
|
226
|
+
json: boolean;
|
|
227
|
+
prune: boolean;
|
|
228
|
+
help: boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
class GcUsageError extends Error {}
|
|
232
|
+
|
|
233
|
+
function parseGcArgs(argv: string[]): ParsedGcArgs {
|
|
234
|
+
let json = false;
|
|
235
|
+
let prune = false;
|
|
236
|
+
let dryRun = false;
|
|
237
|
+
let help = false;
|
|
238
|
+
for (const arg of argv) {
|
|
239
|
+
switch (arg) {
|
|
240
|
+
case "--json":
|
|
241
|
+
case "-j":
|
|
242
|
+
json = true;
|
|
243
|
+
break;
|
|
244
|
+
case "--prune":
|
|
245
|
+
case "--force":
|
|
246
|
+
prune = true;
|
|
247
|
+
break;
|
|
248
|
+
case "--dry-run":
|
|
249
|
+
dryRun = true;
|
|
250
|
+
break;
|
|
251
|
+
case "--help":
|
|
252
|
+
case "-h":
|
|
253
|
+
help = true;
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
throw new GcUsageError(`unknown_flag:${arg}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Explicit --dry-run always wins over --prune/--force.
|
|
260
|
+
if (dryRun) prune = false;
|
|
261
|
+
return { json, prune, help };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Collect every store's records (catching hard discovery errors per adapter),
|
|
266
|
+
* then optionally prune removable records with per-record revalidation.
|
|
267
|
+
*/
|
|
268
|
+
export async function collectGcReport(adapters: GcStoreAdapter[], ctx: GcContext, prune: boolean): Promise<GcReport> {
|
|
269
|
+
const stores = emptyStores();
|
|
270
|
+
const errors: GcError[] = [];
|
|
271
|
+
|
|
272
|
+
for (const adapter of adapters) {
|
|
273
|
+
try {
|
|
274
|
+
const result = await adapter.collect(ctx);
|
|
275
|
+
stores[adapter.store].push(...result.records);
|
|
276
|
+
errors.push(...result.errors);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
errors.push({
|
|
279
|
+
store: adapter.store,
|
|
280
|
+
scope: "collect",
|
|
281
|
+
message: error instanceof Error ? error.message : String(error),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Mark dry-run intent on every removable record before pruning.
|
|
287
|
+
for (const store of GC_STORES) {
|
|
288
|
+
for (const record of stores[store]) {
|
|
289
|
+
if (record.removable) record.action = "would_remove";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (prune) {
|
|
294
|
+
const adapterByStore = new Map(adapters.map(a => [a.store, a] as const));
|
|
295
|
+
for (const store of GC_STORES) {
|
|
296
|
+
const adapter = adapterByStore.get(store);
|
|
297
|
+
if (!adapter) continue;
|
|
298
|
+
for (const record of stores[store]) {
|
|
299
|
+
if (!record.removable) continue;
|
|
300
|
+
try {
|
|
301
|
+
const outcome = await adapter.prune(record, ctx);
|
|
302
|
+
if (outcome.removed) {
|
|
303
|
+
record.action = "removed";
|
|
304
|
+
record.removed = true;
|
|
305
|
+
} else if (outcome.skipped) {
|
|
306
|
+
record.action = "skipped";
|
|
307
|
+
record.reason = outcome.skipped;
|
|
308
|
+
record.removed = false;
|
|
309
|
+
} else {
|
|
310
|
+
record.action = "remove_failed";
|
|
311
|
+
record.removed = false;
|
|
312
|
+
record.error = outcome.error ?? "remove_failed";
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
record.action = "remove_failed";
|
|
316
|
+
record.removed = false;
|
|
317
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { dry_run: !prune, stores, counts: computeCounts(stores, errors), errors };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Exit-code policy:
|
|
328
|
+
* - usage/parse error => 2
|
|
329
|
+
* - hard discovery errors => 1 (both modes)
|
|
330
|
+
* - prune mode with a failed intended removal => 1
|
|
331
|
+
* - otherwise => 0
|
|
332
|
+
*/
|
|
333
|
+
export function computeExitCode(report: GcReport): number {
|
|
334
|
+
if (report.errors.length > 0) return 1;
|
|
335
|
+
if (!report.dry_run && report.counts.failed > 0) return 1;
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function runGjcGcCommand(
|
|
340
|
+
argv: string[],
|
|
341
|
+
cwd: string = process.cwd(),
|
|
342
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
343
|
+
adapters?: GcStoreAdapter[],
|
|
344
|
+
): Promise<GcRunResult> {
|
|
345
|
+
let parsed: ParsedGcArgs;
|
|
346
|
+
try {
|
|
347
|
+
parsed = parseGcArgs(argv);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
const message = error instanceof GcUsageError ? error.message : String(error);
|
|
350
|
+
return { stdout: "", stderr: `gjc gc: ${message}\n`, status: 2 };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (parsed.help) {
|
|
354
|
+
return { stdout: gcHelpText(), stderr: "", status: 0 };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const resolvedAdapters = adapters ?? (await defaultGcAdapters());
|
|
358
|
+
const ctx: GcContext = { probe: gcPidProbe, force: parsed.prune, env, cwd };
|
|
359
|
+
const report = await collectGcReport(resolvedAdapters, ctx, parsed.prune);
|
|
360
|
+
const status = computeExitCode(report);
|
|
361
|
+
const stdout = parsed.json ? `${JSON.stringify(report, null, 2)}\n` : buildGcReportText(report);
|
|
362
|
+
return { stdout, stderr: "", status };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function gcHelpText(): string {
|
|
366
|
+
return [
|
|
367
|
+
"gjc gc - garbage-collect stale GJC session/PID records",
|
|
368
|
+
"",
|
|
369
|
+
"USAGE",
|
|
370
|
+
" $ gjc gc [--prune|--force] [--json]",
|
|
371
|
+
"",
|
|
372
|
+
"FLAGS",
|
|
373
|
+
" --prune, --force Actually remove stale records (default: dry-run report only)",
|
|
374
|
+
" --dry-run Force report-only mode (overrides --prune/--force)",
|
|
375
|
+
" -j, --json Emit machine-readable JSON",
|
|
376
|
+
"",
|
|
377
|
+
"Liveness-only: a record is removed only when its owning process is dead",
|
|
378
|
+
"(ESRCH). Live / permission-denied / unknown processes are always kept.",
|
|
379
|
+
"",
|
|
380
|
+
].join("\n");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Lazily assemble the real store adapters (kept lazy to avoid import cycles). */
|
|
384
|
+
export async function defaultGcAdapters(): Promise<GcStoreAdapter[]> {
|
|
385
|
+
const [
|
|
386
|
+
{ harnessLeasesGcAdapter, registryEntriesGcAdapter },
|
|
387
|
+
{ fileLocksGcAdapter },
|
|
388
|
+
{ teamWorkersGcAdapter },
|
|
389
|
+
{ tmuxSessionsGcAdapter },
|
|
390
|
+
] = await Promise.all([
|
|
391
|
+
import("../harness-control-plane/gc-adapter"),
|
|
392
|
+
import("../config/file-lock-gc"),
|
|
393
|
+
import("./team-gc"),
|
|
394
|
+
import("./tmux-gc"),
|
|
395
|
+
]);
|
|
396
|
+
return [
|
|
397
|
+
harnessLeasesGcAdapter,
|
|
398
|
+
teamWorkersGcAdapter,
|
|
399
|
+
fileLocksGcAdapter,
|
|
400
|
+
tmuxSessionsGcAdapter,
|
|
401
|
+
registryEntriesGcAdapter,
|
|
402
|
+
];
|
|
403
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parse + summarize for ledger-backed skill observability.
|
|
3
|
+
*
|
|
4
|
+
* Workflow progress for ultragoal/ralplan cannot be observed via subagent tool
|
|
5
|
+
* events (those skills persist through `bash`-backed `gjc` CLI calls whose tool
|
|
6
|
+
* `details` carry no structured payload). The durable source of truth is the
|
|
7
|
+
* append-only ledgers:
|
|
8
|
+
* - ultragoal: `.gjc/ultragoal/ledger.jsonl`
|
|
9
|
+
* - ralplan: `.gjc/plans/ralplan/<run-id>/index.jsonl`
|
|
10
|
+
*
|
|
11
|
+
* This module is I/O-free: callers read the files and pass lines or already-parsed
|
|
12
|
+
* rows. It feeds the compact HUD chip builders in `skill-state/workflow-hud.ts`
|
|
13
|
+
* via the runtime sync paths. Display-string helpers stay theme-free.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/* ------------------------------- ultragoal ------------------------------- */
|
|
17
|
+
|
|
18
|
+
/** Minimal projection of an ultragoal ledger row used for the HUD chip. */
|
|
19
|
+
export interface UltragoalLedgerEventLite {
|
|
20
|
+
/** Normalized from the row's `event` field, or `type` for reconcile rows. */
|
|
21
|
+
event: string;
|
|
22
|
+
goalId?: string;
|
|
23
|
+
status?: string;
|
|
24
|
+
timestamp?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Coerce an already-parsed ledger row into the lite shape. Accepts both the
|
|
29
|
+
* `event`-keyed vocabulary (plan_created, goal_started, goal_checkpointed,
|
|
30
|
+
* steering_accepted/rejected, review_blockers_recorded) and the `type`-keyed
|
|
31
|
+
* reconcile-failure row (`type: "reconcile_failed"`). Returns undefined when no
|
|
32
|
+
* event/type discriminator is present.
|
|
33
|
+
*/
|
|
34
|
+
export function coerceUltragoalLedgerEvent(row: Record<string, unknown>): UltragoalLedgerEventLite | undefined {
|
|
35
|
+
const event = typeof row.event === "string" ? row.event : typeof row.type === "string" ? row.type : undefined;
|
|
36
|
+
if (!event) return undefined;
|
|
37
|
+
const lite: UltragoalLedgerEventLite = { event };
|
|
38
|
+
if (typeof row.goalId === "string") lite.goalId = row.goalId;
|
|
39
|
+
if (typeof row.status === "string") lite.status = row.status;
|
|
40
|
+
if (typeof row.timestamp === "string") lite.timestamp = row.timestamp;
|
|
41
|
+
return lite;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Parse a single ultragoal ledger JSONL line; undefined for blank/malformed lines. */
|
|
45
|
+
export function parseUltragoalLedgerLine(line: string): UltragoalLedgerEventLite | undefined {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (!trimmed) return undefined;
|
|
48
|
+
let row: unknown;
|
|
49
|
+
try {
|
|
50
|
+
row = JSON.parse(trimmed);
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return undefined;
|
|
55
|
+
return coerceUltragoalLedgerEvent(row as Record<string, unknown>);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The most recent event, or undefined when the ledger is empty. */
|
|
59
|
+
export function latestUltragoalLedgerEvent(
|
|
60
|
+
events: readonly UltragoalLedgerEventLite[],
|
|
61
|
+
): UltragoalLedgerEventLite | undefined {
|
|
62
|
+
return events.length > 0 ? events[events.length - 1] : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Best-effort latest event from raw ledger text: parses line-by-line and skips
|
|
67
|
+
* blank/malformed rows so a torn or hand-edited ledger never throws on the HUD
|
|
68
|
+
* path. Strict receipt consumers should keep using the validating reader.
|
|
69
|
+
*/
|
|
70
|
+
export function latestUltragoalLedgerEventFromText(text: string): UltragoalLedgerEventLite | undefined {
|
|
71
|
+
const events: UltragoalLedgerEventLite[] = [];
|
|
72
|
+
for (const line of text.split(/\r?\n/)) {
|
|
73
|
+
const event = parseUltragoalLedgerLine(line);
|
|
74
|
+
if (event) events.push(event);
|
|
75
|
+
}
|
|
76
|
+
return latestUltragoalLedgerEvent(events);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* -------------------------------- ralplan -------------------------------- */
|
|
80
|
+
|
|
81
|
+
/** Minimal projection of a ralplan `index.jsonl` row. */
|
|
82
|
+
export interface RalplanIndexRow {
|
|
83
|
+
stage: string;
|
|
84
|
+
stageN?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Stages that open a new consensus iteration when they appear in append order. */
|
|
88
|
+
const RALPLAN_ITERATION_OPENERS = new Set(["planner", "revision"]);
|
|
89
|
+
|
|
90
|
+
const RALPLAN_STAGE_CODES: Record<string, string> = {
|
|
91
|
+
planner: "P",
|
|
92
|
+
revision: "R",
|
|
93
|
+
architect: "A",
|
|
94
|
+
critic: "C",
|
|
95
|
+
adr: "D",
|
|
96
|
+
final: "F",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const DEFAULT_STAGE_PRESENCE_CAP = 6;
|
|
100
|
+
|
|
101
|
+
/** Parse a single ralplan index JSONL line; undefined for blank/malformed lines. */
|
|
102
|
+
export function parseRalplanIndexLine(line: string): RalplanIndexRow | undefined {
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
if (!trimmed) return undefined;
|
|
105
|
+
let row: unknown;
|
|
106
|
+
try {
|
|
107
|
+
row = JSON.parse(trimmed);
|
|
108
|
+
} catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return undefined;
|
|
112
|
+
const record = row as Record<string, unknown>;
|
|
113
|
+
if (typeof record.stage !== "string") return undefined;
|
|
114
|
+
const out: RalplanIndexRow = { stage: record.stage };
|
|
115
|
+
if (typeof record.stage_n === "number") out.stageN = record.stage_n;
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface RalplanIndexSummary {
|
|
120
|
+
/** Number of consensus iterations (planner/revision boundaries), >= 0. */
|
|
121
|
+
iteration: number;
|
|
122
|
+
/** Stage names present in the current (latest) iteration, in append order. */
|
|
123
|
+
currentStages: string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Derive iteration count and current-iteration stage presence from index rows.
|
|
128
|
+
*
|
|
129
|
+
* `stage_n` is NOT used as the iteration key: it is stored verbatim per row and a
|
|
130
|
+
* single planner/architect/critic pass can span multiple stage_n values. Instead,
|
|
131
|
+
* a `planner` or `revision` row opens a new iteration and subsequent rows attach
|
|
132
|
+
* to it. No verdict is derived here (index rows carry none).
|
|
133
|
+
*/
|
|
134
|
+
export function summarizeRalplanIndex(rows: readonly RalplanIndexRow[]): RalplanIndexSummary {
|
|
135
|
+
let iteration = 0;
|
|
136
|
+
let currentStages: string[] = [];
|
|
137
|
+
for (const row of rows) {
|
|
138
|
+
if (RALPLAN_ITERATION_OPENERS.has(row.stage)) {
|
|
139
|
+
iteration += 1;
|
|
140
|
+
currentStages = [row.stage];
|
|
141
|
+
} else {
|
|
142
|
+
if (iteration === 0) iteration = 1;
|
|
143
|
+
currentStages.push(row.stage);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { iteration, currentStages };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Compact, theme-free presence string for the ralplan `stages` chip, e.g.
|
|
151
|
+
* `P·A·C`. Collapses past `cap` with a "… N more" suffix. Returns undefined when
|
|
152
|
+
* there are no stages.
|
|
153
|
+
*/
|
|
154
|
+
export function formatRalplanStagePresence(
|
|
155
|
+
stages: readonly string[],
|
|
156
|
+
cap = DEFAULT_STAGE_PRESENCE_CAP,
|
|
157
|
+
): string | undefined {
|
|
158
|
+
if (stages.length === 0) return undefined;
|
|
159
|
+
const codes = stages.map(stage => RALPLAN_STAGE_CODES[stage] ?? stage.charAt(0).toUpperCase());
|
|
160
|
+
if (codes.length <= cap) return codes.join("·");
|
|
161
|
+
const shown = codes.slice(0, cap).join("·");
|
|
162
|
+
const remaining = codes.length - cap;
|
|
163
|
+
return `${shown} … ${remaining} more ${remaining === 1 ? "stage" : "stages"}`;
|
|
164
|
+
}
|
|
@@ -5,6 +5,12 @@ import { syncSkillActiveState } from "../skill-state/active-state";
|
|
|
5
5
|
import { buildRalplanHudSummary } from "../skill-state/workflow-hud";
|
|
6
6
|
import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
|
|
7
7
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
8
|
+
import {
|
|
9
|
+
formatRalplanStagePresence,
|
|
10
|
+
parseRalplanIndexLine,
|
|
11
|
+
type RalplanIndexRow,
|
|
12
|
+
summarizeRalplanIndex,
|
|
13
|
+
} from "./ledger-event-renderer";
|
|
8
14
|
import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
|
|
9
15
|
import { migrateWorkflowState } from "./state-migrations";
|
|
10
16
|
import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
|
|
@@ -437,12 +443,32 @@ async function persistArtifact(resolved: ResolvedArtifactArgs, cwd: string): Pro
|
|
|
437
443
|
};
|
|
438
444
|
}
|
|
439
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Read and parse the run's `index.jsonl` rows. Best-effort: returns [] when the
|
|
448
|
+
* file is absent or unreadable so HUD sync never fails on a missing index.
|
|
449
|
+
*/
|
|
450
|
+
async function readRalplanIndexRows(cwd: string, runId: string): Promise<RalplanIndexRow[]> {
|
|
451
|
+
try {
|
|
452
|
+
const indexPath = path.join(cwd, ".gjc", "plans", "ralplan", runId, "index.jsonl");
|
|
453
|
+
const text = await fs.readFile(indexPath, "utf8");
|
|
454
|
+
const rows: RalplanIndexRow[] = [];
|
|
455
|
+
for (const line of text.split(/\r?\n/)) {
|
|
456
|
+
const row = parseRalplanIndexLine(line);
|
|
457
|
+
if (row) rows.push(row);
|
|
458
|
+
}
|
|
459
|
+
return rows;
|
|
460
|
+
} catch {
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
440
465
|
async function syncRalplanHud(options: {
|
|
441
466
|
cwd: string;
|
|
442
467
|
sessionId?: string;
|
|
443
468
|
stage: string;
|
|
444
469
|
pendingApproval: boolean;
|
|
445
470
|
iteration?: number;
|
|
471
|
+
runId?: string;
|
|
446
472
|
latestSummary?: string;
|
|
447
473
|
}): Promise<void> {
|
|
448
474
|
try {
|
|
@@ -453,19 +479,42 @@ async function syncRalplanHud(options: {
|
|
|
453
479
|
phase: options.stage,
|
|
454
480
|
sessionId: options.sessionId,
|
|
455
481
|
source: "gjc-ralplan-native",
|
|
456
|
-
hud:
|
|
457
|
-
stage: options.stage,
|
|
458
|
-
iteration: options.iteration,
|
|
459
|
-
pendingApproval: options.pendingApproval,
|
|
460
|
-
latestSummary: options.latestSummary,
|
|
461
|
-
updatedAt: new Date().toISOString(),
|
|
462
|
-
}),
|
|
482
|
+
hud: await buildRalplanHud(options),
|
|
463
483
|
});
|
|
464
484
|
} catch {
|
|
465
485
|
// HUD sync is best-effort and must not change command semantics.
|
|
466
486
|
}
|
|
467
487
|
}
|
|
468
488
|
|
|
489
|
+
async function buildRalplanHud(options: {
|
|
490
|
+
cwd: string;
|
|
491
|
+
stage: string;
|
|
492
|
+
pendingApproval: boolean;
|
|
493
|
+
iteration?: number;
|
|
494
|
+
latestSummary?: string;
|
|
495
|
+
runId?: string;
|
|
496
|
+
}) {
|
|
497
|
+
let iterationFromIndex: number | undefined;
|
|
498
|
+
let stages: string | undefined;
|
|
499
|
+
if (options.runId) {
|
|
500
|
+
const rows = await readRalplanIndexRows(options.cwd, options.runId);
|
|
501
|
+
if (rows.length > 0) {
|
|
502
|
+
const summary = summarizeRalplanIndex(rows);
|
|
503
|
+
iterationFromIndex = summary.iteration;
|
|
504
|
+
stages = formatRalplanStagePresence(summary.currentStages);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return buildRalplanHudSummary({
|
|
508
|
+
stage: options.stage,
|
|
509
|
+
iteration: options.iteration,
|
|
510
|
+
iterationFromIndex,
|
|
511
|
+
stages,
|
|
512
|
+
pendingApproval: options.pendingApproval,
|
|
513
|
+
latestSummary: options.latestSummary,
|
|
514
|
+
updatedAt: new Date().toISOString(),
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
469
518
|
async function handleArtifactWrite(args: readonly string[], cwd: string): Promise<RalplanCommandResult> {
|
|
470
519
|
const plannerState = parsePlannerStateArgs(args);
|
|
471
520
|
const resolved = await resolveArtifactArgs(args, cwd);
|
|
@@ -477,6 +526,7 @@ async function handleArtifactWrite(args: readonly string[], cwd: string): Promis
|
|
|
477
526
|
cwd,
|
|
478
527
|
sessionId: resolved.sessionId,
|
|
479
528
|
stage: persisted.stage,
|
|
529
|
+
runId: persisted.runId,
|
|
480
530
|
pendingApproval: persisted.stage === "final",
|
|
481
531
|
iteration: persisted.stageN,
|
|
482
532
|
latestSummary: `persisted ${persisted.stage} stage ${persisted.stageN}`,
|
|
@@ -617,6 +667,7 @@ async function handleConsensusHandoff(args: readonly string[], cwd: string): Pro
|
|
|
617
667
|
cwd,
|
|
618
668
|
sessionId: resolved.sessionId,
|
|
619
669
|
stage: "planner",
|
|
670
|
+
runId,
|
|
620
671
|
pendingApproval: false,
|
|
621
672
|
iteration: 1,
|
|
622
673
|
latestSummary: `${mode} run · ${resolved.interactive ? "interactive" : "automated"}`,
|
|
@@ -118,8 +118,13 @@ export interface StateStatusSummary {
|
|
|
118
118
|
|
|
119
119
|
function compactStateFields(state: Record<string, unknown>): Array<[string, string]> {
|
|
120
120
|
const fields: Array<[string, string]> = [];
|
|
121
|
+
const nested = isRecord(state.state) ? state.state : undefined;
|
|
121
122
|
for (const key of COMPACT_ELIDE_KEYS) {
|
|
122
|
-
const value = state[key]
|
|
123
|
+
const value = Array.isArray(state[key])
|
|
124
|
+
? state[key]
|
|
125
|
+
: nested && Array.isArray(nested[key])
|
|
126
|
+
? nested[key]
|
|
127
|
+
: undefined;
|
|
123
128
|
if (Array.isArray(value)) fields.push([key, `${value.length} entries (elided)`]);
|
|
124
129
|
}
|
|
125
130
|
return fields;
|
|
@@ -133,9 +138,13 @@ export function compactProjectStateJson(
|
|
|
133
138
|
const state = stateObject(stateJson);
|
|
134
139
|
const compact = projectStateFields(skill, stateJson, manifest, STATE_FIELD_ALLOWLIST);
|
|
135
140
|
const elisions: Record<string, unknown> = {};
|
|
141
|
+
const nested = isRecord(state.state) ? state.state : undefined;
|
|
136
142
|
for (const key of COMPACT_ELIDE_KEYS) {
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
if (Array.isArray(state[key])) {
|
|
144
|
+
elisions[key] = { type: "array", count: (state[key] as unknown[]).length, pointer: `/${key}` };
|
|
145
|
+
} else if (nested && Array.isArray(nested[key])) {
|
|
146
|
+
elisions[key] = { type: "array", count: (nested[key] as unknown[]).length, pointer: `/state/${key}` };
|
|
147
|
+
}
|
|
139
148
|
}
|
|
140
149
|
if (Object.keys(elisions).length) compact.elided = elisions;
|
|
141
150
|
return compact;
|