Skip to content

Commit babbcd6

Browse files
authored
refactor: migrate maintained runtime support to TypeScript (#991)
## Summary - migrate CLI export/watch formatting, routing helpers, execution boundaries, state snapshots, and nested-cluster support from maintained JavaScript to strict TypeScript - preserve every existing CommonJS runtime path, export order, function/class arity, and packed-package output - add unknown-first runtime guards at untyped process, JSON, worker, message-bus, and validator boundaries without `any` or TypeScript suppressions - split state snapshot normalization into a shared internal module to satisfy file-size and clone policy ## Validation - `npm run typecheck` - `npm run lint -- --quiet` - `npx mocha tests/package-smoke.test.js` - focused CLI/routing/execution/snapshot/nested-cluster suites - differential execution against the committed JavaScript for snapshot reducers, snapshotter class surface, sub-cluster validation/defaults, and bridge forwarding - real worker-thread copy behavior probe - `npm run opcore:check` - `opcore-zero check --repo . --staged --json` - `opcore-zero sense --repo . --staged --json`
1 parent 9595c29 commit babbcd6

24 files changed

Lines changed: 1507 additions & 1170 deletions

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ report/
6060
# Generated/temp files
6161
cli/event-copy.js
6262
cli/message-formatter-utils.js
63+
cli/json-export.js
64+
cli/message-formatters-watch.js
6365
test-metadata-manual.sh
6466
test-isolated-fix.js
6567
lib/agent-cli-provider/
@@ -139,12 +141,21 @@ lib/settings-validation.js
139141
lib/settings-validation.d.ts
140142
lib/settings-issue-providers.js
141143
lib/settings-issue-providers.d.ts
144+
src/config-router.js
145+
src/claude-credentials.js
146+
src/copy-worker.js
147+
src/darwin-keychain-boundary.js
142148
src/guidance-topics.js
143149
src/input-helpers.js
144150
src/ledger-sequence.js
151+
src/message-bus-bridge.js
145152
src/message-buffer.js
153+
src/state-snapshot-normalization.js
154+
src/state-snapshot.js
155+
src/state-snapshotter.js
146156
src/task-startup-error.js
147157
src/task-runner.js
158+
src/worktree-tooling-env.js
148159
src/omp-blob-root.js
149160
src/omp-config-overlay.js
150161
task-lib/completion.js
@@ -154,6 +165,7 @@ task-lib/process-termination.js
154165
task-lib/omp-storage-root.js
155166
src/omp-execution-fingerprint.js
156167
src/omp-session-limits.js
168+
src/agent/agent-trigger-evaluator.js
157169
src/agent/context-replay-policy.js
158170
src/agent/critical-agent-policy.js
159171
src/agent/provider-control-plane.js
@@ -165,4 +177,5 @@ src/providers/capabilities.js
165177
src/providers/google/index.js
166178
src/providers/openai/index.js
167179
src/providers/opencode/index.js
180+
src/schemas/sub-cluster.js
168181
src/template-validation/report-formatter.js
Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
1-
const fs = require('fs');
1+
import fs = require('fs');
22

3-
function indentJson(value, spaces) {
3+
interface LedgerMessageIterator extends Iterator<unknown> {
4+
return?: () => IteratorResult<unknown>;
5+
}
6+
7+
interface ClusterLedger {
8+
iterateAll(clusterId: string): LedgerMessageIterator;
9+
}
10+
11+
interface ExportStream {
12+
readonly fd?: number;
13+
write(value: string): unknown;
14+
}
15+
16+
interface JsonExportOptions {
17+
ledger: ClusterLedger;
18+
clusterId: string;
19+
outputPath?: string | null;
20+
stdout?: ExportStream;
21+
}
22+
23+
interface Destination {
24+
close(): void;
25+
write(value: string): void;
26+
}
27+
28+
function isInteger(value: unknown): value is number {
29+
return Number.isInteger(value);
30+
}
31+
32+
function indentJson(value: unknown, spaces: number): string {
433
const prefix = ' '.repeat(spaces);
534
return JSON.stringify(value, null, 2)
635
.split('\n')
736
.map((line) => `${prefix}${line}`)
837
.join('\n');
938
}
1039

11-
function writeAll(fd, value) {
40+
function writeAll(fd: number, value: string): void {
1241
const bytes = Buffer.from(value);
1342
let offset = 0;
1443
while (offset < bytes.length) {
@@ -20,7 +49,10 @@ function writeAll(fd, value) {
2049
}
2150
}
2251

23-
function createDestination(outputPath, stdout) {
52+
function createDestination(
53+
outputPath: string | null | undefined,
54+
stdout: ExportStream
55+
): Destination {
2456
if (outputPath) {
2557
const fd = fs.openSync(outputPath, 'w');
2658
return {
@@ -29,16 +61,19 @@ function createDestination(outputPath, stdout) {
2961
};
3062
}
3163

32-
if (Number.isInteger(stdout.fd)) {
64+
if (isInteger(stdout.fd)) {
65+
const fd = stdout.fd;
3366
return {
34-
close() {},
35-
write: (value) => writeAll(stdout.fd, value),
67+
close(): void {},
68+
write: (value) => writeAll(fd, value),
3669
};
3770
}
3871

3972
return {
40-
close() {},
41-
write: (value) => stdout.write(value),
73+
close(): void {},
74+
write: (value): void => {
75+
stdout.write(value);
76+
},
4277
};
4378
}
4479

@@ -47,7 +82,7 @@ function streamClusterJsonExport({
4782
clusterId,
4883
outputPath = null,
4984
stdout = process.stdout,
50-
}) {
85+
}: JsonExportOptions): void {
5186
const destination = createDestination(outputPath, stdout);
5287
const iterator = ledger.iterateAll(clusterId);
5388
try {
@@ -74,4 +109,4 @@ function streamClusterJsonExport({
74109
}
75110
}
76111

77-
module.exports = { streamClusterJsonExport };
112+
export = { streamClusterJsonExport };
Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,141 @@
1-
const chalk = require('chalk');
2-
const {
3-
buildClusterPrefix,
4-
getColorForSender,
5-
parseDataField,
6-
} = require('./message-formatter-utils');
7-
const { EVENT_COPY, formatMergeStatus } = require('./event-copy');
1+
import chalk = require('chalk');
2+
import formatterUtils = require('./message-formatter-utils');
3+
import eventCopy = require('./event-copy');
84

9-
const DEFAULT_WRITER = Object.freeze({ printLine: (text) => console.log(text) });
5+
const { buildClusterPrefix, getColorForSender, parseDataField } = formatterUtils;
6+
const { EVENT_COPY, formatMergeStatus } = eventCopy;
107

11-
function formatAgentError(msg, clusterPrefix, writer) {
8+
interface WatchMessageData {
9+
issue_number?: string | number;
10+
title?: string;
11+
prompt?: string;
12+
approved?: boolean | string;
13+
summary?: string;
14+
errors?: unknown;
15+
issues?: unknown;
16+
pr_number?: string | number;
17+
merged?: unknown;
18+
}
19+
20+
interface WatchMessage {
21+
sender: string;
22+
cluster_id: string;
23+
topic: string;
24+
content?: {
25+
text?: string;
26+
data?: WatchMessageData;
27+
};
28+
}
29+
30+
interface LineWriter {
31+
printLine(text: string): void;
32+
}
33+
34+
type WatchMessageHandler = (
35+
message: WatchMessage,
36+
clusterPrefix: string,
37+
writer: LineWriter
38+
) => void;
39+
40+
interface CountableData {
41+
readonly length: number;
42+
}
43+
44+
const DEFAULT_WRITER: Readonly<LineWriter> = Object.freeze({
45+
printLine: (text: string) => console.log(text),
46+
});
47+
48+
function isCountableData(value: unknown): value is CountableData {
49+
if (typeof value === 'string') return true;
50+
return (
51+
typeof value === 'object' &&
52+
value !== null &&
53+
'length' in value &&
54+
typeof value.length === 'number'
55+
);
56+
}
57+
58+
function formatAgentError(
59+
msg: WatchMessage,
60+
clusterPrefix: string,
61+
writer: LineWriter
62+
): void {
1263
writer.printLine(`${clusterPrefix} ${chalk.bold.red(`Error: ${msg.sender}`)}`);
1364
if (msg.content?.text) {
1465
writer.printLine(`${clusterPrefix} ${chalk.red(msg.content.text)}`);
1566
}
1667
writer.printLine(`${clusterPrefix} Next: zeroshot logs ${msg.cluster_id} -f`);
1768
}
1869

19-
function formatIssueOpened(msg, clusterPrefix, writer) {
70+
function formatIssueOpened(
71+
msg: WatchMessage,
72+
clusterPrefix: string,
73+
writer: LineWriter
74+
): void {
2075
const issueNum = msg.content?.data?.issue_number || '';
2176
const title = msg.content?.data?.title || '';
2277
const prompt = msg.content?.data?.prompt || msg.content?.text || '';
2378
const taskDesc = title === 'Manual Input' && prompt ? prompt : title;
2479
const truncatedDesc =
2580
taskDesc && taskDesc.length > 60 ? `${taskDesc.substring(0, 60)}...` : taskDesc;
26-
const eventText = `Started ${issueNum ? `#${issueNum}` : 'task'}${truncatedDesc ? chalk.dim(` - ${truncatedDesc}`) : ''}`;
81+
const issueLabel = issueNum ? `#${issueNum}` : 'task';
82+
const description = truncatedDesc ? chalk.dim(` - ${truncatedDesc}`) : '';
83+
const eventText = `Started ${issueLabel}${description}`;
2784
writer.printLine(`${clusterPrefix} ${eventText}`);
2885
}
2986

30-
function formatImplementationReady(msg, clusterPrefix, writer = DEFAULT_WRITER) {
87+
function formatImplementationReady(
88+
msg: WatchMessage,
89+
clusterPrefix: string,
90+
writer: LineWriter = DEFAULT_WRITER
91+
): void {
3192
const agentName = getColorForSender(msg.sender)(msg.sender);
3293
writer.printLine(
3394
`${clusterPrefix} ${agentName} ${EVENT_COPY.IMPLEMENTATION_READY.toLowerCase()}`
3495
);
3596
}
3697

37-
function printRejectionDetails(data, clusterPrefix, writer) {
98+
function printRejectionDetails(
99+
data: WatchMessageData,
100+
clusterPrefix: string,
101+
writer: LineWriter
102+
): void {
38103
const errors = parseDataField(data.errors);
39104
const issues = parseDataField(data.issues);
40-
if (errors.length > 0) {
105+
if (isCountableData(errors) && errors.length > 0) {
41106
const count = JSON.stringify(errors).length;
42107
writer.printLine(
43108
`${clusterPrefix} ${chalk.red('•')} ${errors.length} error${errors.length > 1 ? 's' : ''} (${count} chars)`
44109
);
45110
}
46-
if (issues.length > 0) {
111+
if (isCountableData(issues) && issues.length > 0) {
47112
const count = JSON.stringify(issues).length;
48113
writer.printLine(
49114
`${clusterPrefix} ${chalk.yellow('•')} ${issues.length} issue${issues.length > 1 ? 's' : ''} (${count} chars)`
50115
);
51116
}
52117
}
53118

54-
function formatValidationResult(msg, clusterPrefix, writer) {
119+
function formatValidationResult(
120+
msg: WatchMessage,
121+
clusterPrefix: string,
122+
writer: LineWriter
123+
): void {
55124
const agentName = getColorForSender(msg.sender)(msg.sender);
56125
const data = msg.content?.data;
57126
const approved = data?.approved === 'true' || data?.approved === true;
58127
const status = approved ? chalk.green('Approved') : chalk.red('Rejected');
59128
let eventText = `${agentName} ${status}`;
60129
if (data?.summary && !approved) eventText += chalk.dim(` - ${data.summary}`);
61130
writer.printLine(`${clusterPrefix} ${eventText}`);
62-
if (!approved) printRejectionDetails(data, clusterPrefix, writer);
131+
if (!approved && data) printRejectionDetails(data, clusterPrefix, writer);
63132
}
64133

65-
function formatPrCreated(msg, clusterPrefix, writer = DEFAULT_WRITER) {
134+
function formatPrCreated(
135+
msg: WatchMessage,
136+
clusterPrefix: string,
137+
writer: LineWriter = DEFAULT_WRITER
138+
): void {
66139
const agentName = getColorForSender(msg.sender)(msg.sender);
67140
const prNum = msg.content?.data?.pr_number || '';
68141
let eventText = `${agentName} ${EVENT_COPY.PR_CREATED.toLowerCase()}${prNum ? ` #${prNum}` : ''}`;
@@ -71,33 +144,45 @@ function formatPrCreated(msg, clusterPrefix, writer = DEFAULT_WRITER) {
71144
writer.printLine(`${clusterPrefix} ${eventText}`);
72145
}
73146

74-
function formatPrMerged(msg, clusterPrefix, writer) {
147+
function formatPrMerged(
148+
msg: WatchMessage,
149+
clusterPrefix: string,
150+
writer: LineWriter
151+
): void {
75152
const agentName = getColorForSender(msg.sender)(msg.sender);
76153
writer.printLine(`${clusterPrefix} ${agentName} merged PR`);
77154
}
78155

79-
function formatUnknownTopic(msg, clusterPrefix, writer) {
156+
function formatUnknownTopic(
157+
msg: WatchMessage,
158+
clusterPrefix: string,
159+
writer: LineWriter
160+
): void {
80161
const agentName = getColorForSender(msg.sender)(msg.sender);
81162
const eventText = `${agentName} ${msg.topic.toLowerCase().replace(/_/g, ' ')}`;
82163
writer.printLine(`${clusterPrefix} ${eventText}`);
83164
}
84165

85-
function formatWatchMode(msg, isActive, writer = DEFAULT_WRITER) {
166+
function formatWatchMode(
167+
msg: WatchMessage,
168+
isActive: boolean,
169+
writer: LineWriter = DEFAULT_WRITER
170+
): true {
86171
if (msg.topic === 'AGENT_OUTPUT' || msg.topic === 'AGENT_LIFECYCLE') return true;
87172
const clusterPrefix = buildClusterPrefix(msg.cluster_id, isActive);
88-
const handlers = {
173+
const handlers: Readonly<Record<string, WatchMessageHandler>> = {
89174
AGENT_ERROR: formatAgentError,
90175
ISSUE_OPENED: formatIssueOpened,
91176
IMPLEMENTATION_READY: formatImplementationReady,
92177
VALIDATION_RESULT: formatValidationResult,
93178
PR_CREATED: formatPrCreated,
94179
PR_MERGED: formatPrMerged,
95180
};
96-
(handlers[msg.topic] || formatUnknownTopic)(msg, clusterPrefix, writer);
181+
(handlers[msg.topic] ?? formatUnknownTopic)(msg, clusterPrefix, writer);
97182
return true;
98183
}
99184

100-
module.exports = {
185+
export = {
101186
formatWatchMode,
102187
formatImplementationReady,
103188
formatPrCreated,

0 commit comments

Comments
 (0)