@linzumi/cli 1.0.116 → 1.0.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +359 -359
- package/package.json +1 -1
- package/scripts/build-onboarding-demo-evidence.mjs +186 -0
- package/scripts/build-onboarding-discovery-parity-oracle.mjs +380 -0
- package/scripts/build-onboarding-flow-parity-oracle.mjs +562 -0
- package/scripts/build-onboarding-gateway-parity-oracle.mjs +313 -0
- package/scripts/build-onboarding-support-parity-oracle.mjs +484 -0
package/package.json
CHANGED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 13, onboarding - visual evidence).
|
|
3
|
+
// Relationship: Builds the TS TWIN of the onboarding demo entrypoint
|
|
4
|
+
// (packages/linzumi-cli-rescript/src/onboarding/CommanderOnboardingDemoMain.res):
|
|
5
|
+
// the REAL runSignupFlow mounted on the live terminal - real
|
|
6
|
+
// @inquirer/prompts rendering, the real raw-keypress project picker, the
|
|
7
|
+
// REAL createSignupServerClient speaking HTTP to the SAME in-process
|
|
8
|
+
// ScriptedAuthServer (imported from the ReScript package's compiled
|
|
9
|
+
// module - one server implementation, zero drift), replaying the SAME
|
|
10
|
+
// commanderOnboardingDemoScenario.json. scripts/onboarding_evidence_
|
|
11
|
+
// capture.sh drives both entrypoints with the same tmux key schedule and
|
|
12
|
+
// byte-compares the captures.
|
|
13
|
+
//
|
|
14
|
+
// Determinism: the scenario is dummy data; the only wall-clock effects
|
|
15
|
+
// (config-file timestamps) never reach the terminal (the demo runs with
|
|
16
|
+
// debugLaunchPayload off unless --debug-signup is passed for ad-hoc use).
|
|
17
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { build } from 'esbuild';
|
|
21
|
+
|
|
22
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
23
|
+
|
|
24
|
+
const driverSource = `
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
import { join } from 'node:path';
|
|
27
|
+
import { runSignupFlow, signupSuggestTasksForProjectForTest, signupPromptCancelMessage } from './src/signupFlow';
|
|
28
|
+
import { createSignupServerClient } from './src/signupServerClient';
|
|
29
|
+
// The ONE scripted-server implementation: the ReScript package's cluster 3
|
|
30
|
+
// double, imported as plain ESM.
|
|
31
|
+
import * as ScriptedAuthServer from '../linzumi-cli-rescript/src/parity/ScriptedAuthServer.res.mjs';
|
|
32
|
+
|
|
33
|
+
const scenarioPath = join(
|
|
34
|
+
'${packageRoot}',
|
|
35
|
+
'..',
|
|
36
|
+
'linzumi-cli-rescript',
|
|
37
|
+
'src',
|
|
38
|
+
'onboarding',
|
|
39
|
+
'commanderOnboardingDemoScenario.json'
|
|
40
|
+
);
|
|
41
|
+
const scenario = JSON.parse(readFileSync(scenarioPath, 'utf8'));
|
|
42
|
+
|
|
43
|
+
function reviveProjects(projects) {
|
|
44
|
+
return (projects ?? []).map((project) => ({
|
|
45
|
+
path: project.path,
|
|
46
|
+
language: project.language,
|
|
47
|
+
source: project.source,
|
|
48
|
+
...(project.gitHead === undefined || project.gitHead === null
|
|
49
|
+
? {}
|
|
50
|
+
: { gitHead: project.gitHead }),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const jsonResponse = (value) =>
|
|
55
|
+
ScriptedAuthServer.jsonResponse(200, JSON.stringify(value ?? {}));
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
const args = process.argv.slice(2);
|
|
59
|
+
const mock = args.includes('--mock');
|
|
60
|
+
const handle = await ScriptedAuthServer.start({
|
|
61
|
+
routes: [
|
|
62
|
+
{
|
|
63
|
+
method: 'POST',
|
|
64
|
+
path: '/api/v2/signup/email-code/start',
|
|
65
|
+
responses: [jsonResponse(scenario.server.emailCodeStart)],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
method: 'POST',
|
|
69
|
+
path: '/api/v2/signup/email-code/verify',
|
|
70
|
+
responses: [jsonResponse(scenario.server.emailCodeVerify)],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
method: 'POST',
|
|
74
|
+
path: '/api/v2/signup/mission-control/complete',
|
|
75
|
+
responses: [jsonResponse(scenario.server.missionControlComplete)],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
method: 'POST',
|
|
79
|
+
path: '/api/v2/signup/mission-control/start-tasks',
|
|
80
|
+
responses: [jsonResponse(scenario.server.missionControlStartTasks)],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
const serviceUrl = handle.baseUrl;
|
|
85
|
+
const preflight = scenario.preflight ?? {};
|
|
86
|
+
const tools = preflight.tools ?? {};
|
|
87
|
+
const demoHome = process.env.LINZUMI_ONBOARDING_DEMO_HOME ?? process.env.HOME ?? '/tmp';
|
|
88
|
+
const noopCacheStore = { read: () => undefined, write: () => {} };
|
|
89
|
+
try {
|
|
90
|
+
await runSignupFlow({
|
|
91
|
+
// Real stdio: the prompts default to the REAL inquirer surface and
|
|
92
|
+
// the raw-keypress picker (deps.prompts stays undefined).
|
|
93
|
+
input: process.stdin,
|
|
94
|
+
output: process.stdout,
|
|
95
|
+
preflight: {
|
|
96
|
+
cwd: demoHome,
|
|
97
|
+
homeDir: demoHome,
|
|
98
|
+
probeTool: async (command) => {
|
|
99
|
+
const tool = tools[command];
|
|
100
|
+
if (tool === undefined) {
|
|
101
|
+
return { command, available: false };
|
|
102
|
+
}
|
|
103
|
+
return { command, available: tool.available, version: tool.version ?? undefined };
|
|
104
|
+
},
|
|
105
|
+
readGitEmail: async () => preflight.gitEmail ?? undefined,
|
|
106
|
+
discoverCodeRoots: async () => preflight.codeRoots ?? [],
|
|
107
|
+
checkBrowserHandoff: async () => preflight.browserHandoff !== false,
|
|
108
|
+
},
|
|
109
|
+
projectDiscovery: {
|
|
110
|
+
discoverProjects: async () => reviveProjects(scenario.projects),
|
|
111
|
+
},
|
|
112
|
+
taskSuggestion: {
|
|
113
|
+
suggestTasks: (project, output) =>
|
|
114
|
+
signupSuggestTasksForProjectForTest({
|
|
115
|
+
project,
|
|
116
|
+
output,
|
|
117
|
+
taskCacheStore: noopCacheStore,
|
|
118
|
+
codexTaskRunner: async () => scenario.suggestedTasks ?? [],
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
forceSignup: args.includes('--force-signup'),
|
|
122
|
+
serviceUrl,
|
|
123
|
+
openBrowser: false,
|
|
124
|
+
debugLaunchPayload: args.includes('--debug-signup'),
|
|
125
|
+
signupServerClient: mock ? false : createSignupServerClient({ serviceUrl }),
|
|
126
|
+
commanderLauncher: async () => ({
|
|
127
|
+
status: 'started',
|
|
128
|
+
runnerId: scenario.launch?.runnerId ?? 'demo-commander',
|
|
129
|
+
restartCommand:
|
|
130
|
+
scenario.launch?.restartCommand ?? 'npx -y @linzumi/cli@latest connect',
|
|
131
|
+
instanceId: 'demo-instance',
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
await handle.stop();
|
|
135
|
+
process.exit(0);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
await handle.stop();
|
|
138
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
139
|
+
process.stdout.write((signupPromptCancelMessage(error) ?? message) + '\\n');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
await mkdir(join(packageRoot, '.differential'), { recursive: true });
|
|
148
|
+
|
|
149
|
+
// The build-auth-parity-oracle.mjs banner convention (inquirer's CJS deps
|
|
150
|
+
// require node builtins at module scope).
|
|
151
|
+
const banner = {
|
|
152
|
+
js: "import { createRequire as __onboardingDemoCreateRequire } from 'node:module';\nconst require = __onboardingDemoCreateRequire(import.meta.url);",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
await build({
|
|
156
|
+
banner,
|
|
157
|
+
stdin: {
|
|
158
|
+
contents: driverSource,
|
|
159
|
+
resolveDir: packageRoot,
|
|
160
|
+
sourcefile: 'onboarding-demo-evidence-driver.ts',
|
|
161
|
+
loader: 'ts',
|
|
162
|
+
},
|
|
163
|
+
bundle: true,
|
|
164
|
+
platform: 'node',
|
|
165
|
+
target: 'node20',
|
|
166
|
+
format: 'esm',
|
|
167
|
+
outfile: join(packageRoot, '.differential/onboarding-demo-evidence.mjs'),
|
|
168
|
+
minify: false,
|
|
169
|
+
sourcemap: false,
|
|
170
|
+
legalComments: 'none',
|
|
171
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Keep the scenario honest: fail the build loudly if the shared file is
|
|
175
|
+
// missing (the twin reads it at RUNTIME from the sibling package).
|
|
176
|
+
await readFile(
|
|
177
|
+
join(
|
|
178
|
+
packageRoot,
|
|
179
|
+
'..',
|
|
180
|
+
'linzumi-cli-rescript',
|
|
181
|
+
'src',
|
|
182
|
+
'onboarding',
|
|
183
|
+
'commanderOnboardingDemoScenario.json'
|
|
184
|
+
),
|
|
185
|
+
'utf8'
|
|
186
|
+
);
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// Spec: kandan/plans/2026-07-10-compute-nodes-rescript-commander-zero-ts-program.md
|
|
2
|
+
// (M3 cluster 13, onboarding - the DISCOVERY STACK slice).
|
|
3
|
+
// Relationship: Builds the TS-side PARITY ORACLE for the onboarding
|
|
4
|
+
// discovery port. The oracle bundles the REAL TS discovery modules -
|
|
5
|
+
// onboardingProjectDiscovery.ts, onboardingConversationDiscovery.ts,
|
|
6
|
+
// onboardingDiscoveryAgent.ts, onboardingDiscoveryChildProcess.ts,
|
|
7
|
+
// onboardingPlaceResponsiveness.ts - plus the agent builder's REAL seam
|
|
8
|
+
// modules (codexHome.ts, mcpConfig.ts, safeWorkingDir.ts, probed so the
|
|
9
|
+
// ReScript port's injected deps carry the SAME real values) behind the
|
|
10
|
+
// cluster 1/2 one-shot BATCH driver (a JSON array of cases on stdin, a
|
|
11
|
+
// JSON array of results on stdout).
|
|
12
|
+
//
|
|
13
|
+
// CHILD ROLE MODE: when LINZUMI_ONBOARDING_DISCOVERY_ROLE is set in the
|
|
14
|
+
// environment the oracle becomes a REAL discovery child running the REAL
|
|
15
|
+
// runOnboardingDiscoveryChildRole over process stdin/stdout - exactly the
|
|
16
|
+
// CLI entry's dispatch (src/index.ts main) - so EITHER impl's parent can
|
|
17
|
+
// spawn this bundle as its discovery child (the cross-impl process legs).
|
|
18
|
+
//
|
|
19
|
+
// Determinism: both impls scan the SAME conformance-built scratch trees
|
|
20
|
+
// (absolute paths ride the cases; nothing is normalized here - the
|
|
21
|
+
// conformance normalizes wall-clock durations on both sides identically).
|
|
22
|
+
//
|
|
23
|
+
// SECRETS: synthetic case payloads only; .differential/ is gitignored.
|
|
24
|
+
import { mkdir } from 'node:fs/promises';
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { build } from 'esbuild';
|
|
28
|
+
|
|
29
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
30
|
+
|
|
31
|
+
const driverSource = `
|
|
32
|
+
import {
|
|
33
|
+
discoverCurrentGitProject,
|
|
34
|
+
defaultLocalProjectSourceRootsForTest,
|
|
35
|
+
} from './src/onboardingProjectDiscovery';
|
|
36
|
+
import {
|
|
37
|
+
cleanConversationTitleForTest,
|
|
38
|
+
discoverLocalConversations,
|
|
39
|
+
readLocalConversationImportMessages,
|
|
40
|
+
} from './src/onboardingConversationDiscovery';
|
|
41
|
+
import {
|
|
42
|
+
conversationDiscoveryPrompt,
|
|
43
|
+
onboardingDiscoveryAgentPlans,
|
|
44
|
+
onboardingDiscoveryAgentProcess,
|
|
45
|
+
onboardingDiscoveryAgentProcessesForTest,
|
|
46
|
+
projectDiscoveryPrompt,
|
|
47
|
+
} from './src/onboardingDiscoveryAgent';
|
|
48
|
+
import {
|
|
49
|
+
discoverCurrentGitProjectInChildProcess,
|
|
50
|
+
discoverLocalConversationsInChildProcess,
|
|
51
|
+
onboardingDiscoveryRoleEnvKey,
|
|
52
|
+
runOnboardingDiscoveryChildRole,
|
|
53
|
+
} from './src/onboardingDiscoveryChildProcess';
|
|
54
|
+
import {
|
|
55
|
+
isPlaceResponsive,
|
|
56
|
+
probeWorkerSourceForTest,
|
|
57
|
+
} from './src/onboardingPlaceResponsiveness';
|
|
58
|
+
import {
|
|
59
|
+
codexCredentialStoreFileArgs,
|
|
60
|
+
ensureIsolatedCodexHome,
|
|
61
|
+
} from './src/codexHome';
|
|
62
|
+
import { codexMcpConfigArgs, linzumiMcpServerConfig } from './src/mcpConfig';
|
|
63
|
+
import { assessDangerousCwd } from './src/safeWorkingDir';
|
|
64
|
+
|
|
65
|
+
function outcome(run) {
|
|
66
|
+
try {
|
|
67
|
+
const value = run();
|
|
68
|
+
return value === undefined ? { defined: false } : { defined: true, value };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
threw: true,
|
|
72
|
+
message: error instanceof Error ? error.message : String(error),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const savedEnv = {};
|
|
78
|
+
|
|
79
|
+
function setCaseEnv(env) {
|
|
80
|
+
for (const [key, value] of Object.entries(env ?? {})) {
|
|
81
|
+
savedEnv[key] = process.env[key];
|
|
82
|
+
if (value === null) {
|
|
83
|
+
delete process.env[key];
|
|
84
|
+
} else {
|
|
85
|
+
process.env[key] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function restoreCaseEnv() {
|
|
91
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
92
|
+
if (value === undefined) {
|
|
93
|
+
delete process.env[key];
|
|
94
|
+
} else {
|
|
95
|
+
process.env[key] = value;
|
|
96
|
+
}
|
|
97
|
+
delete savedEnv[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function scriptedResponsiveness(testCase, probeCalls) {
|
|
102
|
+
if (testCase.unresponsivePlaces === undefined && testCase.probeTimeoutMs === undefined) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const unresponsive = new Set(testCase.unresponsivePlaces ?? []);
|
|
106
|
+
return {
|
|
107
|
+
timeoutMs: testCase.probeTimeoutMs,
|
|
108
|
+
probe: (path, timeoutMs) => {
|
|
109
|
+
probeCalls.push({ path, timeoutMs });
|
|
110
|
+
return !unresponsive.has(path);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleProjectDiscovery(testCase) {
|
|
116
|
+
const probeCalls = [];
|
|
117
|
+
setCaseEnv(testCase.env);
|
|
118
|
+
try {
|
|
119
|
+
const summary = discoverCurrentGitProject({
|
|
120
|
+
cwd: testCase.args.cwd,
|
|
121
|
+
nowMs: testCase.args.nowMs ?? undefined,
|
|
122
|
+
homeDir: testCase.args.homeDir ?? undefined,
|
|
123
|
+
sourceRoots: testCase.args.sourceRoots ?? undefined,
|
|
124
|
+
selectedScanRoots: testCase.args.selectedScanRoots ?? undefined,
|
|
125
|
+
includeRuntimeCwdHint: testCase.args.includeRuntimeCwdHint ?? undefined,
|
|
126
|
+
sourceRootMaxDepth: testCase.args.sourceRootMaxDepth ?? undefined,
|
|
127
|
+
sourceRootScanBudgetMs: testCase.args.sourceRootScanBudgetMs ?? undefined,
|
|
128
|
+
sourceRootMaxDirectoryVisits:
|
|
129
|
+
testCase.args.sourceRootMaxDirectoryVisits ?? undefined,
|
|
130
|
+
sourceRootMaxProjectEnrichments:
|
|
131
|
+
testCase.args.sourceRootMaxProjectEnrichments ?? undefined,
|
|
132
|
+
placeResponsiveness: scriptedResponsiveness(testCase, probeCalls),
|
|
133
|
+
});
|
|
134
|
+
return { summary, probeCalls };
|
|
135
|
+
} finally {
|
|
136
|
+
restoreCaseEnv();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function handleConversationDiscovery(testCase) {
|
|
141
|
+
return {
|
|
142
|
+
summary: discoverLocalConversations({
|
|
143
|
+
homeDir: testCase.homeDir ?? undefined,
|
|
144
|
+
env: testCase.env ?? undefined,
|
|
145
|
+
nowMs: testCase.nowMs ?? undefined,
|
|
146
|
+
candidateLimit: testCase.candidateLimit ?? undefined,
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function handleImportMessages(testCase) {
|
|
152
|
+
return outcome(() =>
|
|
153
|
+
readLocalConversationImportMessages({
|
|
154
|
+
source: testCase.source,
|
|
155
|
+
importKey: 'oracle-import-key',
|
|
156
|
+
path: testCase.path,
|
|
157
|
+
displayName: 'oracle-display-name',
|
|
158
|
+
activityAt: undefined,
|
|
159
|
+
metadata: {},
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The REAL seam values the ReScript port's injected deps must carry: the
|
|
165
|
+
// credential-store args, the composed MCP config args, and the
|
|
166
|
+
// dangerous-cwd verdict - probed from the same modules the TS builder
|
|
167
|
+
// calls internally.
|
|
168
|
+
function handleAgentSeams(testCase) {
|
|
169
|
+
const assessment = assessDangerousCwd(testCase.cwd);
|
|
170
|
+
return {
|
|
171
|
+
credentialStoreFileArgs: codexCredentialStoreFileArgs(),
|
|
172
|
+
mcpConfigArgs:
|
|
173
|
+
testCase.mcp === undefined
|
|
174
|
+
? null
|
|
175
|
+
: codexMcpConfigArgs(
|
|
176
|
+
linzumiMcpServerConfig({
|
|
177
|
+
command: testCase.mcp.command,
|
|
178
|
+
argsPrefix: testCase.mcp.argsPrefix,
|
|
179
|
+
kandanUrl: testCase.mcp.serviceUrl,
|
|
180
|
+
authFilePath: testCase.mcp.authFilePath,
|
|
181
|
+
ownerUsername: testCase.mcp.ownerUsername ?? undefined,
|
|
182
|
+
operatingMode: testCase.mcp.operatingMode,
|
|
183
|
+
toolScope: testCase.mcp.toolScope,
|
|
184
|
+
cwd: testCase.mcp.cwd,
|
|
185
|
+
})
|
|
186
|
+
),
|
|
187
|
+
dangerousReason: assessment.dangerous ? assessment.reason : null,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function handleAgentProcess(testCase) {
|
|
192
|
+
return outcome(() =>
|
|
193
|
+
onboardingDiscoveryAgentProcess({
|
|
194
|
+
kind: testCase.kind,
|
|
195
|
+
workerId: testCase.workerId,
|
|
196
|
+
workerLabel: testCase.workerLabel,
|
|
197
|
+
codexBin: testCase.codexBin,
|
|
198
|
+
cwd: testCase.cwd,
|
|
199
|
+
kandanUrl: testCase.serviceUrl,
|
|
200
|
+
runnerId: testCase.commanderId,
|
|
201
|
+
workspaceSlug: testCase.workspaceSlug,
|
|
202
|
+
ownerUsername: testCase.ownerUsername ?? undefined,
|
|
203
|
+
model: testCase.model ?? undefined,
|
|
204
|
+
authFilePath: testCase.authFilePath,
|
|
205
|
+
prompt: testCase.prompt,
|
|
206
|
+
linzumiCommand: testCase.mcpCommand,
|
|
207
|
+
codexHome: testCase.codexHome,
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function handleProcessesForTest(testCase) {
|
|
213
|
+
setCaseEnv(testCase.env);
|
|
214
|
+
try {
|
|
215
|
+
return outcome(() =>
|
|
216
|
+
onboardingDiscoveryAgentProcessesForTest({
|
|
217
|
+
codexBin: testCase.codexBin,
|
|
218
|
+
cwd: testCase.cwd,
|
|
219
|
+
kandanUrl: testCase.serviceUrl,
|
|
220
|
+
runnerId: testCase.commanderId,
|
|
221
|
+
workspaceSlug: testCase.workspaceSlug,
|
|
222
|
+
ownerUsername: testCase.ownerUsername ?? undefined,
|
|
223
|
+
model: testCase.model ?? undefined,
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
} finally {
|
|
227
|
+
restoreCaseEnv();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function handleChildParent(testCase) {
|
|
232
|
+
const logs = [];
|
|
233
|
+
const log = (event, payload) => logs.push({ event, payload });
|
|
234
|
+
setCaseEnv(testCase.env);
|
|
235
|
+
try {
|
|
236
|
+
const deps = {
|
|
237
|
+
log,
|
|
238
|
+
timeoutMs: testCase.timeoutMs ?? undefined,
|
|
239
|
+
entryPath: testCase.entryPath ?? undefined,
|
|
240
|
+
execPath: testCase.execPath ?? undefined,
|
|
241
|
+
};
|
|
242
|
+
const summary =
|
|
243
|
+
testCase.kind === 'projects'
|
|
244
|
+
? await discoverCurrentGitProjectInChildProcess(testCase.args ?? { cwd: process.cwd() }, deps)
|
|
245
|
+
: await discoverLocalConversationsInChildProcess(deps);
|
|
246
|
+
return { summary, logs };
|
|
247
|
+
} finally {
|
|
248
|
+
restoreCaseEnv();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function handleProbeInjected(testCase) {
|
|
253
|
+
const calls = [];
|
|
254
|
+
const result = isPlaceResponsive(testCase.path, {
|
|
255
|
+
timeoutMs: testCase.timeoutMs ?? undefined,
|
|
256
|
+
probe: (path, timeoutMs) => {
|
|
257
|
+
calls.push({ path, timeoutMs });
|
|
258
|
+
return testCase.verdict;
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
return { result, calls };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function handle(testCase) {
|
|
265
|
+
switch (testCase.op) {
|
|
266
|
+
case 'projectDiscovery':
|
|
267
|
+
return handleProjectDiscovery(testCase);
|
|
268
|
+
case 'defaultSourceRoots':
|
|
269
|
+
return outcome(() =>
|
|
270
|
+
defaultLocalProjectSourceRootsForTest({
|
|
271
|
+
cwd: testCase.cwd,
|
|
272
|
+
homeDir: testCase.homeDir ?? undefined,
|
|
273
|
+
selectedScanRoots: testCase.selectedScanRoots ?? undefined,
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
case 'conversationDiscovery':
|
|
277
|
+
return handleConversationDiscovery(testCase);
|
|
278
|
+
case 'importMessages':
|
|
279
|
+
return handleImportMessages(testCase);
|
|
280
|
+
case 'cleanTitle':
|
|
281
|
+
return outcome(() =>
|
|
282
|
+
cleanConversationTitleForTest(testCase.value ?? undefined)
|
|
283
|
+
);
|
|
284
|
+
case 'agentPlans':
|
|
285
|
+
return outcome(() =>
|
|
286
|
+
onboardingDiscoveryAgentPlans({
|
|
287
|
+
cwd: testCase.cwd,
|
|
288
|
+
runnerId: testCase.commanderId,
|
|
289
|
+
workspaceSlug: testCase.workspaceSlug,
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
case 'projectPrompt':
|
|
293
|
+
return outcome(() =>
|
|
294
|
+
projectDiscoveryPrompt({
|
|
295
|
+
runnerId: testCase.commanderId,
|
|
296
|
+
workspaceSlug: testCase.workspaceSlug,
|
|
297
|
+
cwd: testCase.cwd ?? undefined,
|
|
298
|
+
scope: testCase.scope ?? undefined,
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
case 'conversationPrompt':
|
|
302
|
+
return outcome(() =>
|
|
303
|
+
conversationDiscoveryPrompt({
|
|
304
|
+
runnerId: testCase.commanderId,
|
|
305
|
+
workspaceSlug: testCase.workspaceSlug,
|
|
306
|
+
source: testCase.source ?? undefined,
|
|
307
|
+
})
|
|
308
|
+
);
|
|
309
|
+
case 'agentSeams':
|
|
310
|
+
return handleAgentSeams(testCase);
|
|
311
|
+
case 'agentProcess':
|
|
312
|
+
return handleAgentProcess(testCase);
|
|
313
|
+
case 'processesForTest':
|
|
314
|
+
return handleProcessesForTest(testCase);
|
|
315
|
+
case 'childParent':
|
|
316
|
+
return handleChildParent(testCase);
|
|
317
|
+
case 'probeSource':
|
|
318
|
+
return {
|
|
319
|
+
source: probeWorkerSourceForTest(),
|
|
320
|
+
roleEnvKey: onboardingDiscoveryRoleEnvKey,
|
|
321
|
+
};
|
|
322
|
+
case 'probeInjected':
|
|
323
|
+
return handleProbeInjected(testCase);
|
|
324
|
+
case 'probeReal':
|
|
325
|
+
return { result: isPlaceResponsive(testCase.path) };
|
|
326
|
+
case 'isolatedCodexHome':
|
|
327
|
+
setCaseEnv(testCase.env);
|
|
328
|
+
try {
|
|
329
|
+
return outcome(() => ensureIsolatedCodexHome());
|
|
330
|
+
} finally {
|
|
331
|
+
restoreCaseEnv();
|
|
332
|
+
}
|
|
333
|
+
default:
|
|
334
|
+
return { threw: true, message: 'unknown op ' + String(testCase.op) };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const roleValue = process.env[onboardingDiscoveryRoleEnvKey];
|
|
339
|
+
if (roleValue !== undefined && roleValue !== '') {
|
|
340
|
+
// The CLI entry's dispatch, verbatim: this bundle IS a real discovery
|
|
341
|
+
// child for whichever parent implementation spawned it.
|
|
342
|
+
process.exitCode = await runOnboardingDiscoveryChildRole(
|
|
343
|
+
roleValue,
|
|
344
|
+
process.stdin,
|
|
345
|
+
process.stdout
|
|
346
|
+
);
|
|
347
|
+
} else {
|
|
348
|
+
let stdin = '';
|
|
349
|
+
process.stdin.setEncoding('utf8');
|
|
350
|
+
for await (const chunk of process.stdin) {
|
|
351
|
+
stdin += chunk;
|
|
352
|
+
}
|
|
353
|
+
const cases = JSON.parse(stdin);
|
|
354
|
+
const results = [];
|
|
355
|
+
for (const testCase of cases) {
|
|
356
|
+
results.push(await handle(testCase));
|
|
357
|
+
}
|
|
358
|
+
process.stdout.write(JSON.stringify(results));
|
|
359
|
+
}
|
|
360
|
+
`;
|
|
361
|
+
|
|
362
|
+
await mkdir(join(packageRoot, '.differential'), { recursive: true });
|
|
363
|
+
|
|
364
|
+
await build({
|
|
365
|
+
stdin: {
|
|
366
|
+
contents: driverSource,
|
|
367
|
+
resolveDir: packageRoot,
|
|
368
|
+
sourcefile: 'onboarding-discovery-parity-driver.ts',
|
|
369
|
+
loader: 'ts',
|
|
370
|
+
},
|
|
371
|
+
bundle: true,
|
|
372
|
+
platform: 'node',
|
|
373
|
+
target: 'node20',
|
|
374
|
+
format: 'esm',
|
|
375
|
+
outfile: join(packageRoot, '.differential/onboarding-discovery-parity-oracle.mjs'),
|
|
376
|
+
minify: false,
|
|
377
|
+
sourcemap: false,
|
|
378
|
+
legalComments: 'none',
|
|
379
|
+
external: ['ws', 'undici', 'blessed', '@anthropic-ai/claude-agent-sdk'],
|
|
380
|
+
});
|