@zq-silk/yui 2.21.0 → 2.23.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.
- package/README.md +5 -0
- package/dist/cli/commandCatalog.js +87 -27
- package/dist/cli/dynamicCompletion.js +4 -1
- package/dist/cli/helpRenderer.js +12 -1
- package/dist/cli/interactionPolicy.js +2 -0
- package/dist/cli/invocationRouter.js +5 -5
- package/dist/cli/roleWizard.js +5 -1
- package/dist/cli/updateCommand.js +20 -3
- package/dist/cli/updateOrchestrator.js +13 -0
- package/dist/cli/updatePorts.js +27 -0
- package/dist/commands/globalRoleCommands.js +33 -8
- package/dist/commands/inboxCommands.js +39 -12
- package/dist/commands/operatorCommands.js +2 -5
- package/dist/commands/releaseCommands.js +16 -2
- package/dist/commands/roleConfiguration.js +25 -2
- package/dist/commands/sessionCommands.js +5 -2
- package/dist/commands/taskCommands.js +217 -64
- package/dist/commands/taskContextCommand.js +26 -4
- package/dist/commands/taskFactCommands.js +15 -2
- package/dist/commands/taskIntegrationCommands.js +3 -3
- package/dist/commands/taskRoleRuntimeStatus.js +19 -5
- package/dist/commands/textInput.js +2 -2
- package/dist/context/contextFamilies.js +10 -0
- package/dist/context/currentSkills.js +37 -4
- package/dist/context/runContextPack.js +44 -5
- package/dist/context/runInputContract.js +1 -1
- package/dist/context/sessionBootstrapManifest.js +21 -6
- package/dist/context/sessionSkillNotice.js +74 -0
- package/dist/context/taskContext.js +100 -16
- package/dist/controlPlaneCli.js +17 -7
- package/dist/controller/agentRuntimeObserver.js +57 -12
- package/dist/controller/clientRuntime.js +66 -18
- package/dist/controller/controller.js +19 -2
- package/dist/controller/diagnosticJournal.js +10 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +74 -24
- package/dist/controller/hostLifecycle.js +153 -0
- package/dist/controller/hostLifecycleChecks.js +27 -0
- package/dist/controller/hostUpgrade.js +344 -0
- package/dist/controller/messageHealth.js +4 -3
- package/dist/controller/providerRetryAdmission.js +15 -1
- package/dist/controller/providerRetryDelivery.js +18 -2
- package/dist/controller/runtime.js +82 -21
- package/dist/controller/runtimeEventInbox.js +107 -13
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/controller/sessionNotify.js +5 -46
- package/dist/controller/taskRunRecovery.js +153 -0
- package/dist/core/controllerClient.js +16 -2
- package/dist/core/controllerServer.js +14 -5
- package/dist/core/controllerTelemetry.js +51 -5
- package/dist/errors/cliFailure.js +14 -5
- package/dist/execution/codexThreadNaming.js +54 -43
- package/dist/executor/agentAdapter.js +32 -4
- package/dist/executor/agentConfigurationFields.js +2 -0
- package/dist/executor/fileRoleLaunchPlanner.js +18 -3
- package/dist/integration/gitIntegrationService.js +3 -2
- package/dist/integration/integrationSourceApplication.js +12 -1
- package/dist/interaction/notificationPresentation.js +11 -16
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/message/messageContinuation.js +3 -1
- package/dist/output/boundedRead.js +36 -7
- package/dist/output/rolePresentation.js +3 -1
- package/dist/runtime/agentEndpoint.js +11 -1
- package/dist/runtime/agentEndpointIdentity.js +2 -0
- package/dist/runtime/agentHost.js +174 -40
- package/dist/runtime/agentHostImplementation.js +3 -1
- package/dist/runtime/codexAppServerRuntime.js +113 -24
- package/dist/runtime/codexInteractiveHost.js +102 -11
- package/dist/runtime/codexNativeAccess.js +1 -1
- package/dist/runtime/contextRecovery.js +15 -0
- package/dist/runtime/hostSessionLifecycle.js +83 -0
- package/dist/runtime/hostUpgradeGate.js +43 -0
- package/dist/runtime/lifecycleChecks.js +47 -0
- package/dist/runtime/lifecycleReservation.js +9 -1
- package/dist/runtime/nativeTitleReceipt.js +31 -0
- package/dist/runtime/nativeTitleSync.js +192 -0
- package/dist/runtime/providerRetry.js +9 -6
- package/dist/runtime/providerRuntimeIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +65 -35
- package/dist/scheduler/wakeupQueue.js +10 -4
- package/dist/storage/sqliteSchema.js +29 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/tmux/tmuxManager.js +4 -2
- package/dist/web/webSettings.js +2 -0
- package/docs/authorization.md +18 -3
- package/docs/cli-information-contract.md +228 -6
- package/docs/cli-information-contract.zh-CN.md +95 -5
- package/docs/managed-turn-and-session-runtime.md +177 -21
- package/docs/managed-turn-and-session-runtime.zh-CN.md +126 -14
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +65 -0
- package/docs/release-workflow.zh-CN.md +48 -0
- package/docs/roles-and-configuration.md +28 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/storage-baseline.md +13 -1
- package/docs/storage-baseline.zh-CN.md +5 -1
- package/docs/task-delivery.md +7 -0
- package/docs/task-delivery.zh-CN.md +5 -0
- package/docs/testing/review-evaluation.md +7 -7
- package/docs/testing/review-evaluation.zh-CN.md +7 -7
- package/docs/testing/verification-levels.md +45 -2
- package/docs/testing/verification-levels.zh-CN.md +21 -2
- package/i18n/README.zh-CN.md +6 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +98 -193
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/artifacts.md +41 -0
- package/skills/yui-leader/references/assignment-execution.md +140 -0
- package/skills/yui-leader/references/completion.md +79 -0
- package/skills/yui-leader/references/delivery-scope.md +26 -0
- package/skills/yui-leader/references/execution.md +16 -487
- package/skills/yui-leader/references/implementation.md +81 -0
- package/skills/yui-leader/references/planning.md +7 -45
- package/skills/yui-leader/references/review.md +90 -0
- package/skills/yui-leader/references/task-progress.md +23 -0
- package/skills/yui-leader/references/work-allocation.md +118 -0
- package/skills/yui-operator/SKILL.md +77 -333
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-operator/references/body-transport.md +62 -0
- package/skills/yui-operator/references/configuration.md +47 -0
- package/skills/yui-operator/references/lifecycle.md +41 -0
- package/skills/yui-operator/references/progress.md +39 -0
- package/skills/yui-operator/references/routing.md +57 -0
- package/skills/yui-operator/references/submission.md +56 -0
- package/skills/yui-operator/references/task-delivery.md +1 -1
- package/skills/yui-operator/references/web-lifecycle.md +19 -0
- package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
- package/skills/yui-runtime/SKILL.md +182 -531
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/archive.md +51 -0
- package/skills/yui-runtime/references/command-discovery.md +41 -0
- package/skills/yui-runtime/references/completed-followup.md +65 -0
- package/skills/yui-runtime/references/context-ordering.md +3 -34
- package/skills/yui-runtime/references/context-reads.md +61 -0
- package/skills/yui-runtime/references/dispatched-run.md +61 -0
- package/skills/yui-runtime/references/execution-failure.md +105 -0
- package/skills/yui-runtime/references/global-context.md +18 -0
- package/skills/yui-runtime/references/global-input-controls.md +44 -0
- package/skills/yui-runtime/references/host-custody.md +30 -0
- package/skills/yui-runtime/references/inbox-recovery.md +77 -0
- package/skills/yui-runtime/references/incremental-context.md +40 -0
- package/skills/yui-runtime/references/publication.md +4 -111
- package/skills/yui-runtime/references/recovery.md +32 -192
- package/skills/yui-runtime/references/session-identity.md +18 -0
- package/skills/yui-runtime/references/session-replacement.md +45 -0
- package/skills/yui-runtime/references/uncertain-execution.md +29 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, open, readFile, rename, rmdir } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { createNonBlockingChecks } from "./lifecycleChecks.js";
|
|
5
|
+
import { CodexNamingClient } from "../execution/codexThreadNaming.js";
|
|
6
|
+
import { nativeTitleDirectory, parseNativeTitleReceipt } from "./nativeTitleReceipt.js";
|
|
7
|
+
/** A standalone metadata process, NOT a second connection to the shared
|
|
8
|
+
* business daemon. It never starts/resumes a Thread or runs a Turn. Its hard
|
|
9
|
+
* process deadline releases even native locks after an unanswered request.
|
|
10
|
+
* Caller launches this in an independent lane; Goal/Turn never await it. */
|
|
11
|
+
export async function synchronizeNativeTitle(request) {
|
|
12
|
+
const result = (status, detail, extra = {}) => ({ threadId: request.identity.threadId, status, detail, ...extra });
|
|
13
|
+
let client;
|
|
14
|
+
let locked = false;
|
|
15
|
+
let submitted = false;
|
|
16
|
+
let receipt;
|
|
17
|
+
const cancel = () => client?.close("SIGKILL");
|
|
18
|
+
const directory = nativeTitleDirectory(request.home, request.identity);
|
|
19
|
+
try {
|
|
20
|
+
if (!isAbsolute(request.home) || !isAbsolute(request.accountHome)
|
|
21
|
+
|| [request.title, request.generatedTitle].some(title => title !== undefined
|
|
22
|
+
&& (!title || title.trim() !== title || title.length > 1024 || /[\r\n\0]/u.test(title)))) {
|
|
23
|
+
return result("not-submitted", "Native title request has an invalid local identity or title.");
|
|
24
|
+
}
|
|
25
|
+
if (request.signal?.aborted)
|
|
26
|
+
return result("not-submitted", "Title attachment closed.");
|
|
27
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
28
|
+
// Never remove another attempt's lock, including after a Host crash.
|
|
29
|
+
await mkdir(join(directory, "writing"), { mode: 0o700 });
|
|
30
|
+
locked = true;
|
|
31
|
+
try {
|
|
32
|
+
receipt = parseNativeTitleReceipt(await readFile(join(directory, "receipt.json"), "utf8"), request.identity);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code !== "ENOENT")
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
if (receipt !== undefined && (receipt.accountHome !== request.accountHome || receipt.status !== "confirmed")) {
|
|
39
|
+
return result("not-submitted", "Prior title identity/effect is unresolved; no rename replayed.", { attemptId: receipt.attemptId, receiptPath: join(directory, "receipt.json") });
|
|
40
|
+
}
|
|
41
|
+
if (request.localTitle !== undefined && request.localTitle !== request.previousTitle
|
|
42
|
+
&& !receipt?.generatedTitles.includes(request.localTitle)) {
|
|
43
|
+
return result("preserved", "Yui Session has a custom or unowned local title; native name left unchanged.");
|
|
44
|
+
}
|
|
45
|
+
client = await CodexNamingClient.start(request.command, { ...request.environment, CODEX_HOME: request.accountHome }, request.baseArgs, request.timeoutMs ?? 2_000);
|
|
46
|
+
request.signal?.addEventListener("abort", cancel, { once: true });
|
|
47
|
+
if (request.signal?.aborted)
|
|
48
|
+
return result("not-submitted", "Title attachment closed.");
|
|
49
|
+
if (client.accountHome !== request.accountHome) {
|
|
50
|
+
return result("not-submitted", "Metadata process account identity did not match the business connection.");
|
|
51
|
+
}
|
|
52
|
+
const read = await client.request("thread/read", { threadId: request.identity.threadId, includeTurns: false });
|
|
53
|
+
const thread = read.thread;
|
|
54
|
+
if (thread?.id !== request.identity.threadId
|
|
55
|
+
|| !(thread.name == null || typeof thread.name === "string")) {
|
|
56
|
+
return result("not-submitted", "Native title observation has no exact Thread/name identity.");
|
|
57
|
+
}
|
|
58
|
+
const current = typeof thread.name === "string" && thread.name.length > 0 ? thread.name : null;
|
|
59
|
+
const previous = receipt?.requestedTitle ?? request.previousTitle;
|
|
60
|
+
const title = request.generatedTitle !== undefined
|
|
61
|
+
&& (request.title === previous || receipt?.generatedTitles.includes(request.title))
|
|
62
|
+
? request.generatedTitle : request.title;
|
|
63
|
+
// A prefix (including the historic Skill notice) is not ownership proof.
|
|
64
|
+
if (current !== previous && !(request.newThread && current === null && receipt === undefined)) {
|
|
65
|
+
const pollution = [current, thread.preview].some(value => typeof value === "string"
|
|
66
|
+
&& (value.startsWith("Yui Host checked current Skills;")
|
|
67
|
+
|| value.startsWith("Yui Session Manifest:")
|
|
68
|
+
|| value.startsWith("<!-- yui:skills-checked -->")));
|
|
69
|
+
return result("preserved", pollution
|
|
70
|
+
? "Historical Skill-prefix title/preview candidate; no native ownership proof, so no repair submitted."
|
|
71
|
+
: "Native name has no matching generated-title provenance; left unchanged.");
|
|
72
|
+
}
|
|
73
|
+
if (current === title)
|
|
74
|
+
return result("confirmed", "Existing generated title observed.", { title, ...(receipt === undefined ? {} : { attemptId: receipt.attemptId }) });
|
|
75
|
+
receipt = {
|
|
76
|
+
schemaVersion: 1, identity: request.identity, accountHome: request.accountHome,
|
|
77
|
+
attemptId: randomUUID(), requestedTitle: title, expectedTitle: current,
|
|
78
|
+
status: "submitted", generatedTitles: receipt?.generatedTitles ?? (previous === undefined ? [] : [previous])
|
|
79
|
+
};
|
|
80
|
+
// Durable intent precedes the only mutation. A crash anywhere after this
|
|
81
|
+
// write is an unknown effect, never permission to resubmit.
|
|
82
|
+
await saveReceipt(directory, receipt);
|
|
83
|
+
if (request.signal?.aborted)
|
|
84
|
+
return result("not-submitted", "Title attachment closed before mutation.");
|
|
85
|
+
// Persistence may yield to a user's edit. Recheck immediately before
|
|
86
|
+
// sending; the native API has no expected-name compare-and-swap.
|
|
87
|
+
const prewrite = await client.request("thread/read", { threadId: request.identity.threadId, includeTurns: false });
|
|
88
|
+
const fresh = prewrite.thread;
|
|
89
|
+
if (fresh?.id !== request.identity.threadId || (fresh.name ?? null) !== current) {
|
|
90
|
+
await saveReceipt(directory, { ...receipt, status: "preserved" });
|
|
91
|
+
return result("preserved", "Native name changed before submission; no rename sent.", { attemptId: receipt.attemptId });
|
|
92
|
+
}
|
|
93
|
+
if (request.signal?.aborted)
|
|
94
|
+
return result("not-submitted", "Title attachment closed before mutation.");
|
|
95
|
+
submitted = true;
|
|
96
|
+
await client.request("thread/name/set", { threadId: request.identity.threadId, name: title });
|
|
97
|
+
const confirmation = await client.request("thread/read", { threadId: request.identity.threadId, includeTurns: false });
|
|
98
|
+
const observed = confirmation.thread;
|
|
99
|
+
if (observed?.id !== request.identity.threadId || observed.name !== title) {
|
|
100
|
+
throw new Error("Title mutation has no matching readback; preserve unknown effect.");
|
|
101
|
+
}
|
|
102
|
+
receipt = { ...receipt, status: "confirmed",
|
|
103
|
+
generatedTitles: [...new Set([...receipt.generatedTitles, title])] };
|
|
104
|
+
await saveReceipt(directory, receipt);
|
|
105
|
+
return result("confirmed", "Native title acknowledged and read back.", {
|
|
106
|
+
attemptId: receipt.attemptId, title, receiptPath: join(directory, "receipt.json")
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (submitted && receipt !== undefined) {
|
|
111
|
+
await saveReceipt(directory, { ...receipt, status: "unknown" }).catch(() => { });
|
|
112
|
+
}
|
|
113
|
+
return result(submitted ? "unknown" : "not-submitted", error instanceof Error ? error.message : String(error), receipt === undefined ? {} : { attemptId: receipt.attemptId, receiptPath: join(directory, "receipt.json") });
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
request.signal?.removeEventListener("abort", cancel);
|
|
117
|
+
client?.close();
|
|
118
|
+
await client?.exited;
|
|
119
|
+
if (locked)
|
|
120
|
+
await rmdir(join(directory, "writing")).catch(() => { });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Use task-161's independent lifecycle-check lane without changing its FSM.
|
|
124
|
+
* One trigger per attachment; receipt fencing handles reconnects/other Hosts. */
|
|
125
|
+
export function createNativeTitleSync(payload, threadId, accountHome, report) {
|
|
126
|
+
let triggered = false;
|
|
127
|
+
const checks = createNonBlockingChecks([{
|
|
128
|
+
id: "native-title",
|
|
129
|
+
async run(_observation, signal) {
|
|
130
|
+
const env = payload.environment;
|
|
131
|
+
const title = payload.providerControl?.sessionTitle ?? env.YUI_SESSION_TITLE ?? env.YUI_NATIVE_TITLE_PROPOSAL;
|
|
132
|
+
if (title === undefined)
|
|
133
|
+
return;
|
|
134
|
+
const scope = env.YUI_SESSION_SCOPE;
|
|
135
|
+
if (!accountHome || !env.YUI_HOME || !env.YUI_ROLE || !env.YUI_AGENT_ID
|
|
136
|
+
|| !["task", "global"].includes(scope ?? "") || scope === "task" && !env.YUI_TASK_ID
|
|
137
|
+
|| !env.YUI_AGENT_BASE_ARGS) {
|
|
138
|
+
report({ threadId, status: "not-submitted", detail: "Native title synchronization lacks an exact local account/launch identity." });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const baseArgs = JSON.parse(env.YUI_AGENT_BASE_ARGS);
|
|
142
|
+
if (!Array.isArray(baseArgs) || baseArgs.some(value => typeof value !== "string")) {
|
|
143
|
+
throw new Error("Invalid native naming executable arguments.");
|
|
144
|
+
}
|
|
145
|
+
const observed = await synchronizeNativeTitle({
|
|
146
|
+
home: env.YUI_HOME, identity: {
|
|
147
|
+
scope: scope,
|
|
148
|
+
...(scope === "task" ? { taskId: env.YUI_TASK_ID } : {}),
|
|
149
|
+
roleName: env.YUI_ROLE, agentId: env.YUI_AGENT_ID, threadId
|
|
150
|
+
},
|
|
151
|
+
accountHome, command: payload.command, baseArgs,
|
|
152
|
+
environment: env, title,
|
|
153
|
+
...(env.YUI_NATIVE_TITLE_PROPOSAL === undefined ? {} : { generatedTitle: env.YUI_NATIVE_TITLE_PROPOSAL }),
|
|
154
|
+
...(env.YUI_NATIVE_TITLE_LOCAL === undefined ? {} : { localTitle: env.YUI_NATIVE_TITLE_LOCAL }),
|
|
155
|
+
newThread: payload.providerControl?.mode === "new"
|
|
156
|
+
|| payload.providerControl === undefined && env.YUI_NATIVE_SESSION_ID === undefined,
|
|
157
|
+
...(payload.providerControl?.previousSessionTitle === undefined ? {} : {
|
|
158
|
+
previousTitle: payload.providerControl.previousSessionTitle
|
|
159
|
+
}),
|
|
160
|
+
signal
|
|
161
|
+
});
|
|
162
|
+
if (!signal.aborted)
|
|
163
|
+
report(observed);
|
|
164
|
+
}
|
|
165
|
+
}], error => report({ threadId, status: "not-submitted", detail: String(error) }));
|
|
166
|
+
return {
|
|
167
|
+
notify() { if (!triggered) {
|
|
168
|
+
triggered = true;
|
|
169
|
+
checks.notify(true);
|
|
170
|
+
} },
|
|
171
|
+
close() { checks.close(); }
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
async function saveReceipt(directory, receipt) {
|
|
175
|
+
const temporary = join(directory, `${receipt.attemptId}-${receipt.status}.tmp`);
|
|
176
|
+
const file = await open(temporary, "wx", 0o600);
|
|
177
|
+
try {
|
|
178
|
+
await file.writeFile(JSON.stringify(receipt) + "\n");
|
|
179
|
+
await file.sync();
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await file.close();
|
|
183
|
+
}
|
|
184
|
+
await rename(temporary, join(directory, "receipt.json"));
|
|
185
|
+
const parent = await open(directory, "r");
|
|
186
|
+
try {
|
|
187
|
+
await parent.sync();
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
await parent.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -164,16 +164,16 @@ export function completeProviderRetry(binding, turn) {
|
|
|
164
164
|
export function providerRetryAttemptId(retry) {
|
|
165
165
|
return `provider-retry:${retry.chainId}/${retry.transportSequence + 1}`;
|
|
166
166
|
}
|
|
167
|
-
|
|
167
|
+
/** Recovery guidance only. User input is delivered separately, once. */
|
|
168
|
+
export function providerRetryContext(retry) {
|
|
168
169
|
if (retry.failedNativeTurnId === undefined)
|
|
169
|
-
return
|
|
170
|
+
return undefined;
|
|
170
171
|
return [
|
|
171
172
|
"Yui infrastructure recovery, not new user authorization.",
|
|
172
173
|
`Continue the existing input after failed attempt ${retry.failureAttemptId}`,
|
|
173
174
|
`(native Turn ${retry.failedNativeTurnId}; failure ${retry.failureRef}).`,
|
|
174
175
|
"Read current authorized context and the original input. Inspect existing work and receipts first;",
|
|
175
|
-
"continue only unfinished work. Preserve local changes and do not repeat confirmed side effects."
|
|
176
|
-
original
|
|
176
|
+
"continue only unfinished work. Preserve local changes and do not repeat confirmed side effects."
|
|
177
177
|
].join("\n");
|
|
178
178
|
}
|
|
179
179
|
/** The same presentation is consumed by explicit detail reads and Web. */
|
|
@@ -211,7 +211,7 @@ export function providerRetryProjection(binding) {
|
|
|
211
211
|
}
|
|
212
212
|
/** Entry-point facts, never accumulated retry history or raw diagnostics.
|
|
213
213
|
* Exact Session retry/show and failureRef retain the original evidence. */
|
|
214
|
-
export function providerRetrySummary(binding) {
|
|
214
|
+
export function providerRetrySummary(binding, detailCommand) {
|
|
215
215
|
const view = providerRetryProjection(binding);
|
|
216
216
|
if (view === null)
|
|
217
217
|
return null;
|
|
@@ -224,7 +224,10 @@ export function providerRetrySummary(binding) {
|
|
|
224
224
|
currentTurn: currentTurn === null ? null : {
|
|
225
225
|
attemptId: currentTurn.attemptId, status: currentTurn.status, nativeTurnId: currentTurn.nativeTurnId
|
|
226
226
|
},
|
|
227
|
-
historyCount: history.length, detailOmitted: true
|
|
227
|
+
historyCount: history.length, detailOmitted: true,
|
|
228
|
+
// This reads current Session evidence, not a frozen Run snapshot. The
|
|
229
|
+
// returned chain/attempt identities must still be compared by the caller.
|
|
230
|
+
...(detailCommand === undefined ? {} : { currentDetailCommand: detailCommand })
|
|
228
231
|
};
|
|
229
232
|
}
|
|
230
233
|
export function controlProviderRetry(binding, action, at) {
|
|
@@ -462,7 +462,7 @@ function orderedRunTimestamp(run, value, label) {
|
|
|
462
462
|
}
|
|
463
463
|
return normalized;
|
|
464
464
|
}
|
|
465
|
-
function providerTurnIsActive(run) {
|
|
465
|
+
export function providerTurnIsActive(run) {
|
|
466
466
|
return run !== null && ["submitting", "accepted", "delivery-unknown"]
|
|
467
467
|
.includes(run.status);
|
|
468
468
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { createNativeTitleSync } from "./nativeTitleSync.js";
|
|
3
4
|
import { Duplex } from "node:stream";
|
|
4
5
|
import { nativeExecutable } from "./nativeExecutable.js";
|
|
5
6
|
import { startMissingCodexSharedDaemon } from "./codexSharedDaemon.js";
|
|
@@ -15,6 +16,7 @@ import { JsonLineChannel, PROVIDER_MESSAGE_MAX_BYTES, terminateProcessGroup } fr
|
|
|
15
16
|
import { PROVIDER_ACCEPT_TIMEOUT_MS } from "./runtimeDeadlines.js";
|
|
16
17
|
import { foldPublicReply } from "./publicReply.js";
|
|
17
18
|
import { createCodexNativeAccess } from "./codexNativeAccess.js";
|
|
19
|
+
import { isNativeContextRecovery } from "./contextRecovery.js";
|
|
18
20
|
const CODEX_PROXY_HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
19
21
|
/**
|
|
20
22
|
* These Session implementations do not expose run-configuration observations.
|
|
@@ -31,7 +33,7 @@ export async function startStructuredProviderSession(payload, input = {}) {
|
|
|
31
33
|
if (control.adapterId === "codex" && control.transport === "codex-app-server-proxy") {
|
|
32
34
|
const proxy = await connectCodexProxy(payload, mirror, true);
|
|
33
35
|
try {
|
|
34
|
-
const opened = await CodexStructuredProviderSession.open(proxy.child, proxy.exit, proxy.processInstanceId, payload, control, proxy.channel, input.onStarted, input.onTerminal, input.onGoal, input.onInput, input.onActivity,
|
|
36
|
+
const opened = await CodexStructuredProviderSession.open(proxy.child, proxy.exit, proxy.processInstanceId, payload, control, proxy.channel, input.onStarted, input.onTerminal, input.onGoal, input.onInput, input.onActivity, mirror);
|
|
35
37
|
return Object.freeze({
|
|
36
38
|
session: opened.session,
|
|
37
39
|
...(opened.recoveredTerminal === undefined
|
|
@@ -238,7 +240,10 @@ class CodexProxyWebSocketChannel {
|
|
|
238
240
|
this.#pending.delete(id);
|
|
239
241
|
reject(new Error(`Provider request timed out: ${method}.`));
|
|
240
242
|
}, PROVIDER_ACCEPT_TIMEOUT_MS);
|
|
241
|
-
this.#pending.set(id, {
|
|
243
|
+
this.#pending.set(id, {
|
|
244
|
+
resolve: resolvePromise, reject, timer, method,
|
|
245
|
+
requestBytes: Buffer.byteLength(JSON.stringify({ id, method, params }), "utf8")
|
|
246
|
+
});
|
|
242
247
|
});
|
|
243
248
|
try {
|
|
244
249
|
await this.send({ id, method, params });
|
|
@@ -282,7 +287,7 @@ class CodexProxyWebSocketChannel {
|
|
|
282
287
|
if (isBinary || encoded.byteLength > PROVIDER_MESSAGE_MAX_BYTES) {
|
|
283
288
|
this.#close(new Error(isBinary
|
|
284
289
|
? "Provider returned an unsupported binary WebSocket message."
|
|
285
|
-
:
|
|
290
|
+
: `Provider response message exceeds its bound (receivedBytes=${encoded.byteLength}).`));
|
|
286
291
|
terminateProcessGroup(this.child, "SIGTERM");
|
|
287
292
|
return;
|
|
288
293
|
}
|
|
@@ -320,6 +325,15 @@ class CodexProxyWebSocketChannel {
|
|
|
320
325
|
#close(error) {
|
|
321
326
|
if (this.#closedError !== undefined)
|
|
322
327
|
return;
|
|
328
|
+
// A rejected WebSocket frame has not reached #receive: its full size and
|
|
329
|
+
// response/notification identity are unknown. Record pending request
|
|
330
|
+
// metadata, not parameters or transcript bytes, and never blame a specific
|
|
331
|
+
// response just because that request was in flight.
|
|
332
|
+
const pendingMetadata = [...this.#pending].slice(0, 8).map(([id, pending]) => ({
|
|
333
|
+
id, method: pending.method, requestBytes: pending.requestBytes
|
|
334
|
+
}));
|
|
335
|
+
error = new Error(`Codex proxy transport closed: ${error.message}; messageLimitBytes=${PROVIDER_MESSAGE_MAX_BYTES}; `
|
|
336
|
+
+ `pendingRequests=${JSON.stringify(pendingMetadata)}; pendingCount=${this.#pending.size}.`, { cause: error });
|
|
323
337
|
this.#closedError = error;
|
|
324
338
|
if (!this.#ready)
|
|
325
339
|
this.#rejectReady(error);
|
|
@@ -384,7 +398,22 @@ class CodexStructuredProviderSession {
|
|
|
384
398
|
onActivity;
|
|
385
399
|
mirror;
|
|
386
400
|
nativeAccountHome;
|
|
401
|
+
contextEpoch = 0;
|
|
402
|
+
async checkQuiescent() {
|
|
403
|
+
const snapshot = await this.runtime.readConversation(this.conversationId, { includeTurns: false });
|
|
404
|
+
if (snapshot.threadId !== this.conversationId || snapshot.status !== "idle"
|
|
405
|
+
|| snapshot.activeTurnId !== undefined)
|
|
406
|
+
return false;
|
|
407
|
+
const backgrounds = await this.nativeAccess.read("thread/backgroundTerminals/list", {
|
|
408
|
+
threadId: this.conversationId, limit: 1
|
|
409
|
+
});
|
|
410
|
+
if (!Array.isArray(backgrounds.data) || !("nextCursor" in backgrounds)) {
|
|
411
|
+
throw new Error("Native background execution response is invalid.");
|
|
412
|
+
}
|
|
413
|
+
return backgrounds.data.length === 0 && backgrounds.nextCursor === null;
|
|
414
|
+
}
|
|
387
415
|
appliedGeneratedTitle;
|
|
416
|
+
#titleSync;
|
|
388
417
|
nativeAccess;
|
|
389
418
|
#turnAttempts = new Map();
|
|
390
419
|
adapterId = "codex";
|
|
@@ -410,7 +439,7 @@ class CodexStructuredProviderSession {
|
|
|
410
439
|
this.mirror = mirror;
|
|
411
440
|
this.nativeAccountHome = nativeAccountHome;
|
|
412
441
|
}
|
|
413
|
-
static async open(child, exit, processInstanceId, payload, control, channel, onStarted, onTerminal, onGoal, onInput, onActivity,
|
|
442
|
+
static async open(child, exit, processInstanceId, payload, control, channel, onStarted, onTerminal, onGoal, onInput, onActivity, mirror) {
|
|
414
443
|
const openingMessages = [];
|
|
415
444
|
const stopOpeningBuffer = channel.onMessage((message) => openingMessages.push(message));
|
|
416
445
|
const initialized = await channel.request("initialize", codexClientInitialization());
|
|
@@ -419,7 +448,6 @@ class CodexStructuredProviderSession {
|
|
|
419
448
|
let conversationId;
|
|
420
449
|
let nativeConfiguration;
|
|
421
450
|
let resumedActiveTurnId;
|
|
422
|
-
let resumedTurns = [];
|
|
423
451
|
if (control.mode === "new") {
|
|
424
452
|
const opened = await runtime.openConversation({
|
|
425
453
|
cwd: payload.cwd,
|
|
@@ -437,7 +465,6 @@ class CodexStructuredProviderSession {
|
|
|
437
465
|
conversationId = resumed.threadId;
|
|
438
466
|
nativeConfiguration = resumed.raw;
|
|
439
467
|
resumedActiveTurnId = resumed.activeTurnId;
|
|
440
|
-
resumedTurns = resumed.turns;
|
|
441
468
|
}
|
|
442
469
|
catch (error) {
|
|
443
470
|
if (codexAppServerErrorIsMissing(error)) {
|
|
@@ -446,37 +473,15 @@ class CodexStructuredProviderSession {
|
|
|
446
473
|
throw error;
|
|
447
474
|
}
|
|
448
475
|
}
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (control.sessionTitle !== undefined && (control.mode === "new"
|
|
456
|
-
|| (control.previousSessionTitle !== undefined && nativeTitle === control.previousSessionTitle))) {
|
|
457
|
-
try {
|
|
458
|
-
await runtime.setConversationName({
|
|
459
|
-
conversationId,
|
|
460
|
-
name: control.sessionTitle
|
|
461
|
-
});
|
|
462
|
-
appliedGeneratedTitle = control.sessionTitle;
|
|
463
|
-
}
|
|
464
|
-
catch (error) {
|
|
465
|
-
onDiagnostic?.({
|
|
466
|
-
nativeSessionId: conversationId,
|
|
467
|
-
failure: providerDeliveryFailureFrom(error, {
|
|
468
|
-
phase: "session-start",
|
|
469
|
-
inputDisposition: "not-accepted",
|
|
470
|
-
sessionDisposition: "recoverable"
|
|
471
|
-
})
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
}
|
|
476
|
+
const ownedTurn = control.kind === "restore" ? control.ownedTurn : undefined;
|
|
477
|
+
// Keep notifications buffered during paginated recovery. Otherwise a live
|
|
478
|
+
// terminal can be emitted before open returns and then returned again as a
|
|
479
|
+
// synthetic recovered terminal, or a newer start can be overwritten.
|
|
480
|
+
const recoveredOwnedTurn = ownedTurn !== undefined && resumedActiveTurnId !== ownedTurn.turnId
|
|
481
|
+
? await runtime.readTurn(conversationId, ownedTurn.turnId) : undefined;
|
|
475
482
|
const session = new CodexStructuredProviderSession(child, exit, processInstanceId, conversationId, runtime, onStarted, onTerminal, onGoal, onInput, onActivity, mirror, optionalId(initialized.codexHome));
|
|
476
483
|
session.nativeAccess = createCodexNativeAccess(conversationId, initialized, channel, openingMessages, nativeConfiguration);
|
|
477
|
-
session.appliedGeneratedTitle = appliedGeneratedTitle;
|
|
478
484
|
session.#activeTurnId = resumedActiveTurnId;
|
|
479
|
-
const ownedTurn = control.kind === "restore" ? control.ownedTurn : undefined;
|
|
480
485
|
session.#clientOwnedTurnId = ownedTurn?.turnId;
|
|
481
486
|
session.#clientOwnedAttemptId = ownedTurn?.attemptId;
|
|
482
487
|
if (ownedTurn !== undefined)
|
|
@@ -493,7 +498,7 @@ class CodexStructuredProviderSession {
|
|
|
493
498
|
});
|
|
494
499
|
if (ownedTurn !== undefined && recoveredTerminal === undefined
|
|
495
500
|
&& session.#activeTurnId !== ownedTurn.turnId) {
|
|
496
|
-
const recovered =
|
|
501
|
+
const recovered = recoveredOwnedTurn;
|
|
497
502
|
if (recovered?.status === "completed"
|
|
498
503
|
|| recovered?.status === "interrupted"
|
|
499
504
|
|| recovered?.status === "failed") {
|
|
@@ -523,6 +528,15 @@ class CodexStructuredProviderSession {
|
|
|
523
528
|
}
|
|
524
529
|
}
|
|
525
530
|
const goal = await runtime.readGoal(conversationId);
|
|
531
|
+
session.#titleSync = createNativeTitleSync(payload, conversationId, session.nativeAccountHome, result => {
|
|
532
|
+
if (result.status === "confirmed")
|
|
533
|
+
session.appliedGeneratedTitle = result.title;
|
|
534
|
+
mirror("stderr", `Native title: ${JSON.stringify(result)}\n`);
|
|
535
|
+
});
|
|
536
|
+
void exit.then(() => session.#titleSync?.close());
|
|
537
|
+
// Fresh Threads may not be persisted until their first accepted input.
|
|
538
|
+
if (control.mode === "resume")
|
|
539
|
+
session.#titleSync.notify();
|
|
526
540
|
return Object.freeze({
|
|
527
541
|
session,
|
|
528
542
|
...(recoveredTerminal === undefined ? {} : { recoveredTerminal }),
|
|
@@ -532,6 +546,8 @@ class CodexStructuredProviderSession {
|
|
|
532
546
|
#observeMessage(message, emit) {
|
|
533
547
|
const method = typeof message.method === "string" ? message.method : "";
|
|
534
548
|
const params = object(message.params) ?? {};
|
|
549
|
+
if (isNativeContextRecovery("codex", this.conversationId, message))
|
|
550
|
+
this.contextEpoch += 1;
|
|
535
551
|
const threadId = optionalId(params.threadId);
|
|
536
552
|
if (emit)
|
|
537
553
|
this.publicReply = foldPublicReply(this.publicReply, this.conversationId, method, params);
|
|
@@ -541,6 +557,13 @@ class CodexStructuredProviderSession {
|
|
|
541
557
|
}
|
|
542
558
|
return undefined;
|
|
543
559
|
}
|
|
560
|
+
if (method === "thread/name/updated") {
|
|
561
|
+
this.#titleSync?.close();
|
|
562
|
+
if (params.threadName !== this.appliedGeneratedTitle)
|
|
563
|
+
this.appliedGeneratedTitle = undefined;
|
|
564
|
+
}
|
|
565
|
+
if (method === "turn/started")
|
|
566
|
+
this.#titleSync?.notify();
|
|
544
567
|
let goal;
|
|
545
568
|
try {
|
|
546
569
|
goal = codexGoalNotification({ method, params });
|
|
@@ -665,6 +688,7 @@ class CodexStructuredProviderSession {
|
|
|
665
688
|
conversationId: this.conversationId,
|
|
666
689
|
attemptId: turn.attemptId,
|
|
667
690
|
text: turn.boundedText,
|
|
691
|
+
...(turn.additionalContext === undefined ? {} : { additionalContext: turn.additionalContext }),
|
|
668
692
|
expectedNoActiveTurn: true,
|
|
669
693
|
...(turn.requireQuiescent ? { requireQuiescent: true } : {}),
|
|
670
694
|
...(turn.expectedFailedNativeTurnId === undefined ? {} : {
|
|
@@ -684,6 +708,7 @@ class CodexStructuredProviderSession {
|
|
|
684
708
|
this.#clientOwnedTurnId = acceptance.turnId;
|
|
685
709
|
this.#clientOwnedAttemptId = turn.attemptId;
|
|
686
710
|
this.#turnAttempts.set(acceptance.turnId, turn.attemptId);
|
|
711
|
+
this.#titleSync?.notify();
|
|
687
712
|
return Object.freeze({
|
|
688
713
|
attemptId: turn.attemptId,
|
|
689
714
|
conversationId: this.conversationId,
|
|
@@ -714,6 +739,7 @@ class CodexStructuredProviderSession {
|
|
|
714
739
|
conversationId: this.conversationId,
|
|
715
740
|
expectedTurnId: nativeTurnId,
|
|
716
741
|
text: turn.boundedText,
|
|
742
|
+
...(turn.additionalContext === undefined ? {} : { additionalContext: turn.additionalContext }),
|
|
717
743
|
clientUserMessageId: turn.attemptId
|
|
718
744
|
});
|
|
719
745
|
if (acceptance.status === "unknown") {
|
|
@@ -778,6 +804,7 @@ class CodexStructuredProviderSession {
|
|
|
778
804
|
return result === "interrupted" ? "requested" : result;
|
|
779
805
|
}
|
|
780
806
|
terminate(signal) {
|
|
807
|
+
this.#titleSync?.close();
|
|
781
808
|
terminateProcessGroup(this.child, signal);
|
|
782
809
|
}
|
|
783
810
|
}
|
|
@@ -789,6 +816,7 @@ class ClaudeStructuredProviderSession {
|
|
|
789
816
|
channel;
|
|
790
817
|
onGoal;
|
|
791
818
|
mirror;
|
|
819
|
+
contextEpoch = 0;
|
|
792
820
|
adapterId = "claude";
|
|
793
821
|
get ownedProcessId() { return this.child.pid; }
|
|
794
822
|
#activeAttemptId;
|
|
@@ -905,6 +933,8 @@ class ClaudeStructuredProviderSession {
|
|
|
905
933
|
terminateProcessGroup(this.child, signal);
|
|
906
934
|
}
|
|
907
935
|
#receive(message, onTerminal, onAccepted, onActivity) {
|
|
936
|
+
if (isNativeContextRecovery("claude", this.conversationId, message))
|
|
937
|
+
this.contextEpoch += 1;
|
|
908
938
|
const goal = claudeActiveGoal(message, this.conversationId);
|
|
909
939
|
if (goal !== undefined) {
|
|
910
940
|
const key = JSON.stringify(goal === null ? null : {
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
2
2
|
/** The caller's TaskStore transaction owns this read/merge/write operation. */
|
|
3
3
|
export function queueLeaderWakeup(store, taskId, reason, now) {
|
|
4
4
|
if (store.getTask(taskId)?.status === "archived")
|
|
5
5
|
return null;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// The scheduler's pending-wakeup projection may expose an already-owned
|
|
7
|
+
// processing prefix. Append to the actual suffix, never write that read
|
|
8
|
+
// projection back over independent inputs.
|
|
9
|
+
const pending = enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, reason, now).pending;
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1, taskId, reasons: [...pending.reasons],
|
|
12
|
+
requestCount: pending.requestCount,
|
|
13
|
+
firstRequestedAt: pending.firstQueuedAt, lastRequestedAt: pending.lastQueuedAt
|
|
14
|
+
};
|
|
9
15
|
}
|
|
@@ -286,6 +286,35 @@ const MINOR_UPGRADES = Object.freeze([{
|
|
|
286
286
|
// Optional Role presentation and generated-title provenance. Never infer
|
|
287
287
|
// ownership of historical titles, rewrite frozen profiles, or fill defaults.
|
|
288
288
|
sql: ""
|
|
289
|
+
}, {
|
|
290
|
+
fromVersion: "1.25",
|
|
291
|
+
toVersion: "1.26",
|
|
292
|
+
name: "codex-workspace-temp-exclusions",
|
|
293
|
+
introducedIn: "next",
|
|
294
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
295
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
296
|
+
// Optional workspace-write permission fields; preserve all existing payloads.
|
|
297
|
+
sql: ""
|
|
298
|
+
}, {
|
|
299
|
+
fromVersion: "1.26",
|
|
300
|
+
toVersion: "1.27",
|
|
301
|
+
name: "host-upgrade-lifecycle-signals",
|
|
302
|
+
introducedIn: "next",
|
|
303
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
304
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
305
|
+
// New versioned signal sources in the existing role-runtime mailbox. No
|
|
306
|
+
// historical requests are inferred and existing records remain unchanged.
|
|
307
|
+
sql: ""
|
|
308
|
+
}, {
|
|
309
|
+
fromVersion: "1.27",
|
|
310
|
+
toVersion: "1.28",
|
|
311
|
+
name: "native-title-effect-receipts",
|
|
312
|
+
introducedIn: "next",
|
|
313
|
+
sourceChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
314
|
+
targetChecksum: CURRENT_SCHEMA_CHECKSUM,
|
|
315
|
+
// Additive exact-identity intent/confirmation files under runtime/native-titles.
|
|
316
|
+
// Never invent native ownership or replay history during migration.
|
|
317
|
+
sql: ""
|
|
289
318
|
}]);
|
|
290
319
|
export function storageMinorUpgradePlan(from) {
|
|
291
320
|
if (from === CURRENT_STORAGE_VERSION)
|
package/dist/tmux/tmuxManager.js
CHANGED
|
@@ -62,7 +62,9 @@ export class TmuxManager {
|
|
|
62
62
|
this.#yuiHome = options.yuiHome ?? process.env.YUI_HOME ?? process.cwd();
|
|
63
63
|
this.#serverName = yuiTmuxServerName(this.#yuiHome);
|
|
64
64
|
this.#onRoleTargetRecorded = options.onRoleTargetRecorded;
|
|
65
|
-
|
|
65
|
+
// Creating Node's stdin stream can make piped fd 0 nonblocking. Commands
|
|
66
|
+
// may still need to read that pipe synchronously before any attach.
|
|
67
|
+
this.#terminalInput = options.terminalInput;
|
|
66
68
|
this.#terminalType = usableInteractiveTerminal(options.terminalType ?? process.env.TERM);
|
|
67
69
|
this.#closeInteractiveInput = options.closeInteractiveInput ?? (() => { });
|
|
68
70
|
this.#readinessTimeoutMs = positiveInteger(options.readinessTimeoutMs, DEFAULT_READINESS_TIMEOUT_MS, "readiness timeout");
|
|
@@ -228,7 +230,7 @@ export class TmuxManager {
|
|
|
228
230
|
}
|
|
229
231
|
if (resolvedAccess === "read-write")
|
|
230
232
|
options.revalidateWritableAttach?.();
|
|
231
|
-
handoffTerminal(this.#terminalInput, this.#closeInteractiveInput);
|
|
233
|
+
handoffTerminal(this.#terminalInput ?? process.stdin, this.#closeInteractiveInput);
|
|
232
234
|
this.run([
|
|
233
235
|
"attach-session",
|
|
234
236
|
...(resolvedAccess === "read-only" ? ["-r"] : []),
|
package/dist/web/webSettings.js
CHANGED
|
@@ -276,6 +276,8 @@ const ROLE_FIELDS = {
|
|
|
276
276
|
model: { set: "--model", clear: "--clear-model" }, effort: { set: "--effort", clear: "--clear-effort" },
|
|
277
277
|
"permission.strategy": { set: "--permission-strategy" }, "permission.sandbox": { set: "--sandbox" },
|
|
278
278
|
"permission.approval": { set: "--approval" }, "permission.mode": { set: "--permission-mode" },
|
|
279
|
+
"permission.excludeTmpdirEnvVar": { set: "--exclude-tmpdir-env-var", clear: "--clear-exclude-tmpdir-env-var" },
|
|
280
|
+
"permission.excludeSlashTmp": { set: "--exclude-slash-tmp", clear: "--clear-exclude-slash-tmp" },
|
|
279
281
|
"permission.allowedTools": { set: "--allowed-tool", clear: "--clear-allowed-tools", many: true },
|
|
280
282
|
"permission.disallowedTools": { set: "--disallowed-tool", clear: "--clear-disallowed-tools", many: true },
|
|
281
283
|
search: { set: "--search", clear: "--clear-search" },
|
package/docs/authorization.md
CHANGED
|
@@ -20,9 +20,24 @@ authorization for any external effect.
|
|
|
20
20
|
| May it access an additional resource or irreversible effect? | `CapabilityGrant`, `grant/taskAuthorization.ts`, resource/plugin/release consumers | Task activation does not authorize arbitrary resources |
|
|
21
21
|
|
|
22
22
|
The full command inventory is generated from the catalog, not maintained as a
|
|
23
|
-
second per-command permissions table
|
|
24
|
-
`yui --json help task
|
|
25
|
-
|
|
23
|
+
second per-command permissions table. `yui --json help` and scoped
|
|
24
|
+
`yui --json help task` return the selected command's full details and all its
|
|
25
|
+
immediate child **summaries**, not a recursively expanded tree. The `discovery`
|
|
26
|
+
field labels this boundary and gives the detail and full-tree commands. Follow
|
|
27
|
+
each child's `path` with `yui --json help <command ...>` for full usage, options,
|
|
28
|
+
values and examples, or use `yui --json help [command ...] --recursive` for the
|
|
29
|
+
complete caller-filtered tree in one call.
|
|
30
|
+
|
|
31
|
+
This changes the default JSON help shape: consumers that previously traversed
|
|
32
|
+
nested `children` must request `--recursive`; absence of child details in the
|
|
33
|
+
default view is not proof that the child has no options or descendants.
|
|
34
|
+
Focused leaf details are unchanged, with additive discovery metadata.
|
|
35
|
+
Text help still shows immediate children; `--recursive` requires `--json`.
|
|
36
|
+
`config describe --json` and the offline
|
|
37
|
+
`describeCommandTree(discoveryCommandTree(env))` API retain full recursive
|
|
38
|
+
descriptions. Native `yui agent` product help is a separate surface and unchanged.
|
|
39
|
+
|
|
40
|
+
Complete role hints retain an offline template even without
|
|
26
41
|
a running Home; that is not authentication. Native-ID-only callers are resolved
|
|
27
42
|
from read-only local storage for help/completion; if that read fails the CLI shows
|
|
28
43
|
the restricted `unbound` template. No Controller or provider is started for that
|