@gajae-code/coding-agent 0.5.0 → 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 +19 -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/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +7 -0
- 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 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +11 -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 +7 -0
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +16 -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/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +39 -3
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/subagent.d.ts +6 -0
- 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 +1 -0
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +7 -3
- package/src/config/file-lock-gc.ts +181 -0
- package/src/config/file-lock.ts +14 -0
- package/src/config/model-profiles.ts +24 -15
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +459 -3
- 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-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- 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 +179 -2
- package/src/gjc-runtime/tmux-common.ts +14 -0
- package/src/gjc-runtime/tmux-gc.ts +176 -0
- package/src/gjc-runtime/tmux-sessions.ts +49 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +12 -0
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +11 -0
- package/src/harness-control-plane/storage.ts +70 -0
- package/src/internal-urls/docs-index.generated.ts +14 -8
- package/src/main.ts +7 -2
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +25 -8
- package/src/modes/components/status-line/segments.ts +1 -1
- 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 +1 -0
- package/src/modes/rpc/rpc-mode.ts +151 -33
- package/src/modes/shared/agent-wire/command-dispatch.ts +278 -261
- 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 +17 -3
- package/src/session/agent-session.ts +77 -8
- package/src/session/blob-store.ts +59 -3
- package/src/session/session-manager.ts +4 -4
- 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 +9 -0
- package/src/tools/ask.ts +56 -1
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/subagent-render.ts +9 -0
- package/src/tools/subagent.ts +26 -2
|
@@ -4,6 +4,7 @@ import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
|
4
4
|
import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
|
|
5
5
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
6
6
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
7
|
+
import { latestUltragoalLedgerEventFromText } from "./ledger-event-renderer";
|
|
7
8
|
import { renderUltragoalStatusMarkdown } from "./state-renderer";
|
|
8
9
|
import { reconcileWorkflowSkillState } from "./state-runtime";
|
|
9
10
|
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
@@ -2127,6 +2128,17 @@ async function reconcileUltragoalState(cwd: string): Promise<void> {
|
|
|
2127
2128
|
goals_path: summary.paths.goalsPath,
|
|
2128
2129
|
};
|
|
2129
2130
|
if (summary.gjcObjective) payload.gjc_objective = summary.gjcObjective;
|
|
2131
|
+
const ledgerText = await Bun.file(summary.paths.ledgerPath)
|
|
2132
|
+
.text()
|
|
2133
|
+
.catch(() => "");
|
|
2134
|
+
const latestLedger = latestUltragoalLedgerEventFromText(ledgerText);
|
|
2135
|
+
if (latestLedger) {
|
|
2136
|
+
payload.latestLedgerEvent = {
|
|
2137
|
+
event: latestLedger.event,
|
|
2138
|
+
...(latestLedger.goalId ? { goalId: latestLedger.goalId } : {}),
|
|
2139
|
+
...(latestLedger.timestamp ? { timestamp: latestLedger.timestamp } : {}),
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2130
2142
|
await reconcileWorkflowSkillState({ cwd, mode: "ultragoal", sessionId, active, phase: status, payload });
|
|
2131
2143
|
} catch (error) {
|
|
2132
2144
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type GcCollectResult,
|
|
5
|
+
type GcContext,
|
|
6
|
+
type GcError,
|
|
7
|
+
type GcPruneOutcome,
|
|
8
|
+
type GcRecord,
|
|
9
|
+
type GcStoreAdapter,
|
|
10
|
+
gcPidStatusLabel,
|
|
11
|
+
gcProbeToLeasePidStatus,
|
|
12
|
+
} from "../gjc-runtime/gc-runtime";
|
|
13
|
+
import { classifyLeaseStatus, readLease, reapDeadOwnerArtifacts } from "./session-lease";
|
|
14
|
+
import {
|
|
15
|
+
type HarnessRootRegistryForGc,
|
|
16
|
+
type HarnessRootRegistryListingForGc,
|
|
17
|
+
listHarnessRootRegistriesForGc,
|
|
18
|
+
removeHarnessRootRegistryFileForGc,
|
|
19
|
+
rewriteHarnessRootRegistryForGc,
|
|
20
|
+
sessionPaths,
|
|
21
|
+
} from "./storage";
|
|
22
|
+
|
|
23
|
+
function errorMessage(error: unknown): string {
|
|
24
|
+
return error instanceof Error ? error.message : String(error);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function exists(file: string): Promise<boolean> {
|
|
28
|
+
try {
|
|
29
|
+
await fs.access(file);
|
|
30
|
+
return true;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function registryErrors(registries: HarnessRootRegistryListingForGc[]): GcError[] {
|
|
38
|
+
return registries
|
|
39
|
+
.filter(registry => registry.error)
|
|
40
|
+
.map(registry => ({
|
|
41
|
+
store: "registry_entries",
|
|
42
|
+
scope: registry.file,
|
|
43
|
+
message: registry.error ?? "registry_error",
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function collectRegistries(ctx: GcContext): Promise<HarnessRootRegistryListingForGc[]> {
|
|
48
|
+
return listHarnessRootRegistriesForGc(ctx.env);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const harnessLeasesGcAdapter: GcStoreAdapter = {
|
|
52
|
+
store: "harness_leases",
|
|
53
|
+
async collect(ctx: GcContext): Promise<GcCollectResult> {
|
|
54
|
+
const records: GcRecord[] = [];
|
|
55
|
+
const errors: GcError[] = [];
|
|
56
|
+
const registries = await collectRegistries(ctx);
|
|
57
|
+
errors.push(...registryErrors(registries).map(error => ({ ...error, store: "harness_leases" as const })));
|
|
58
|
+
|
|
59
|
+
const roots = new Set<string>();
|
|
60
|
+
for (const registry of registries) {
|
|
61
|
+
if (registry.error) continue;
|
|
62
|
+
for (const entry of registry.roots) roots.add(path.resolve(entry.root));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const root of roots) {
|
|
66
|
+
const sessionsDir = path.join(root, "sessions");
|
|
67
|
+
let sessionEntries: string[];
|
|
68
|
+
try {
|
|
69
|
+
sessionEntries = await fs.readdir(sessionsDir);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
72
|
+
if (code === "ENOENT") continue;
|
|
73
|
+
errors.push({ store: "harness_leases", scope: sessionsDir, message: errorMessage(error) });
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const sessionId of sessionEntries) {
|
|
78
|
+
const sessionDir = sessionPaths(root, sessionId).dir;
|
|
79
|
+
try {
|
|
80
|
+
const stat = await fs.stat(sessionDir);
|
|
81
|
+
if (!stat.isDirectory()) continue;
|
|
82
|
+
const lease = await readLease(root, sessionId);
|
|
83
|
+
if (!lease) continue;
|
|
84
|
+
const status = classifyLeaseStatus(lease, { probe: gcProbeToLeasePidStatus(ctx.probe) });
|
|
85
|
+
const pidProbe = ctx.probe(lease.pid);
|
|
86
|
+
const pidStatus = gcPidStatusLabel(pidProbe);
|
|
87
|
+
const removable = status === "dead" && pidProbe.status === "dead";
|
|
88
|
+
records.push({
|
|
89
|
+
store: "harness_leases",
|
|
90
|
+
id: sessionId,
|
|
91
|
+
root,
|
|
92
|
+
path: sessionPaths(root, sessionId).lease,
|
|
93
|
+
pid: lease.pid,
|
|
94
|
+
pid_status: pidStatus,
|
|
95
|
+
status,
|
|
96
|
+
stale: status === "dead",
|
|
97
|
+
removable,
|
|
98
|
+
action: "none",
|
|
99
|
+
reason: removable
|
|
100
|
+
? `lease owner pid ${lease.pid} is dead`
|
|
101
|
+
: `lease owner pid ${lease.pid} is ${pidStatus}; keeping`,
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
errors.push({ store: "harness_leases", scope: sessionDir, message: errorMessage(error) });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { records, errors };
|
|
110
|
+
},
|
|
111
|
+
async prune(record: GcRecord, ctx: GcContext): Promise<GcPruneOutcome> {
|
|
112
|
+
if (!record.root) return { removed: false, skipped: "missing_root" };
|
|
113
|
+
const lease = await readLease(record.root, record.id);
|
|
114
|
+
if (!lease) return { removed: false, skipped: "lease_not_dead_or_missing" };
|
|
115
|
+
const status = classifyLeaseStatus(lease, { probe: gcProbeToLeasePidStatus(ctx.probe) });
|
|
116
|
+
if (status !== "dead") return { removed: false, skipped: "lease_not_dead_or_missing" };
|
|
117
|
+
const removed = await reapDeadOwnerArtifacts(record.root, record.id, lease.ownerId, lease.leaseEpoch, {
|
|
118
|
+
probe: gcProbeToLeasePidStatus(ctx.probe),
|
|
119
|
+
});
|
|
120
|
+
return removed ? { removed: true } : { removed: false, skipped: "reaper_guard_rejected" };
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
async function splitRegistryRoots(registry: HarnessRootRegistryForGc): Promise<{
|
|
125
|
+
liveRoots: HarnessRootRegistryForGc["roots"];
|
|
126
|
+
danglingRoots: HarnessRootRegistryForGc["roots"];
|
|
127
|
+
}> {
|
|
128
|
+
const liveRoots: HarnessRootRegistryForGc["roots"] = [];
|
|
129
|
+
const danglingRoots: HarnessRootRegistryForGc["roots"] = [];
|
|
130
|
+
for (const entry of registry.roots) {
|
|
131
|
+
const sessionDir = sessionPaths(entry.root, registry.sessionId).dir;
|
|
132
|
+
if (await exists(sessionDir)) liveRoots.push(entry);
|
|
133
|
+
else danglingRoots.push(entry);
|
|
134
|
+
}
|
|
135
|
+
return { liveRoots, danglingRoots };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const registryEntriesGcAdapter: GcStoreAdapter = {
|
|
139
|
+
store: "registry_entries",
|
|
140
|
+
async collect(ctx: GcContext): Promise<GcCollectResult> {
|
|
141
|
+
const records: GcRecord[] = [];
|
|
142
|
+
const errors: GcError[] = [];
|
|
143
|
+
const registries = await collectRegistries(ctx);
|
|
144
|
+
errors.push(...registryErrors(registries));
|
|
145
|
+
|
|
146
|
+
for (const registry of registries) {
|
|
147
|
+
if (registry.error) continue;
|
|
148
|
+
try {
|
|
149
|
+
const { liveRoots, danglingRoots } = await splitRegistryRoots(registry);
|
|
150
|
+
if (danglingRoots.length === 0) continue;
|
|
151
|
+
records.push({
|
|
152
|
+
store: "registry_entries",
|
|
153
|
+
id: registry.sessionId,
|
|
154
|
+
path: registry.file,
|
|
155
|
+
pid_status: "none",
|
|
156
|
+
status: "dangling",
|
|
157
|
+
stale: true,
|
|
158
|
+
removable: true,
|
|
159
|
+
action: "none",
|
|
160
|
+
reason: `dangling roots: ${danglingRoots.map(entry => entry.root).join(", ")}`,
|
|
161
|
+
detail: `${danglingRoots.length} dangling, ${liveRoots.length} live`,
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
errors.push({ store: "registry_entries", scope: registry.file, message: errorMessage(error) });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { records, errors };
|
|
169
|
+
},
|
|
170
|
+
async prune(record: GcRecord, ctx: GcContext): Promise<GcPruneOutcome> {
|
|
171
|
+
if (!record.path) return { removed: false, skipped: "missing_registry_path" };
|
|
172
|
+
const registries = await collectRegistries(ctx);
|
|
173
|
+
const registry = registries.find(entry => entry.file === record.path);
|
|
174
|
+
if (!registry || registry.error) return { removed: false, skipped: "registry_not_readable" };
|
|
175
|
+
const { liveRoots, danglingRoots } = await splitRegistryRoots(registry);
|
|
176
|
+
if (danglingRoots.length === 0) return { removed: false, skipped: "no_dangling_roots" };
|
|
177
|
+
if (liveRoots.length === 0) {
|
|
178
|
+
await removeHarnessRootRegistryFileForGc(record.path);
|
|
179
|
+
} else {
|
|
180
|
+
await rewriteHarnessRootRegistryForGc(record.path, { sessionId: registry.sessionId, roots: liveRoots });
|
|
181
|
+
}
|
|
182
|
+
return { removed: true };
|
|
183
|
+
},
|
|
184
|
+
};
|
|
@@ -670,3 +670,14 @@ export async function resolveOwner(root: string, sessionId: string): Promise<Res
|
|
|
670
670
|
const live = status === "live" || status === "expiredAlive" || status === "epermAlive";
|
|
671
671
|
return { live, socketPath: lease.endpoint?.path ?? null, lease };
|
|
672
672
|
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Owner liveness for verbs that do not route to the owner (e.g. `classify`): a routable owner
|
|
676
|
+
* has a live lease and a socket endpoint. This is the same lease/socket probe `observe` uses to
|
|
677
|
+
* decide routing, so non-routing verbs derive `ownerLive` consistently instead of assuming the
|
|
678
|
+
* owner is gone (which would misclassify a live owner as vanished/restart-clean).
|
|
679
|
+
*/
|
|
680
|
+
export async function resolveOwnerLive(root: string, sessionId: string): Promise<boolean> {
|
|
681
|
+
const owner = await resolveOwner(root, sessionId);
|
|
682
|
+
return owner.live && owner.socketPath !== null;
|
|
683
|
+
}
|
|
@@ -32,6 +32,18 @@ interface HarnessRootRegistry {
|
|
|
32
32
|
roots: HarnessRootRegistryEntry[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface HarnessRootRegistryForGc {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
roots: HarnessRootRegistryEntry[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface HarnessRootRegistryListingForGc {
|
|
41
|
+
sessionId: string;
|
|
42
|
+
file: string;
|
|
43
|
+
roots: HarnessRootRegistryEntry[];
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
interface ResolveHarnessSessionRootOptions {
|
|
36
48
|
expectedWorkspace?: string;
|
|
37
49
|
}
|
|
@@ -101,6 +113,64 @@ async function writeHarnessRootRegistry(
|
|
|
101
113
|
const file = harnessRootRegistryPath(registry.sessionId, env);
|
|
102
114
|
await writeJsonAtomicPrivate(file, registry);
|
|
103
115
|
}
|
|
116
|
+
|
|
117
|
+
function parseHarnessRootRegistryForGc(value: unknown, fallbackSessionId: string): HarnessRootRegistryForGc | null {
|
|
118
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
119
|
+
const registry = value as Record<string, unknown>;
|
|
120
|
+
if (typeof registry.sessionId !== "string" || !Array.isArray(registry.roots)) return null;
|
|
121
|
+
const roots: HarnessRootRegistryEntry[] = [];
|
|
122
|
+
for (const entry of registry.roots) {
|
|
123
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
124
|
+
const rootEntry = entry as Record<string, unknown>;
|
|
125
|
+
if (typeof rootEntry.root !== "string" || typeof rootEntry.updatedAt !== "string") return null;
|
|
126
|
+
roots.push({ root: rootEntry.root, updatedAt: rootEntry.updatedAt });
|
|
127
|
+
}
|
|
128
|
+
return { sessionId: registry.sessionId || fallbackSessionId, roots };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @internal */
|
|
132
|
+
export async function listHarnessRootRegistriesForGc(
|
|
133
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
134
|
+
): Promise<HarnessRootRegistryListingForGc[]> {
|
|
135
|
+
const dir = harnessRootRegistryDir(env);
|
|
136
|
+
let entries: string[];
|
|
137
|
+
try {
|
|
138
|
+
entries = await fs.readdir(dir);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
141
|
+
if (code === "ENOENT") return [];
|
|
142
|
+
return [{ sessionId: "", file: dir, roots: [], error: (error as Error).message }];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const registries: HarnessRootRegistryListingForGc[] = [];
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
if (!entry.endsWith(".json")) continue;
|
|
148
|
+
const file = path.join(dir, entry);
|
|
149
|
+
const fallbackSessionId = entry.slice(0, -".json".length);
|
|
150
|
+
try {
|
|
151
|
+
const raw = await fs.readFile(file, "utf8");
|
|
152
|
+
const parsed = parseHarnessRootRegistryForGc(JSON.parse(raw), fallbackSessionId);
|
|
153
|
+
if (!parsed) {
|
|
154
|
+
registries.push({ sessionId: fallbackSessionId, file, roots: [], error: "malformed_registry" });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
registries.push({ sessionId: parsed.sessionId, file, roots: parsed.roots });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
registries.push({ sessionId: fallbackSessionId, file, roots: [], error: (error as Error).message });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return registries;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @internal */
|
|
166
|
+
export async function rewriteHarnessRootRegistryForGc(file: string, registry: HarnessRootRegistryForGc): Promise<void> {
|
|
167
|
+
await writeJsonAtomicPrivate(file, registry);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @internal */
|
|
171
|
+
export async function removeHarnessRootRegistryFileForGc(file: string): Promise<void> {
|
|
172
|
+
await fs.rm(file, { force: true });
|
|
173
|
+
}
|
|
104
174
|
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
105
175
|
export const MAX_UNIX_SOCKET_PATH_BYTES = 100;
|
|
106
176
|
|