@zq-silk/yui 0.16.1 → 0.99.0
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.
Potentially problematic release.
This version of @zq-silk/yui might be problematic. Click here for more details.
- package/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +3 -0
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +7 -17
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -33
- package/dist/cli/updatePorts.js +40 -17
- package/dist/cli.js +35 -26
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +84 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +1 -1
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/controller.js +8 -6
- package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
- package/dist/controller/globalInputDelivery.js +28 -7
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +10 -3
- package/dist/controller/runtime.js +1 -27
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +27 -0
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/doctor/doctor.js +2 -1
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +5 -2
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/message/messageContinuation.js +8 -9
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +3 -5
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +22 -62
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +1 -1
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/providerContinuationReconciliationService.js +42 -3
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +2 -1
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/docs/managed-turn-and-session-runtime.md +31 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +113 -20
- package/docs/release-workflow.zh-CN.md +95 -14
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/task-delivery.md +109 -0
- package/docs/task-delivery.zh-CN.md +80 -0
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +29 -11
- package/docs/testing/verification-levels.zh-CN.md +25 -7
- package/i18n/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
|
@@ -9,6 +9,7 @@ import { codexClientInitialization } from "../runtime/codexAppServerRuntime.js";
|
|
|
9
9
|
import { acpInitializeRequest, readAcpInitializeResult } from "../runtime/acpProtocol.js";
|
|
10
10
|
import { handshakeObservationFrom } from "../runtime/agentRunConfiguration.js";
|
|
11
11
|
import { YUI_VERSION } from "../version.js";
|
|
12
|
+
import { configurationFieldsFromHelp, configurationFieldWarnings, STATIC_CONFIGURATION_NOTICE } from "./agentConfigurationFields.js";
|
|
12
13
|
/**
|
|
13
14
|
* Codex and Claude Code are configured by flags and their own files; neither
|
|
14
15
|
* plan exchanges capabilities on connect. Saying so explicitly keeps a caller
|
|
@@ -20,8 +21,6 @@ const NO_HANDSHAKE = Object.freeze({
|
|
|
20
21
|
});
|
|
21
22
|
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
22
23
|
const PROCESS_TERMINATION_GRACE_MS = 100;
|
|
23
|
-
const CODEX_SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
|
|
24
|
-
const CODEX_APPROVALS = ["untrusted", "on-request", "never"];
|
|
25
24
|
const ACP_PROBE_REQUEST_ID = 1;
|
|
26
25
|
export async function discoverCodexConfiguration(input) {
|
|
27
26
|
const environment = configuredAgentLaunchEnvironment(input.agent, input.environment);
|
|
@@ -43,17 +42,35 @@ export async function discoverCodexConfiguration(input) {
|
|
|
43
42
|
client.request("configRequirements/read", {}),
|
|
44
43
|
client.request("modelProvider/capabilities/read", {})
|
|
45
44
|
]);
|
|
46
|
-
const requirements =
|
|
47
|
-
const requirementRecord =
|
|
45
|
+
const requirements = requiredObject(requirementsResult, "Codex configuration requirements response").requirements;
|
|
46
|
+
const requirementRecord = requirements === null ? undefined
|
|
47
|
+
: requiredObject(requirements, "Codex configuration requirements");
|
|
48
48
|
const allowedSandboxes = optionalStrings(requirementRecord?.allowedSandboxModes);
|
|
49
|
-
const allowedApprovals =
|
|
49
|
+
const allowedApprovals = optionalApprovalChoices(requirementRecord?.allowedApprovalPolicies);
|
|
50
50
|
const allowedWebSearchModes = optionalStrings(requirementRecord?.allowedWebSearchModes);
|
|
51
|
-
const capabilityRecord =
|
|
52
|
-
|
|
51
|
+
const capabilityRecord = requiredObject(providerCapabilities, "Codex Provider capabilities");
|
|
52
|
+
if (typeof capabilityRecord.webSearch !== "boolean") {
|
|
53
|
+
throw new Error("Codex Provider capabilities did not report webSearch.");
|
|
54
|
+
}
|
|
55
|
+
const webSearch = capabilityRecord.webSearch
|
|
53
56
|
&& (allowedWebSearchModes === undefined || allowedWebSearchModes.includes("live"));
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
const fields = configurationFieldsFromHelp("codex", help).map(candidate => {
|
|
58
|
+
const allowed = candidate.key === "permission.sandbox" ? allowedSandboxes
|
|
59
|
+
: candidate.key === "permission.approval" ? allowedApprovals : undefined;
|
|
60
|
+
if (allowed !== undefined) {
|
|
61
|
+
const choices = candidate.choices.filter(choice => allowed.includes(choice.value));
|
|
62
|
+
return {
|
|
63
|
+
...candidate, choices, available: choices.length > 0,
|
|
64
|
+
reason: `${candidate.reason} Native configuration requirements applied.${choices.length === 0 ? " No usable values were confirmed." : ""}`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (candidate.key === "search")
|
|
68
|
+
return field("search", webSearch ? [choice("true")] : [], false, webSearch, webSearch ? "Native Provider capability and configuration requirements permit live web search."
|
|
69
|
+
: "Live web search is unavailable or disallowed.");
|
|
70
|
+
if (candidate.key === "profile")
|
|
71
|
+
return codexProfileField(environment);
|
|
72
|
+
return candidate;
|
|
73
|
+
});
|
|
57
74
|
return {
|
|
58
75
|
schemaVersion: 1,
|
|
59
76
|
agentId: input.agent.id,
|
|
@@ -61,21 +78,8 @@ export async function discoverCodexConfiguration(input) {
|
|
|
61
78
|
...(semanticVersion(version) === undefined ? {} : { cliVersion: semanticVersion(version) }),
|
|
62
79
|
handshake: NO_HANDSHAKE,
|
|
63
80
|
models,
|
|
64
|
-
fields
|
|
65
|
-
|
|
66
|
-
field("effort", [], true),
|
|
67
|
-
field("permission.strategy", [
|
|
68
|
-
choice("default"),
|
|
69
|
-
...(bypass ? [choice("bypass")] : []),
|
|
70
|
-
choice("configured")
|
|
71
|
-
], false, true, bypass ? undefined : "Codex bypass strategy is unavailable."),
|
|
72
|
-
field("permission.sandbox", sandboxChoices.map(choice), false),
|
|
73
|
-
field("permission.approval", approvalChoices.map(choice), false),
|
|
74
|
-
field("search", webSearch ? [choice("true")] : [], false, webSearch, webSearch ? undefined : "Live web search is unavailable or disallowed."),
|
|
75
|
-
field("profile", codexProfileChoices(environment).map(choice), true),
|
|
76
|
-
field("additionalDirectories", [], true)
|
|
77
|
-
],
|
|
78
|
-
warnings: client.warnings()
|
|
81
|
+
fields,
|
|
82
|
+
warnings: [...client.warnings(), ...configurationFieldWarnings(fields)]
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
85
|
finally {
|
|
@@ -105,9 +109,7 @@ export async function discoverClaudeConfiguration(input) {
|
|
|
105
109
|
]);
|
|
106
110
|
const initialized = object(initialization);
|
|
107
111
|
const models = array(initialized?.models, "Claude model catalog").map(claudeModel);
|
|
108
|
-
const
|
|
109
|
-
const bypass = configurationFlagAvailable(help, "--dangerously-skip-permissions");
|
|
110
|
-
const settingsSources = configurationHelpChoices(help, "--setting-sources", ["user", "project", "local"]);
|
|
112
|
+
const fields = configurationFieldsFromHelp("claude", help);
|
|
111
113
|
return {
|
|
112
114
|
schemaVersion: 1,
|
|
113
115
|
agentId: input.agent.id,
|
|
@@ -115,22 +117,8 @@ export async function discoverClaudeConfiguration(input) {
|
|
|
115
117
|
...(semanticVersion(version) === undefined ? {} : { cliVersion: semanticVersion(version) }),
|
|
116
118
|
handshake: NO_HANDSHAKE,
|
|
117
119
|
models,
|
|
118
|
-
fields
|
|
119
|
-
|
|
120
|
-
field("effort", [], true),
|
|
121
|
-
field("permission.strategy", [
|
|
122
|
-
choice("default"),
|
|
123
|
-
...(bypass ? [choice("bypass")] : []),
|
|
124
|
-
choice("configured")
|
|
125
|
-
], false, true, bypass ? undefined : "Claude bypass strategy is unavailable."),
|
|
126
|
-
field("permission.mode", permissionModes.map(choice), true),
|
|
127
|
-
field("permission.allowedTools", [], true),
|
|
128
|
-
field("permission.disallowedTools", [], true),
|
|
129
|
-
field("settingsSources", settingsSources.map(choice), false),
|
|
130
|
-
field("settingsFile", [], true),
|
|
131
|
-
field("additionalDirectories", [], true)
|
|
132
|
-
],
|
|
133
|
-
warnings: []
|
|
120
|
+
fields,
|
|
121
|
+
warnings: configurationFieldWarnings(fields)
|
|
134
122
|
};
|
|
135
123
|
}
|
|
136
124
|
async function listCodexModels(client) {
|
|
@@ -283,7 +271,7 @@ async function requestClaudeInitialization(command, args, cwd, environment, sign
|
|
|
283
271
|
export async function discoverAcpConfiguration(input) {
|
|
284
272
|
const environment = configuredAgentLaunchEnvironment(input.agent, input.environment);
|
|
285
273
|
const negotiated = await requestAcpInitialization(input.agent.command, [...input.agent.baseArgs], input.cwd, environment, input.signal);
|
|
286
|
-
const warnings = [];
|
|
274
|
+
const warnings = [STATIC_CONFIGURATION_NOTICE];
|
|
287
275
|
if (!negotiated.capabilities.loadSession) {
|
|
288
276
|
warnings.push("This ACP Agent does not support `session/load`, so a managed Yui Turn "
|
|
289
277
|
+ "cannot survive a Provider restart.");
|
|
@@ -331,7 +319,7 @@ export async function discoverAcpConfiguration(input) {
|
|
|
331
319
|
field("effort", [], true, true, "ACP selects reasoning effort with session/set_config_option, under the "
|
|
332
320
|
+ "`thought_level` category. As with the model, the accepted values are "
|
|
333
321
|
+ "negotiated per Session and verified at launch rather than listed here."),
|
|
334
|
-
field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false, true, "`default` sends no mode, so the Agent's own default stands. `configured` "
|
|
322
|
+
field("permission.strategy", [choice("default"), choice("bypass"), choice("configured")], false, true, "Static Yui strategies: `default` sends no mode, so the Agent's own default stands. `configured` "
|
|
335
323
|
+ "selects one exact mode the Agent offers. `bypass` applies the mode that "
|
|
336
324
|
+ "grants unattended action, and only for an execution component Yui can "
|
|
337
325
|
+ "identify — it is never guessed from a mode's name. None of these change "
|
|
@@ -573,47 +561,34 @@ function terminateProcess(child) {
|
|
|
573
561
|
child.once("exit", () => { clearTimeout(force); });
|
|
574
562
|
child.kill("SIGTERM");
|
|
575
563
|
}
|
|
576
|
-
|
|
564
|
+
function codexProfileField(environment) {
|
|
577
565
|
const root = environment.CODEX_HOME
|
|
578
566
|
?? join(environment.HOME ?? homedir(), ".codex");
|
|
579
567
|
try {
|
|
580
568
|
const profiles = object(parse(readFileSync(join(root, "config.toml"), "utf8")).profiles);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
catch {
|
|
584
|
-
return [];
|
|
569
|
+
const names = Object.keys(profiles ?? {}).filter((name) => name.trim().length > 0 && !name.includes("\0")).sort();
|
|
570
|
+
return field("profile", names.map(choice), true, undefined, "Local config.toml profile names, not a Provider enumeration; custom names remain explicit input.");
|
|
585
571
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return [...fallback];
|
|
592
|
-
let end = start + 1;
|
|
593
|
-
while (end < lines.length && !/^\s{2,}(?:-[A-Za-z](?:,\s*)?|--)[\w-]/.test(lines[end] ?? "")) {
|
|
594
|
-
end += 1;
|
|
572
|
+
catch (error) {
|
|
573
|
+
const absent = error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
574
|
+
return field("profile", [], true, absent ? undefined : false, absent
|
|
575
|
+
? "No local config.toml; no profile names observed. Custom names remain explicit input."
|
|
576
|
+
: "Local config.toml could not be read or parsed; profile enumeration unavailable.");
|
|
595
577
|
}
|
|
596
|
-
const section = lines.slice(start, end).join("\n");
|
|
597
|
-
const inline = /(?:\[possible values:\s*([^\]]+)\]|\(choices:\s*([^)]*)\))/i
|
|
598
|
-
.exec(section);
|
|
599
|
-
const declared = inline?.[1] ?? inline?.[2];
|
|
600
|
-
const bulletValues = declared === undefined && /Possible values:/i.test(section)
|
|
601
|
-
? [...section.matchAll(/^\s*-\s*([\w.+-]+)\s*:/gm)].map((match) => match[1] ?? "")
|
|
602
|
-
: [];
|
|
603
|
-
const parsed = [...new Set((declared ?? bulletValues.join(","))
|
|
604
|
-
.replace(/["']/g, "")
|
|
605
|
-
.split(",")
|
|
606
|
-
.map((value) => value.trim().replace(/\.$/, ""))
|
|
607
|
-
.filter((value) => /^[\w.+-]+$/.test(value)))];
|
|
608
|
-
return parsed.length === 0 ? [...fallback] : parsed;
|
|
609
|
-
}
|
|
610
|
-
function configurationFlagAvailable(help, flag) {
|
|
611
|
-
return help.replace(/\r\n/g, "\n").split("\n").some((line) => line.includes(flag));
|
|
612
578
|
}
|
|
613
|
-
function
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
579
|
+
function optionalApprovalChoices(value) {
|
|
580
|
+
if (value === null || value === undefined)
|
|
581
|
+
return undefined;
|
|
582
|
+
// Current Codex also reports structured granular policies. They constrain
|
|
583
|
+
// configuration but cannot be selected by Yui's scalar approval input; do not
|
|
584
|
+
// reject the whole legitimate response or expand them into guessed aliases.
|
|
585
|
+
return array(value, "configuration approval requirements").flatMap(entry => {
|
|
586
|
+
if (typeof entry === "string")
|
|
587
|
+
return [requiredString(entry, "approval requirement")];
|
|
588
|
+
if (object(object(entry)?.granular) !== undefined)
|
|
589
|
+
return [];
|
|
590
|
+
throw new Error("Codex configuration approval requirement is invalid.");
|
|
591
|
+
});
|
|
617
592
|
}
|
|
618
593
|
function optionalStrings(value) {
|
|
619
594
|
if (value === null || value === undefined)
|
|
@@ -7,7 +7,7 @@ import { builtinAgentEndpointImplementation, validateAgentEndpointImplementation
|
|
|
7
7
|
/** Recovery addresses retained execution identity, not a live Session grant.
|
|
8
8
|
* A released Session cache can be absent while its native binding still needs
|
|
9
9
|
* to be stopped. No active Session record is fabricated by this projection. */
|
|
10
|
-
export function
|
|
10
|
+
export function roleSessionControlTarget(set) {
|
|
11
11
|
if (set == null)
|
|
12
12
|
return undefined;
|
|
13
13
|
const current = set.sessions[set.activeAgentId];
|
|
@@ -19,7 +19,10 @@ export function taskRoleControlTarget(set) {
|
|
|
19
19
|
const agentId = fromBinding ? binding.accountScope : current?.agentId;
|
|
20
20
|
if (nativeSessionId === undefined || agentId === undefined)
|
|
21
21
|
return undefined;
|
|
22
|
-
const
|
|
22
|
+
const history = set.owner.scope === "global"
|
|
23
|
+
? Object.values(set.history ?? {})
|
|
24
|
+
: set.history ?? [];
|
|
25
|
+
const session = [...Object.values(set.sessions), ...history]
|
|
23
26
|
.find(entry => entry.nativeSessionId === nativeSessionId && entry.agentId === agentId);
|
|
24
27
|
const adapterId = session?.adapterId ?? (binding === null ? undefined : builtinAgentDriverRegistry().find(binding.providerNamespace)?.adapterId);
|
|
25
28
|
return { nativeSessionId, agentId, adapterId, status: session?.status,
|
|
@@ -176,15 +176,6 @@ export class ExecutorRegistry {
|
|
|
176
176
|
});
|
|
177
177
|
return deliveryReport(outcome);
|
|
178
178
|
}
|
|
179
|
-
async notifyOperatorInputOnce(input) {
|
|
180
|
-
if (this.runtimePorts?.notifyOperatorInputOnce !== undefined) {
|
|
181
|
-
return this.runtimePorts.notifyOperatorInputOnce(input);
|
|
182
|
-
}
|
|
183
|
-
const probe = this.readiness(input.adapterId, "operator");
|
|
184
|
-
return this.tmux.sendRoleInputOnceIfReadyAsync === undefined
|
|
185
|
-
? this.tmux.sendRoleInputOnceIfReady("operator", input.roleName, input.receiptId, input.text, probe)
|
|
186
|
-
: this.tmux.sendRoleInputOnceIfReadyAsync("operator", input.roleName, input.receiptId, input.text, probe);
|
|
187
|
-
}
|
|
188
179
|
forgetPrepared(input) {
|
|
189
180
|
for (const [deliveryId, prepared] of this.#prepared) {
|
|
190
181
|
const delivery = prepared.delivery;
|
|
@@ -21,7 +21,7 @@ import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
|
21
21
|
import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment } from "../runtime/taskRuntimeIsolation.js";
|
|
22
22
|
import { planningRuntimeCwd } from "../storage/homeLayout.js";
|
|
23
23
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
24
|
-
import { activeLiveRoleAgentSession,
|
|
24
|
+
import { activeLiveRoleAgentSession, roleSessionControlTarget } from "./agentExecutor.js";
|
|
25
25
|
import { assertCodexLaunchOverridesAvailable, inspectCodexLaunchConfig } from "./codexConfigConflict.js";
|
|
26
26
|
import { effectiveRoleForLaunch, resolveEffectiveLaunch, roleSessionMayContinue } from "./effectiveLaunch.js";
|
|
27
27
|
import { classifyWorkspacePreflight, formatWorkspacePreflightError } from "./workspacePreflightClassification.js";
|
|
@@ -69,9 +69,11 @@ export class FileRoleLaunchPlanner {
|
|
|
69
69
|
}
|
|
70
70
|
/** Recovery addresses the recorded Agent/Session, independent of delivery
|
|
71
71
|
* admission, a missing worktree, or the old Endpoint code generation. */
|
|
72
|
-
planNativeControl(
|
|
73
|
-
const set =
|
|
74
|
-
|
|
72
|
+
planNativeControl(owner) {
|
|
73
|
+
const set = owner.scope === "task"
|
|
74
|
+
? this.store.getTaskRoleSessionSet(owner.taskId, owner.roleName)
|
|
75
|
+
: this.store.getGlobalRoleSessionSet(owner.roleName);
|
|
76
|
+
const session = roleSessionControlTarget(set);
|
|
75
77
|
if (session?.adapterId !== "codex")
|
|
76
78
|
throw new Error("Native metadata control requires a recorded Codex Session.");
|
|
77
79
|
const configured = this.store.getConfiguredAgent(session.agentId);
|
|
@@ -79,9 +81,11 @@ export class FileRoleLaunchPlanner {
|
|
|
79
81
|
throw new Error("The recorded native Agent connection is unavailable.");
|
|
80
82
|
}
|
|
81
83
|
const agent = configuredAgentToDefinition(configured);
|
|
82
|
-
const connection =
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
const connection = owner.scope === "task"
|
|
85
|
+
? this.store.listEvents(owner.taskId).find(event => event.type === "runtime.native-connection-bound"
|
|
86
|
+
&& event.payload.roleName === owner.roleName && event.payload.agentId === session.agentId
|
|
87
|
+
&& event.payload.nativeSessionId === session.nativeSessionId)?.payload
|
|
88
|
+
: undefined;
|
|
85
89
|
return {
|
|
86
90
|
command: configured.command, args: [...agent.baseArgs, "app-server", "proxy"], cwd: this.home,
|
|
87
91
|
expectedAccountHome: connection?.nativeAccountHome,
|
|
@@ -758,10 +762,7 @@ function managedClaudeControlPlaneConfig(config, taskId, workItemId, runId) {
|
|
|
758
762
|
}
|
|
759
763
|
function isManagedYuiBashRule(rule) {
|
|
760
764
|
const normalized = rule.trim();
|
|
761
|
-
return /^Bash\(yui(?:\s|:\*|\*|\))/u.test(normalized)
|
|
762
|
-
// Yui no longer writes a control-plane digest into a managed rule; this
|
|
763
|
-
// shape only clears one an earlier release left in a Provider config.
|
|
764
|
-
|| /^Bash\(.*\s--yui-control\s/u.test(normalized);
|
|
765
|
+
return /^Bash\(yui(?:\s|:\*|\*|\))/u.test(normalized);
|
|
765
766
|
}
|
|
766
767
|
function canonicalPath(path) {
|
|
767
768
|
const absolute = resolve(path);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One mailbox batch becomes one short
|
|
2
|
+
* One mailbox batch becomes one short system notification. The durable
|
|
3
3
|
* records remain the context authority; this wake text carries only stable
|
|
4
4
|
* identities and exact CLI reads, never copied Task or Provider narrative.
|
|
5
5
|
*/
|
|
@@ -11,6 +11,7 @@ export function createOperatorBatchPresentation(batchId, items) {
|
|
|
11
11
|
text: [
|
|
12
12
|
"[Yui updates]",
|
|
13
13
|
"Yui recorded the following durable updates. Inspect the referenced records and present only information that changes the user's understanding, authorization, or next action.",
|
|
14
|
+
"This notification is not a new execution assignment or authorization to replay prior operations.",
|
|
14
15
|
...items.flatMap(renderItem)
|
|
15
16
|
].join("\n")
|
|
16
17
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import { createRun
|
|
2
|
+
import { createRun } from "../agentRun/agentRun.js";
|
|
3
3
|
import { createRunInput } from "../context/runInputContract.js";
|
|
4
4
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot } from "../context/contextSnapshot.js";
|
|
5
|
+
import { readRunContextSnapshot } from "../context/runContextPack.js";
|
|
5
6
|
import { roleSessionMayContinue } from "../executor/effectiveLaunch.js";
|
|
6
7
|
import { enqueueRoleRunDispatch } from "../coordination/workMailboxQueue.js";
|
|
7
8
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
@@ -265,12 +266,9 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
|
|
|
265
266
|
continue;
|
|
266
267
|
const previous = store.listRuns(taskId).filter((run) => run.roleName === owner.roleName
|
|
267
268
|
&& run.workItemId === owner.workItemId && run.reviewRoundId === owner.reviewRoundId).at(-1);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
markNotDelivered(store, message, "assignment-context-unavailable");
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
269
|
+
// Missing or drifted evidence is diagnosed by the caller; never reconstruct
|
|
270
|
+
// an Assignment from today's Task facts.
|
|
271
|
+
const baseline = readRunContextSnapshot(store, previous);
|
|
274
272
|
const runId = store.nextRunId(taskId);
|
|
275
273
|
const round = owner.reviewRoundId === undefined ? null : store.getReviewRound(taskId, owner.reviewRoundId);
|
|
276
274
|
const continuingRound = round === null ? null : (() => {
|
|
@@ -300,9 +298,10 @@ export function prepareMessageContinuations(store, taskId, now, roleName) {
|
|
|
300
298
|
resources, refs: resources.map(({ ref }) => ref)
|
|
301
299
|
});
|
|
302
300
|
store.saveContextSnapshot(snapshot);
|
|
303
|
-
const run =
|
|
301
|
+
const run = createRun(runId, taskId, recipient.roleName, "resume", createRunInput({ source: { type: "yui", channel: "message-continuation" },
|
|
304
302
|
directive: `Continue the same Assignment in its existing workspace. Read Messages ${batch.map((m) => m.id).join(", ")} from this exact Context. Message receipt is not implementation or acceptance.`,
|
|
305
|
-
|
|
303
|
+
contextSnapshotRef: contextSnapshotRef(snapshot),
|
|
304
|
+
deltaRefIds: batch.map((entry) => entry.id) }), now, copyAssignment(owner));
|
|
306
305
|
store.saveRun(run);
|
|
307
306
|
store.saveActiveRun(run);
|
|
308
307
|
if (continuingRound !== null)
|
|
@@ -678,13 +678,6 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
678
678
|
})();
|
|
679
679
|
const storage = (() => {
|
|
680
680
|
try {
|
|
681
|
-
let stateJsonBytes = UNSUPPORTED;
|
|
682
|
-
try {
|
|
683
|
-
stateJsonBytes = statSync(join(home, "state.json")).size;
|
|
684
|
-
}
|
|
685
|
-
catch {
|
|
686
|
-
stateJsonBytes = UNSUPPORTED;
|
|
687
|
-
}
|
|
688
681
|
let databaseBytes = UNSUPPORTED;
|
|
689
682
|
try {
|
|
690
683
|
databaseBytes = statSync(join(home, "yui.db")).size;
|
|
@@ -693,15 +686,13 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
693
686
|
databaseBytes = UNSUPPORTED;
|
|
694
687
|
}
|
|
695
688
|
return ok({
|
|
696
|
-
stateJsonBytes,
|
|
697
689
|
databaseBytes,
|
|
698
690
|
// The audit aggregates history; live db integrity is the status
|
|
699
691
|
// command's job (it runs PRAGMA quick_check). Report presence/size
|
|
700
692
|
// and the authoritative backend, not a duplicate health probe.
|
|
701
693
|
databaseHealth: UNSUPPORTED,
|
|
702
694
|
backend: resolveTaskStoreBackendForHome(home),
|
|
703
|
-
runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED
|
|
704
|
-
deploymentsBytes: ports.directorySize(join(home, "runtime", "deployments")) ?? UNSUPPORTED
|
|
695
|
+
runtimeDirBytes: ports.directorySize(join(home, "runtime")) ?? UNSUPPORTED
|
|
705
696
|
});
|
|
706
697
|
}
|
|
707
698
|
catch (error) {
|
|
@@ -165,9 +165,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
165
165
|
|| storage.status === "unsupported"
|
|
166
166
|
? storage.currentVersion
|
|
167
167
|
: UNSUPPORTED;
|
|
168
|
-
const statePath = join(home, "state.json");
|
|
169
|
-
const statePresent = ports.exists(statePath);
|
|
170
|
-
const stateBytes = ports.fileSize(statePath);
|
|
171
168
|
const dbPath = join(home, "yui.db");
|
|
172
169
|
const dbPresent = ports.exists(dbPath);
|
|
173
170
|
const dbBytes = ports.fileSize(dbPath);
|
|
@@ -221,14 +218,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
221
218
|
remediation: "Preserve this Home for diagnosis and restore a known-good backup."
|
|
222
219
|
});
|
|
223
220
|
}
|
|
224
|
-
if (ports.exists(join(home, "schema.json"))) {
|
|
225
|
-
findings.push({
|
|
226
|
-
code: "ignored-legacy-storage-manifest",
|
|
227
|
-
severity: "warning",
|
|
228
|
-
message: "schema.json is legacy metadata and is not a storage authority.",
|
|
229
|
-
remediation: "This file is not used. Preserve or archive it separately after inspecting its contents."
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
221
|
if (dbPresent && dbHealth !== null && dbHealth !== "ok") {
|
|
233
222
|
findings.push({
|
|
234
223
|
code: "database-unhealthy",
|
|
@@ -237,14 +226,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
237
226
|
remediation: "Preserve this Home for diagnosis and initialize a new Home."
|
|
238
227
|
});
|
|
239
228
|
}
|
|
240
|
-
if (statePresent) {
|
|
241
|
-
findings.push({
|
|
242
|
-
code: "ignored-historical-store",
|
|
243
|
-
severity: "warning",
|
|
244
|
-
message: "state.json is present but is not read or written by the current store.",
|
|
245
|
-
remediation: "Keep it only as read-only historical evidence or archive it outside the active Home."
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
229
|
const rawWorkerFlag = ports.env.YUI_STORE_WORKER;
|
|
249
230
|
if (rawWorkerFlag !== undefined && !["0", "1", "false", "true"].includes(rawWorkerFlag.toLowerCase())) {
|
|
250
231
|
findings.push({
|
|
@@ -261,10 +242,6 @@ export function collectStorageIdentity(home, ports = createProductionStorageIden
|
|
|
261
242
|
minimumStorageVersion: MIN_SUPPORTED_STORAGE_VERSION,
|
|
262
243
|
configuredBackend,
|
|
263
244
|
workerEnabled,
|
|
264
|
-
physicalStateJson: {
|
|
265
|
-
present: statePresent,
|
|
266
|
-
bytes: stateBytes ?? UNSUPPORTED
|
|
267
|
-
},
|
|
268
245
|
physicalDatabase: {
|
|
269
246
|
present: dbPresent,
|
|
270
247
|
bytes: dbBytes ?? UNSUPPORTED,
|
|
@@ -23,7 +23,9 @@ export function renderAgentConfigurationCatalog(resolved) {
|
|
|
23
23
|
// query rather than a deliberate boundary, so it is shown either way.
|
|
24
24
|
field.available === false
|
|
25
25
|
? `unavailable${field.reason === undefined ? "" : `: ${field.reason}`}`
|
|
26
|
-
: field.
|
|
26
|
+
: field.available === true
|
|
27
|
+
? `available${field.reason === undefined ? "" : `: ${field.reason}`}`
|
|
28
|
+
: field.reason ?? "Native support not confirmed",
|
|
27
29
|
field.allowCustom ? "yes" : "no"
|
|
28
30
|
]);
|
|
29
31
|
const sections = [
|
|
@@ -39,7 +41,7 @@ export function renderAgentConfigurationCatalog(resolved) {
|
|
|
39
41
|
agentModelOptionsSummary(catalog),
|
|
40
42
|
...(fieldRows.length === 0
|
|
41
43
|
? []
|
|
42
|
-
: [renderTable("
|
|
44
|
+
: [renderTable("Configuration fields (native observations and static adapter contracts)", [
|
|
43
45
|
{ header: "Field", minWidth: 12, maxWidth: 30 },
|
|
44
46
|
{ header: "Values", minWidth: 18, maxWidth: 58 },
|
|
45
47
|
{ header: "Status", minWidth: 10, maxWidth: 38 },
|
|
@@ -131,14 +131,14 @@ async function activateLocked(ports, locked) {
|
|
|
131
131
|
catch (rollbackError) {
|
|
132
132
|
return {
|
|
133
133
|
outcome: "aborted", phase: "preflight",
|
|
134
|
-
message: `
|
|
134
|
+
message: `Storage preflight failed: ${messageOf(error)}; rollback failed: ${messageOf(rollbackError)}`,
|
|
135
135
|
action: "No candidate was started and the release pointer is unchanged. Preserve the fence and inspect the old Controller.",
|
|
136
136
|
recoverable: true
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
return {
|
|
140
140
|
outcome: "aborted", phase: "preflight",
|
|
141
|
-
message: `Fenced
|
|
141
|
+
message: `Fenced storage preflight failed: ${messageOf(error)}`,
|
|
142
142
|
action: "The old Controller is serving again; the active release and Home are unchanged.",
|
|
143
143
|
recoverable: true
|
|
144
144
|
};
|
|
@@ -38,41 +38,19 @@ import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
|
38
38
|
* an unpersisted success into an ambiguous/fail-closed outcome; reporting
|
|
39
39
|
* success for an effect whose dedup record did not land would let a crash
|
|
40
40
|
* re-run the effect.
|
|
41
|
-
*
|
|
42
|
-
* Unsupported layouts
|
|
43
|
-
* -------------------
|
|
44
|
-
* The per-key `schemaVersion: 1` layout is the only supported format. A
|
|
45
|
-
* legacy whole-map file (`release-idempotency.json`) from an unsupported
|
|
46
|
-
* older layout is never auto-migrated: it fails closed with a bounded
|
|
47
|
-
* diagnostic so the operator can inspect and clean it up deliberately.
|
|
48
41
|
*/
|
|
49
42
|
export const RELEASE_IDEMPOTENCY_SCHEMA_VERSION = 1;
|
|
50
43
|
const DIRECTORY_NAME = "release-idempotency";
|
|
51
|
-
const LEGACY_FILE_NAME = "release-idempotency.json";
|
|
52
44
|
export function createFileReleaseIdempotencyStore(home, now = () => new Date()) {
|
|
53
45
|
const directory = join(home, DIRECTORY_NAME);
|
|
54
|
-
const legacyPath = join(home, LEGACY_FILE_NAME);
|
|
55
46
|
function recordPath(key) {
|
|
56
47
|
// encodeURIComponent is injective and leaves only Linux-filename-safe
|
|
57
48
|
// characters unescaped, so distinct keys never collide on disk.
|
|
58
49
|
return join(directory, `${encodeURIComponent(key)}.json`);
|
|
59
50
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Fail closed when an unsupported legacy whole-map file is present.
|
|
62
|
-
* The per-key layout is the only supported format; a whole-map file
|
|
63
|
-
* from an unsupported older layout must not be silently trusted.
|
|
64
|
-
*/
|
|
65
|
-
function rejectLegacyLayout() {
|
|
66
|
-
if (existsSync(legacyPath)) {
|
|
67
|
-
throw new Error(`unsupported legacy release idempotency layout: ${legacyPath} ` +
|
|
68
|
-
`(whole-map file). Remove it after inspecting its contents; ` +
|
|
69
|
-
`the per-key schemaVersion 1 layout is the only supported format.`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
51
|
return {
|
|
73
52
|
async load(key) {
|
|
74
53
|
try {
|
|
75
|
-
rejectLegacyLayout();
|
|
76
54
|
const path = recordPath(key);
|
|
77
55
|
if (!existsSync(path))
|
|
78
56
|
return undefined;
|
|
@@ -88,7 +66,6 @@ export function createFileReleaseIdempotencyStore(home, now = () => new Date())
|
|
|
88
66
|
throw new Error(`release idempotency store only records succeeded effects: ${effect.outcome}`);
|
|
89
67
|
}
|
|
90
68
|
try {
|
|
91
|
-
rejectLegacyLayout();
|
|
92
69
|
const record = Object.freeze({
|
|
93
70
|
schemaVersion: RELEASE_IDEMPOTENCY_SCHEMA_VERSION,
|
|
94
71
|
key,
|
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import { chmod, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { runUpdate } from "../cli/updateOrchestrator.js";
|
|
5
|
+
import { renderUpdateResult } from "../cli/updateCommand.js";
|
|
5
6
|
import { activatedControllerEntrypoint } from "../cli/updatePorts.js";
|
|
6
7
|
import { runProjectCommand } from "../commands/projectCommands.js";
|
|
7
8
|
import { restartFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
@@ -116,11 +117,14 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
116
117
|
// Best-effort persistence; the effect may still succeed.
|
|
117
118
|
}
|
|
118
119
|
const result = runUpdate(inspectedPorts, { home: deps.home });
|
|
120
|
+
// Preserve cleanup/restore receipts in the existing step log, even
|
|
121
|
+
// when the binary result is success or a later phase is blocked.
|
|
122
|
+
const updateLog = renderUpdateResult(result);
|
|
119
123
|
if (result.outcome === "updated" || result.outcome === "already-current") {
|
|
120
124
|
return {
|
|
121
125
|
outcome: "succeeded",
|
|
122
126
|
externalId: result.version,
|
|
123
|
-
logs: [
|
|
127
|
+
logs: [updateLog]
|
|
124
128
|
};
|
|
125
129
|
}
|
|
126
130
|
if (result.outcome === "aborted" && !result.recoverable) {
|
|
@@ -143,7 +147,7 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
143
147
|
if (result.controllerOwnershipUnknown === true) {
|
|
144
148
|
return {
|
|
145
149
|
outcome: "timeout",
|
|
146
|
-
logs: [
|
|
150
|
+
logs: [updateLog]
|
|
147
151
|
};
|
|
148
152
|
}
|
|
149
153
|
// P1-2 (rr22): persist the EXACT activation target (the resolved
|
|
@@ -154,13 +158,13 @@ export function createReleaseWorkflowPorts(deps) {
|
|
|
154
158
|
return {
|
|
155
159
|
outcome: "timeout",
|
|
156
160
|
...(identity === undefined ? {} : { externalIdentity: identity }),
|
|
157
|
-
logs: [
|
|
161
|
+
logs: [updateLog]
|
|
158
162
|
};
|
|
159
163
|
}
|
|
160
164
|
return {
|
|
161
165
|
outcome: "failed",
|
|
162
166
|
error: `cli-update aborted at phase ${result.phase}: ${result.message}`,
|
|
163
|
-
logs: [
|
|
167
|
+
logs: [updateLog]
|
|
164
168
|
};
|
|
165
169
|
}
|
|
166
170
|
case "controller-replace": {
|