@zq-silk/yui 0.2.0 → 0.4.2
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/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { parse } from "smol-toml";
|
|
7
|
+
import { configuredAgentLaunchEnvironment } from "../agent/launchEnvironment.js";
|
|
8
|
+
import { YUI_VERSION } from "../version.js";
|
|
9
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
10
|
+
const PROCESS_TERMINATION_GRACE_MS = 100;
|
|
11
|
+
const CODEX_SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
|
|
12
|
+
const CODEX_APPROVALS = ["untrusted", "on-request", "never"];
|
|
13
|
+
export async function discoverCodexConfiguration(input) {
|
|
14
|
+
const environment = configuredAgentLaunchEnvironment(input.agent, input.environment);
|
|
15
|
+
const profile = input.config?.adapterId === "codex" ? input.config.profile : undefined;
|
|
16
|
+
const globalArgs = [
|
|
17
|
+
...input.agent.baseArgs,
|
|
18
|
+
...(profile === undefined ? [] : ["--profile", profile])
|
|
19
|
+
];
|
|
20
|
+
const client = JsonRpcProcess.start(input.agent.command, [...globalArgs, "app-server", "--stdio"], input.cwd, environment, input.signal);
|
|
21
|
+
try {
|
|
22
|
+
const [version, help] = await Promise.all([
|
|
23
|
+
runTextProcess(input.agent.command, [...input.agent.baseArgs, "--version"], input.cwd, environment, input.signal),
|
|
24
|
+
runTextProcess(input.agent.command, [...input.agent.baseArgs, "--help"], input.cwd, environment, input.signal),
|
|
25
|
+
client.request("initialize", {
|
|
26
|
+
clientInfo: { name: "yui", title: "Yui", version: YUI_VERSION },
|
|
27
|
+
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
28
|
+
}).then(() => { client.notify("initialized", {}); })
|
|
29
|
+
]);
|
|
30
|
+
const [models, requirementsResult, providerCapabilities] = await Promise.all([
|
|
31
|
+
listCodexModels(client),
|
|
32
|
+
client.request("configRequirements/read", {}),
|
|
33
|
+
client.request("modelProvider/capabilities/read", {})
|
|
34
|
+
]);
|
|
35
|
+
const requirements = object(requirementsResult)?.requirements;
|
|
36
|
+
const requirementRecord = object(requirements);
|
|
37
|
+
const allowedSandboxes = optionalStrings(requirementRecord?.allowedSandboxModes);
|
|
38
|
+
const allowedApprovals = optionalStrings(requirementRecord?.allowedApprovalPolicies);
|
|
39
|
+
const allowedWebSearchModes = optionalStrings(requirementRecord?.allowedWebSearchModes);
|
|
40
|
+
const capabilityRecord = object(providerCapabilities);
|
|
41
|
+
const webSearch = capabilityRecord?.webSearch === true
|
|
42
|
+
&& (allowedWebSearchModes === undefined || allowedWebSearchModes.includes("live"));
|
|
43
|
+
const bypass = configurationFlagAvailable(help, "--dangerously-bypass-approvals-and-sandbox");
|
|
44
|
+
const sandboxChoices = intersectChoices(configurationHelpChoices(help, "--sandbox", CODEX_SANDBOXES), allowedSandboxes);
|
|
45
|
+
const approvalChoices = intersectChoices(configurationHelpChoices(help, "--ask-for-approval", CODEX_APPROVALS), allowedApprovals);
|
|
46
|
+
return {
|
|
47
|
+
schemaVersion: 1,
|
|
48
|
+
agentId: input.agent.id,
|
|
49
|
+
adapterId: "codex",
|
|
50
|
+
...(semanticVersion(version) === undefined ? {} : { cliVersion: semanticVersion(version) }),
|
|
51
|
+
models,
|
|
52
|
+
fields: [
|
|
53
|
+
field("model", [], true),
|
|
54
|
+
field("effort", [], true),
|
|
55
|
+
field("permission.strategy", [
|
|
56
|
+
choice("default"),
|
|
57
|
+
...(bypass ? [choice("bypass")] : []),
|
|
58
|
+
choice("configured")
|
|
59
|
+
], false, true, bypass ? undefined : "Codex bypass strategy is unavailable."),
|
|
60
|
+
field("permission.sandbox", sandboxChoices.map(choice), false),
|
|
61
|
+
field("permission.approval", approvalChoices.map(choice), false),
|
|
62
|
+
field("search", webSearch ? [choice("true")] : [], false, webSearch, webSearch ? undefined : "Live web search is unavailable or disallowed."),
|
|
63
|
+
field("profile", codexProfileChoices(environment).map(choice), true),
|
|
64
|
+
field("additionalDirectories", [], true)
|
|
65
|
+
],
|
|
66
|
+
warnings: client.warnings()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
client.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function discoverClaudeConfiguration(input) {
|
|
74
|
+
const environment = {
|
|
75
|
+
...configuredAgentLaunchEnvironment(input.agent, input.environment),
|
|
76
|
+
CLAUDE_CODE_ENTRYPOINT: "sdk-ts"
|
|
77
|
+
};
|
|
78
|
+
const config = input.config?.adapterId === "claude" ? input.config : undefined;
|
|
79
|
+
const initializationArgs = [
|
|
80
|
+
...input.agent.baseArgs,
|
|
81
|
+
"--output-format", "stream-json",
|
|
82
|
+
"--verbose",
|
|
83
|
+
"--input-format", "stream-json",
|
|
84
|
+
"--no-session-persistence",
|
|
85
|
+
...(config?.settingsSources === undefined
|
|
86
|
+
? [] : [`--setting-sources=${config.settingsSources.join(",")}`]),
|
|
87
|
+
...(config?.settingsFile === undefined ? [] : ["--settings", config.settingsFile])
|
|
88
|
+
];
|
|
89
|
+
const [version, help, initialization] = await Promise.all([
|
|
90
|
+
runTextProcess(input.agent.command, [...input.agent.baseArgs, "--version"], input.cwd, environment, input.signal),
|
|
91
|
+
runTextProcess(input.agent.command, [...input.agent.baseArgs, "--help"], input.cwd, environment, input.signal),
|
|
92
|
+
requestClaudeInitialization(input.agent.command, initializationArgs, input.cwd, environment, input.signal)
|
|
93
|
+
]);
|
|
94
|
+
const initialized = object(initialization);
|
|
95
|
+
const models = array(initialized?.models, "Claude model catalog").map(claudeModel);
|
|
96
|
+
const permissionModes = configurationHelpChoices(help, "--permission-mode", []);
|
|
97
|
+
const bypass = configurationFlagAvailable(help, "--dangerously-skip-permissions");
|
|
98
|
+
const settingsSources = configurationHelpChoices(help, "--setting-sources", ["user", "project", "local"]);
|
|
99
|
+
return {
|
|
100
|
+
schemaVersion: 1,
|
|
101
|
+
agentId: input.agent.id,
|
|
102
|
+
adapterId: "claude",
|
|
103
|
+
...(semanticVersion(version) === undefined ? {} : { cliVersion: semanticVersion(version) }),
|
|
104
|
+
models,
|
|
105
|
+
fields: [
|
|
106
|
+
field("model", [], true),
|
|
107
|
+
field("effort", [], true),
|
|
108
|
+
field("permission.strategy", [
|
|
109
|
+
choice("default"),
|
|
110
|
+
...(bypass ? [choice("bypass")] : []),
|
|
111
|
+
choice("configured")
|
|
112
|
+
], false, true, bypass ? undefined : "Claude bypass strategy is unavailable."),
|
|
113
|
+
field("permission.mode", permissionModes.map(choice), true),
|
|
114
|
+
field("permission.allowedTools", [], true),
|
|
115
|
+
field("permission.disallowedTools", [], true),
|
|
116
|
+
field("settingsSources", settingsSources.map(choice), false),
|
|
117
|
+
field("settingsFile", [], true),
|
|
118
|
+
field("additionalDirectories", [], true)
|
|
119
|
+
],
|
|
120
|
+
warnings: []
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function listCodexModels(client) {
|
|
124
|
+
const models = [];
|
|
125
|
+
let cursor;
|
|
126
|
+
for (let page = 0; page < 20; page += 1) {
|
|
127
|
+
const result = object(await client.request("model/list", {
|
|
128
|
+
limit: 100,
|
|
129
|
+
includeHidden: false,
|
|
130
|
+
...(cursor === undefined ? {} : { cursor })
|
|
131
|
+
}));
|
|
132
|
+
for (const raw of array(result?.data, "Codex model catalog")) {
|
|
133
|
+
models.push(codexModel(raw));
|
|
134
|
+
}
|
|
135
|
+
const next = result?.nextCursor;
|
|
136
|
+
if (next === null || next === undefined)
|
|
137
|
+
return models;
|
|
138
|
+
if (typeof next !== "string" || next.length === 0) {
|
|
139
|
+
throw new Error("Codex model catalog returned an invalid cursor.");
|
|
140
|
+
}
|
|
141
|
+
cursor = next;
|
|
142
|
+
}
|
|
143
|
+
throw new Error("Codex model catalog exceeded the pagination limit.");
|
|
144
|
+
}
|
|
145
|
+
function codexModel(value) {
|
|
146
|
+
const model = requiredObject(value, "Codex model");
|
|
147
|
+
const rawEfforts = array(model.supportedReasoningEfforts, "Codex model efforts");
|
|
148
|
+
const efforts = rawEfforts.map((raw) => {
|
|
149
|
+
const effort = requiredObject(raw, "Codex effort");
|
|
150
|
+
const value = requiredString(effort.reasoningEffort, "Codex effort value");
|
|
151
|
+
return {
|
|
152
|
+
value,
|
|
153
|
+
label: value,
|
|
154
|
+
...(typeof effort.description === "string" && effort.description.trim().length > 0
|
|
155
|
+
? { description: effort.description.trim() } : {})
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
const serviceTiers = Array.isArray(model.serviceTiers)
|
|
159
|
+
? model.serviceTiers.map((raw) => {
|
|
160
|
+
const tier = requiredObject(raw, "Codex service tier");
|
|
161
|
+
return {
|
|
162
|
+
value: requiredString(tier.id, "Codex service tier id"),
|
|
163
|
+
label: requiredString(tier.name, "Codex service tier name"),
|
|
164
|
+
...(typeof tier.description === "string" && tier.description.trim().length > 0
|
|
165
|
+
? { description: tier.description.trim() } : {})
|
|
166
|
+
};
|
|
167
|
+
})
|
|
168
|
+
: undefined;
|
|
169
|
+
return {
|
|
170
|
+
value: requiredString(model.model ?? model.id, "Codex model value"),
|
|
171
|
+
label: requiredString(model.displayName ?? model.model ?? model.id, "Codex model label"),
|
|
172
|
+
...(typeof model.description === "string" && model.description.trim().length > 0
|
|
173
|
+
? { description: model.description.trim() } : {}),
|
|
174
|
+
isDefault: model.isDefault === true,
|
|
175
|
+
...(typeof model.defaultReasoningEffort === "string"
|
|
176
|
+
? { defaultEffort: model.defaultReasoningEffort } : {}),
|
|
177
|
+
efforts,
|
|
178
|
+
...(serviceTiers === undefined ? {} : { serviceTiers }),
|
|
179
|
+
...(typeof model.defaultServiceTier === "string"
|
|
180
|
+
? { defaultServiceTier: model.defaultServiceTier } : {})
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function claudeModel(value) {
|
|
184
|
+
const model = requiredObject(value, "Claude model");
|
|
185
|
+
const modelValue = requiredString(model.value, "Claude model value");
|
|
186
|
+
const effortValues = Array.isArray(model.supportedEffortLevels)
|
|
187
|
+
? model.supportedEffortLevels.map((effort) => requiredString(effort, "Claude effort"))
|
|
188
|
+
: [];
|
|
189
|
+
return {
|
|
190
|
+
value: modelValue,
|
|
191
|
+
label: requiredString(model.displayName ?? modelValue, "Claude model label"),
|
|
192
|
+
...(typeof model.description === "string" && model.description.trim().length > 0
|
|
193
|
+
? { description: model.description.trim() } : {}),
|
|
194
|
+
...(typeof model.resolvedModel === "string" && model.resolvedModel.trim().length > 0
|
|
195
|
+
? { resolvedModel: model.resolvedModel.trim() } : {}),
|
|
196
|
+
isDefault: modelValue === "default",
|
|
197
|
+
efforts: effortValues.map(choice)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async function requestClaudeInitialization(command, args, cwd, environment, signal) {
|
|
201
|
+
const requestId = `yui-catalog-${process.pid}`;
|
|
202
|
+
const child = spawn(command, args, {
|
|
203
|
+
cwd,
|
|
204
|
+
env: environment,
|
|
205
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
206
|
+
});
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
let settled = false;
|
|
209
|
+
let bytes = 0;
|
|
210
|
+
const output = createInterface({ input: child.stdout });
|
|
211
|
+
const finish = (error, value) => {
|
|
212
|
+
if (settled)
|
|
213
|
+
return;
|
|
214
|
+
settled = true;
|
|
215
|
+
output.close();
|
|
216
|
+
terminateProcess(child);
|
|
217
|
+
signal.removeEventListener("abort", abort);
|
|
218
|
+
if (error === undefined)
|
|
219
|
+
resolve(value);
|
|
220
|
+
else
|
|
221
|
+
reject(error);
|
|
222
|
+
};
|
|
223
|
+
const abort = () => finish(abortError());
|
|
224
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
225
|
+
child.on("error", (error) => finish(error));
|
|
226
|
+
child.on("exit", (code, exitSignal) => {
|
|
227
|
+
if (!settled)
|
|
228
|
+
finish(new Error(`Claude configuration probe exited before initialization (${code ?? exitSignal ?? "unknown"}).`));
|
|
229
|
+
});
|
|
230
|
+
output.on("line", (line) => {
|
|
231
|
+
bytes += Buffer.byteLength(line);
|
|
232
|
+
if (bytes > MAX_OUTPUT_BYTES) {
|
|
233
|
+
finish(new Error("Claude configuration probe exceeded the output limit."));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
let message;
|
|
237
|
+
try {
|
|
238
|
+
message = JSON.parse(line);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const envelope = object(message);
|
|
244
|
+
const response = object(envelope?.response);
|
|
245
|
+
if (envelope?.type !== "control_response" || response?.request_id !== requestId)
|
|
246
|
+
return;
|
|
247
|
+
if (response.subtype !== "success") {
|
|
248
|
+
finish(new Error(typeof response.error === "string"
|
|
249
|
+
? response.error : "Claude configuration initialization failed."));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
finish(undefined, response.response);
|
|
253
|
+
});
|
|
254
|
+
child.stdin.on("error", (error) => finish(error));
|
|
255
|
+
child.stdin.end(`${JSON.stringify({
|
|
256
|
+
request_id: requestId,
|
|
257
|
+
type: "control_request",
|
|
258
|
+
request: { subtype: "initialize" }
|
|
259
|
+
})}\n`);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
async function runTextProcess(command, args, cwd, environment, signal) {
|
|
263
|
+
const child = spawn(command, args, {
|
|
264
|
+
cwd,
|
|
265
|
+
env: environment,
|
|
266
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
267
|
+
});
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
let stdout = "";
|
|
270
|
+
let stderr = "";
|
|
271
|
+
let settled = false;
|
|
272
|
+
const finish = (error) => {
|
|
273
|
+
if (settled)
|
|
274
|
+
return;
|
|
275
|
+
settled = true;
|
|
276
|
+
signal.removeEventListener("abort", abort);
|
|
277
|
+
if (error !== undefined)
|
|
278
|
+
reject(error);
|
|
279
|
+
else
|
|
280
|
+
resolve(`${stdout}\n${stderr}`);
|
|
281
|
+
};
|
|
282
|
+
const abort = () => {
|
|
283
|
+
terminateProcess(child);
|
|
284
|
+
finish(abortError());
|
|
285
|
+
};
|
|
286
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
287
|
+
child.on("error", finish);
|
|
288
|
+
child.stdout.setEncoding("utf8");
|
|
289
|
+
child.stderr.setEncoding("utf8");
|
|
290
|
+
child.stdout.on("data", (chunk) => {
|
|
291
|
+
stdout += chunk;
|
|
292
|
+
if (stdout.length + stderr.length > MAX_OUTPUT_BYTES) {
|
|
293
|
+
terminateProcess(child);
|
|
294
|
+
finish(new Error("Agent configuration probe exceeded the output limit."));
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
child.stderr.on("data", (chunk) => {
|
|
298
|
+
stderr += chunk;
|
|
299
|
+
if (stdout.length + stderr.length > MAX_OUTPUT_BYTES) {
|
|
300
|
+
terminateProcess(child);
|
|
301
|
+
finish(new Error("Agent configuration probe exceeded the output limit."));
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
child.on("exit", (code, exitSignal) => {
|
|
305
|
+
if (code === 0)
|
|
306
|
+
finish();
|
|
307
|
+
else
|
|
308
|
+
finish(new Error(`Agent configuration probe exited (${code ?? exitSignal ?? "unknown"}).`));
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
class JsonRpcProcess {
|
|
313
|
+
child;
|
|
314
|
+
#pending = new Map();
|
|
315
|
+
#stderr = [];
|
|
316
|
+
#nextId = 1;
|
|
317
|
+
#closed = false;
|
|
318
|
+
constructor(child, signal) {
|
|
319
|
+
this.child = child;
|
|
320
|
+
const output = createInterface({ input: child.stdout });
|
|
321
|
+
output.on("line", (line) => { this.#receive(line); });
|
|
322
|
+
child.stderr.setEncoding("utf8");
|
|
323
|
+
child.stderr.on("data", (chunk) => {
|
|
324
|
+
this.#stderr.push(chunk);
|
|
325
|
+
while (this.#stderr.join("").length > 16_384)
|
|
326
|
+
this.#stderr.shift();
|
|
327
|
+
});
|
|
328
|
+
child.stdin.on("error", (error) => { this.#failAll(error); });
|
|
329
|
+
const abort = () => {
|
|
330
|
+
this.#failAll(abortError());
|
|
331
|
+
this.close();
|
|
332
|
+
};
|
|
333
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
334
|
+
child.on("error", (error) => { this.#failAll(error); });
|
|
335
|
+
child.on("exit", (code, exitSignal) => {
|
|
336
|
+
signal.removeEventListener("abort", abort);
|
|
337
|
+
if (this.#closed)
|
|
338
|
+
return;
|
|
339
|
+
this.#failAll(new Error(`Codex App Server exited (${code ?? exitSignal ?? "unknown"}).`));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
static start(command, args, cwd, environment, signal) {
|
|
343
|
+
return new JsonRpcProcess(spawn(command, args, {
|
|
344
|
+
cwd,
|
|
345
|
+
env: environment,
|
|
346
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
347
|
+
}), signal);
|
|
348
|
+
}
|
|
349
|
+
request(method, params) {
|
|
350
|
+
if (this.#closed)
|
|
351
|
+
return Promise.reject(new Error("Codex App Server is closed."));
|
|
352
|
+
const id = this.#nextId++;
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
this.#pending.set(id, { resolve, reject });
|
|
355
|
+
this.#write({ id, method, params });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
notify(method, params) {
|
|
359
|
+
this.#write({ method, params });
|
|
360
|
+
}
|
|
361
|
+
warnings() {
|
|
362
|
+
return this.#stderr.join("").split(/\r?\n/)
|
|
363
|
+
.some((line) => /\b(?:warn|error|failed)\b/i.test(line))
|
|
364
|
+
? ["Codex App Server reported warnings during catalog discovery."]
|
|
365
|
+
: [];
|
|
366
|
+
}
|
|
367
|
+
close() {
|
|
368
|
+
if (this.#closed)
|
|
369
|
+
return;
|
|
370
|
+
this.#closed = true;
|
|
371
|
+
terminateProcess(this.child);
|
|
372
|
+
this.#failAll(new Error("Codex App Server was closed."));
|
|
373
|
+
}
|
|
374
|
+
#receive(line) {
|
|
375
|
+
let message;
|
|
376
|
+
try {
|
|
377
|
+
message = JSON.parse(line);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const response = object(message);
|
|
383
|
+
if (typeof response?.id !== "number")
|
|
384
|
+
return;
|
|
385
|
+
const pending = this.#pending.get(response.id);
|
|
386
|
+
if (pending === undefined)
|
|
387
|
+
return;
|
|
388
|
+
this.#pending.delete(response.id);
|
|
389
|
+
const error = object(response.error);
|
|
390
|
+
if (error !== undefined) {
|
|
391
|
+
pending.reject(new Error(typeof error.message === "string"
|
|
392
|
+
? error.message : "Codex App Server request failed."));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
pending.resolve(response.result);
|
|
396
|
+
}
|
|
397
|
+
#write(value) {
|
|
398
|
+
this.child.stdin.write(`${JSON.stringify(value)}\n`);
|
|
399
|
+
}
|
|
400
|
+
#failAll(error) {
|
|
401
|
+
for (const pending of this.#pending.values())
|
|
402
|
+
pending.reject(error);
|
|
403
|
+
this.#pending.clear();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function terminateProcess(child) {
|
|
407
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
408
|
+
return;
|
|
409
|
+
const force = setTimeout(() => {
|
|
410
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
411
|
+
child.kill("SIGKILL");
|
|
412
|
+
}
|
|
413
|
+
}, PROCESS_TERMINATION_GRACE_MS);
|
|
414
|
+
force.unref();
|
|
415
|
+
child.once("exit", () => { clearTimeout(force); });
|
|
416
|
+
child.kill("SIGTERM");
|
|
417
|
+
}
|
|
418
|
+
export function codexProfileChoices(environment) {
|
|
419
|
+
const root = environment.CODEX_HOME
|
|
420
|
+
?? join(environment.HOME ?? homedir(), ".codex");
|
|
421
|
+
try {
|
|
422
|
+
const profiles = object(parse(readFileSync(join(root, "config.toml"), "utf8")).profiles);
|
|
423
|
+
return Object.keys(profiles ?? {}).filter((name) => name.trim().length > 0 && !name.includes("\0")).sort();
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
export function configurationHelpChoices(help, flag, fallback) {
|
|
430
|
+
const lines = help.replace(/\r\n/g, "\n").split("\n");
|
|
431
|
+
const start = lines.findIndex((line) => line.includes(flag));
|
|
432
|
+
if (start < 0)
|
|
433
|
+
return [...fallback];
|
|
434
|
+
let end = start + 1;
|
|
435
|
+
while (end < lines.length && !/^\s{2,}(?:-[A-Za-z](?:,\s*)?|--)[\w-]/.test(lines[end] ?? "")) {
|
|
436
|
+
end += 1;
|
|
437
|
+
}
|
|
438
|
+
const section = lines.slice(start, end).join("\n");
|
|
439
|
+
const inline = /(?:\[possible values:\s*([^\]]+)\]|\(choices:\s*([^)]*)\))/i
|
|
440
|
+
.exec(section);
|
|
441
|
+
const declared = inline?.[1] ?? inline?.[2];
|
|
442
|
+
const bulletValues = declared === undefined && /Possible values:/i.test(section)
|
|
443
|
+
? [...section.matchAll(/^\s*-\s*([\w.+-]+)\s*:/gm)].map((match) => match[1] ?? "")
|
|
444
|
+
: [];
|
|
445
|
+
const parsed = [...new Set((declared ?? bulletValues.join(","))
|
|
446
|
+
.replace(/["']/g, "")
|
|
447
|
+
.split(",")
|
|
448
|
+
.map((value) => value.trim().replace(/\.$/, ""))
|
|
449
|
+
.filter((value) => /^[\w.+-]+$/.test(value)))];
|
|
450
|
+
return parsed.length === 0 ? [...fallback] : parsed;
|
|
451
|
+
}
|
|
452
|
+
function configurationFlagAvailable(help, flag) {
|
|
453
|
+
return help.replace(/\r\n/g, "\n").split("\n").some((line) => line.includes(flag));
|
|
454
|
+
}
|
|
455
|
+
function intersectChoices(choices, allowed) {
|
|
456
|
+
return allowed === undefined
|
|
457
|
+
? [...choices]
|
|
458
|
+
: choices.filter((candidate) => allowed.includes(candidate));
|
|
459
|
+
}
|
|
460
|
+
function optionalStrings(value) {
|
|
461
|
+
if (value === null || value === undefined)
|
|
462
|
+
return undefined;
|
|
463
|
+
return array(value, "configuration requirements").map((entry) => requiredString(entry, "configuration requirement"));
|
|
464
|
+
}
|
|
465
|
+
function field(key, choices, allowCustom, available, reason) {
|
|
466
|
+
return {
|
|
467
|
+
key,
|
|
468
|
+
choices,
|
|
469
|
+
allowCustom,
|
|
470
|
+
...(available === undefined ? {} : { available }),
|
|
471
|
+
...(reason === undefined ? {} : { reason })
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function choice(value) {
|
|
475
|
+
return { value, label: value };
|
|
476
|
+
}
|
|
477
|
+
function semanticVersion(value) {
|
|
478
|
+
return /(?:^|\D)(\d+\.\d+\.\d+)(?:\D|$)/m.exec(value)?.[1];
|
|
479
|
+
}
|
|
480
|
+
function abortError() {
|
|
481
|
+
return Object.assign(new Error("Agent configuration discovery was aborted."), {
|
|
482
|
+
name: "AbortError"
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function requiredObject(value, label) {
|
|
486
|
+
const result = object(value);
|
|
487
|
+
if (result === undefined)
|
|
488
|
+
throw new Error(`${label} is invalid.`);
|
|
489
|
+
return result;
|
|
490
|
+
}
|
|
491
|
+
function object(value) {
|
|
492
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
493
|
+
? value
|
|
494
|
+
: undefined;
|
|
495
|
+
}
|
|
496
|
+
function array(value, label) {
|
|
497
|
+
if (!Array.isArray(value))
|
|
498
|
+
throw new Error(`${label} is invalid.`);
|
|
499
|
+
return value;
|
|
500
|
+
}
|
|
501
|
+
function requiredString(value, label) {
|
|
502
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
503
|
+
throw new Error(`${label} is invalid.`);
|
|
504
|
+
}
|
|
505
|
+
return value.trim();
|
|
506
|
+
}
|