@zq-silk/yui 1.1.2 → 2.1.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/ARCHITECTURE.md +2 -1
- package/ARCHITECTURE.zh-CN.md +2 -1
- package/README.md +2 -0
- package/dist/cli/commandCatalog.js +344 -324
- package/dist/cli/commandDiscovery.js +69 -0
- package/dist/cli/completion.js +22 -227
- package/dist/cli/dynamicCompletion.js +12 -6
- package/dist/cli/invocationAuthority.js +2 -2
- package/dist/cli/invocationRouter.js +18 -7
- package/dist/cli/managedDiagnostics.js +2 -1
- package/dist/cli/updatePorts.js +0 -5
- package/dist/cli.js +184 -130
- package/dist/commands/capabilityCommands.js +5 -5
- package/dist/commands/controllerCommands.js +2 -1
- package/dist/commands/globalRoleCommands.js +86 -44
- package/dist/commands/grantCommands.js +48 -8
- package/dist/commands/taskCommands.js +157 -196
- package/dist/commands/taskContextCommand.js +29 -6
- package/dist/commands/taskFactCommands.js +10 -69
- package/dist/commands/taskInputCommands.js +26 -35
- package/dist/commands/taskPublicationCommands.js +2 -35
- package/dist/commands/taskPublicationVerifyCommand.js +1 -2
- package/dist/commands/taskUpstreamCommands.js +2 -1
- package/dist/context/runContextPack.js +8 -29
- package/dist/context/sessionBootstrapManifest.js +5 -101
- package/dist/context/taskContext.js +104 -41
- package/dist/controller/clientRuntime.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +24 -0
- package/dist/controller/runtime.js +6 -0
- package/dist/core/controllerClient.js +46 -21
- package/dist/core/protocol.js +12 -7
- package/dist/doctor/doctor.js +4 -3
- package/dist/errors/cliError.js +4 -4
- package/dist/errors/cliFailure.js +213 -0
- package/dist/executor/fileRoleLaunchPlanner.js +1 -4
- package/dist/grant/capabilityGrant.js +13 -0
- package/dist/grant/taskAuthorization.js +127 -0
- package/dist/kernel/accessAssessment.js +1 -0
- package/dist/kernel/builtinCapabilities.js +88 -9
- package/dist/kernel/capabilityRegistry.js +64 -19
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/nativeAgent/agent.js +173 -0
- package/dist/nativeAgent/contracts.js +1 -0
- package/dist/nativeAgent/demo.js +29 -0
- package/dist/nativeAgent/index.js +3 -0
- package/dist/nativeAgent/mockProvider.js +43 -0
- package/dist/nativeAgent/textTools.js +156 -0
- package/dist/nativeAgent/validation.js +95 -0
- package/dist/output/boundedRead.js +118 -0
- package/dist/plugins/pluginService.js +98 -8
- package/dist/release/releaseWorkflowEngine.js +8 -1
- package/dist/resources/projectResourceService.js +23 -1
- package/dist/runtime/managedCaller.js +2 -2
- package/dist/runtime/managedIdentity.js +6 -0
- package/dist/runtime/runtimeCoherence.js +10 -8
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +3 -0
- package/dist/surface/surfaceContributions.js +3 -0
- package/dist/task/leaderArchive.js +111 -0
- package/dist/task/leaderArchiveAuthority.js +24 -0
- package/dist/task/taskAuthority.js +5 -10
- package/dist/tmux/commandExecutor.js +7 -5
- package/dist/web/assets/assetManifest.js +38 -22
- package/dist/web/assets/client/api.js +115 -0
- package/dist/web/assets/client/app.js +555 -863
- package/dist/web/assets/client/components.js +323 -897
- package/dist/web/assets/client/detail.js +333 -0
- package/dist/web/assets/client/dock.js +225 -0
- package/dist/web/assets/client/dom.js +55 -3
- package/dist/web/assets/client/evidence.js +172 -0
- package/dist/web/assets/client/format.js +25 -14
- package/dist/web/assets/client/forms.js +230 -0
- package/dist/web/assets/client/i18n.js +1020 -791
- package/dist/web/assets/client/overview.js +115 -0
- package/dist/web/assets/client/records.js +63 -0
- package/dist/web/assets/client/sections.js +313 -0
- package/dist/web/assets/client/sidebar.js +117 -0
- package/dist/web/assets/client/theme.js +61 -22
- package/dist/web/assets/icons.js +39 -0
- package/dist/web/assets/shell.js +124 -118
- package/dist/web/assets/styles/base.js +41 -0
- package/dist/web/assets/styles/components.js +167 -0
- package/dist/web/assets/styles/layout.js +49 -77
- package/dist/web/assets/styles/markdown.js +16 -24
- package/dist/web/assets/styles/responsive.js +33 -44
- package/dist/web/assets/styles/tokens.js +65 -89
- package/dist/web/assets/styles/views.js +289 -0
- package/dist/web/webServer.js +2 -1
- package/docs/architecture/README.md +1 -0
- package/docs/architecture/README.zh-CN.md +1 -0
- package/docs/cli-information-contract.md +107 -0
- package/docs/cli-information-contract.zh-CN.md +81 -0
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
- package/docs/plugin-sdk.md +7 -3
- package/docs/plugin-sdk.zh-CN.md +5 -2
- package/docs/release-workflow.md +42 -9
- package/docs/release-workflow.zh-CN.md +30 -7
- package/docs/storage-baseline.md +5 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -1
- package/skills/yui-leader/references/authorization.md +64 -0
- package/skills/yui-leader/references/execution.md +18 -4
- package/skills/yui-leader/references/task-plugins.md +4 -2
- package/skills/yui-operator/SKILL.md +9 -2
- package/skills/yui-runtime/SKILL.md +77 -9
- package/skills/yui-runtime/references/publication.md +3 -1
- package/dist/web/assets/client/taskSummary.js +0 -350
- package/dist/web/assets/client/taskSurface.js +0 -616
- package/dist/web/assets/client/view.js +0 -608
- package/dist/web/assets/styles/cards.js +0 -247
- package/dist/web/assets/styles/widgets.js +0 -168
|
@@ -3,16 +3,18 @@ import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArt
|
|
|
3
3
|
import { taskNotFound, usageError } from "../errors/cliError.js";
|
|
4
4
|
import { expandTaskMessageResult } from "../message/message.js";
|
|
5
5
|
import { resolveManagedTaskReader } from "../runtime/managedCaller.js";
|
|
6
|
+
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
6
7
|
import { providerRetryProjection } from "../runtime/providerRetry.js";
|
|
7
8
|
import { projectTaskRemoteDeliveryFromStore } from "../task/remoteDeliveryService.js";
|
|
9
|
+
import { retiredTaskRecordIds, TASK_RECORD_RETIRED_EVENT } from "../task/taskRecordRetirement.js";
|
|
8
10
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
9
11
|
import { contextContentDigest } from "./contextSnapshot.js";
|
|
10
12
|
import { buildRunContextPack } from "./runContextPack.js";
|
|
11
13
|
import { sourceRunContextValue } from "./sourceRunContext.js";
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
import { boundedDocument, recordPage } from "../output/boundedRead.js";
|
|
15
|
+
const MAX_RECORDS = 64;
|
|
16
|
+
const MAX_VALUE_BYTES = 2048;
|
|
17
|
+
const MAX_PAGE_BYTES = 24 * 1024;
|
|
16
18
|
const MAX_EVENTS = 100;
|
|
17
19
|
const MAX_ATTENTION_REFS = 8;
|
|
18
20
|
/** A current read model, not a second snapshot store or a delivery receipt.
|
|
@@ -21,7 +23,7 @@ const MAX_ATTENTION_REFS = 8;
|
|
|
21
23
|
export function readTaskContext(store, taskId, environment = {}) {
|
|
22
24
|
return store.readTransaction((reader) => {
|
|
23
25
|
const scope = authorizeContext(reader, taskId, environment);
|
|
24
|
-
const sources = authorizedSources(reader, scope);
|
|
26
|
+
const sources = authorizedSources(reader, scope, true);
|
|
25
27
|
const count = sources.reduce((total, source) => total + source.count, 0);
|
|
26
28
|
const cursor = currentCursor(reader, taskId);
|
|
27
29
|
// Reserve a small, classified view before ordinary history consumes the
|
|
@@ -34,10 +36,12 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
34
36
|
for (const entry of source.read()) {
|
|
35
37
|
if (records.length >= MAX_RECORDS)
|
|
36
38
|
break page;
|
|
39
|
+
if (records.filter(record => record.ref.store === source.family).length >= 8)
|
|
40
|
+
continue;
|
|
37
41
|
if (entry.ref.store === "run")
|
|
38
42
|
runtimeEvents ??= reader.listEventsByType(taskId, ["runtime.agent-error"]);
|
|
39
43
|
const valueBytes = Buffer.byteLength(JSON.stringify(entry.value));
|
|
40
|
-
const content = valueBytes > MAX_VALUE_BYTES
|
|
44
|
+
const content = valueBytes > MAX_VALUE_BYTES || ["task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
|
|
41
45
|
? { ref: entry.ref, summary: summarize(entry.value), omitted: true }
|
|
42
46
|
: { ...entry, omitted: false };
|
|
43
47
|
const record = { ...content, ...(entry.ref.store === "run" ? {
|
|
@@ -53,11 +57,24 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
53
57
|
}
|
|
54
58
|
return {
|
|
55
59
|
taskId, coreCursor: encode(cursor), throughCursor: encode(cursor),
|
|
60
|
+
view: "current",
|
|
61
|
+
collections: Object.entries(sources.reduce((counts, source) => {
|
|
62
|
+
counts[source.family] = (counts[source.family] ?? 0) + source.count;
|
|
63
|
+
return counts;
|
|
64
|
+
}, {})).map(([store, total]) => ({
|
|
65
|
+
store, total, returned: records.filter(record => record.ref.store === store).length
|
|
66
|
+
})),
|
|
67
|
+
discovery: `task context list ${taskId} --store <store>`,
|
|
68
|
+
commandDiscovery: {
|
|
69
|
+
help: "yui help", describe: "yui --json help",
|
|
70
|
+
note: "Help and completion share the current caller's offline command view. Follow that view for operation-specific discovery; execution always revalidates authority."
|
|
71
|
+
},
|
|
72
|
+
detail: `task context inspect ${taskId} --store <store> --ref <refId> --digest <digest>`,
|
|
56
73
|
attention,
|
|
57
74
|
records, count,
|
|
58
75
|
omitted: { records: count - records.length, values: records.filter((r) => r.omitted).length },
|
|
59
76
|
observations: [],
|
|
60
|
-
limits: { records: MAX_RECORDS, valueBytes: MAX_VALUE_BYTES, pageBytes: MAX_PAGE_BYTES,
|
|
77
|
+
limits: { records: MAX_RECORDS, valueBytes: MAX_VALUE_BYTES, pageBytes: 32 * 1024, recordBytes: MAX_PAGE_BYTES,
|
|
61
78
|
attentionRefsPerCategory: MAX_ATTENTION_REFS, attentionRefBytesPerCategory: MAX_VALUE_BYTES }
|
|
62
79
|
};
|
|
63
80
|
});
|
|
@@ -65,15 +82,16 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
65
82
|
function summarizeAttention(store, taskId, allow) {
|
|
66
83
|
const group = () => ({ count: 0, refs: [], omittedRefs: 0 });
|
|
67
84
|
const attention = {
|
|
68
|
-
openInputs: group(), pendingOperations: group(), unknownOperations: group()
|
|
85
|
+
openInputs: group(), pendingOperations: group(), unknownOperations: group(), messages: group()
|
|
69
86
|
};
|
|
70
87
|
for (const [category, family, statuses] of [
|
|
71
88
|
["openInputs", "input-request", ["open"]],
|
|
72
89
|
["pendingOperations", "job", ["queued", "running"]],
|
|
73
|
-
["unknownOperations", "job", ["unknown-needs-attention"]]
|
|
90
|
+
["unknownOperations", "job", ["unknown-needs-attention"]],
|
|
91
|
+
["messages", "task-message", undefined]
|
|
74
92
|
]) {
|
|
75
93
|
const page = store.queryContextRecords(taskId, {
|
|
76
|
-
family, statuses, ids: allowedIds(allow, family), limit: MAX_ATTENTION_REFS
|
|
94
|
+
family, statuses, descending: category === "messages", ids: allowedIds(allow, family), limit: MAX_ATTENTION_REFS
|
|
77
95
|
});
|
|
78
96
|
const summary = attention[category];
|
|
79
97
|
summary.count = page.count;
|
|
@@ -152,22 +170,58 @@ export function inspectTaskContext(store, taskId, selector, environment = {}) {
|
|
|
152
170
|
currentRef: entry.ref
|
|
153
171
|
});
|
|
154
172
|
}
|
|
155
|
-
if (Buffer.byteLength(JSON.stringify(entry.value)) > MAX_INSPECT_BYTES) {
|
|
156
|
-
throw usageError("Context value exceeds the bounded inspect limit.", undefined, { ref: entry.ref, maxBytes: MAX_INSPECT_BYTES });
|
|
157
|
-
}
|
|
158
173
|
const expansion = selector.store === "task-message"
|
|
159
174
|
? expandTaskMessageResult(value, (task, run) => reader.getRun(task, run))
|
|
160
175
|
: undefined;
|
|
161
176
|
const response = { ...entry, ...(expansion !== undefined && "result" in expansion
|
|
162
177
|
? { result: expansion.result } : {}),
|
|
163
178
|
...(selector.store === "run" ? { execution: runExecutionObservation(value, reader.getTaskRoleSessionSet(taskId, value.roleName)?.providerBinding, reader.listEventsByType(taskId, ["runtime.agent-error"])) } : {}),
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
};
|
|
180
|
+
return boundedDocument(response, `task:${taskId}/${selector.store}/${selector.refId}`, selector.cursor);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** Discover one authorized family without copying its bodies or unrelated Task
|
|
184
|
+
* state. Content-version cursors fail explicitly if the collection changes.
|
|
185
|
+
*/
|
|
186
|
+
export function listTaskContext(store, taskId, family, options = {}, environment = {}) {
|
|
187
|
+
return store.readTransaction(reader => {
|
|
188
|
+
const scope = authorizeContext(reader, taskId, environment);
|
|
189
|
+
const sources = authorizedSources(reader, scope, false, family);
|
|
190
|
+
if (!CONTEXT_FAMILIES.includes(family))
|
|
191
|
+
throw usageError(`Unknown Context store: ${family}.`);
|
|
192
|
+
if (options.after !== undefined && !Number.isFinite(Date.parse(options.after)))
|
|
193
|
+
throw usageError("Invalid --after timestamp.");
|
|
194
|
+
const retiredKind = family === "task-message" ? "message" : family === "run" ? "run" : undefined;
|
|
195
|
+
const retired = retiredKind === undefined ? undefined
|
|
196
|
+
: retiredTaskRecordIds(reader.listEventsByType(taskId, [TASK_RECORD_RETIRED_EVENT]), retiredKind);
|
|
197
|
+
const records = [];
|
|
198
|
+
for (const source of sources) {
|
|
199
|
+
for (let offset = 0; offset < source.count; offset += MAX_RECORDS) {
|
|
200
|
+
for (const entry of source.read(offset)) {
|
|
201
|
+
const value = entry.value;
|
|
202
|
+
if (options.status !== undefined && value.status !== options.status
|
|
203
|
+
|| options.workItemId !== undefined && value.workItemId !== options.workItemId
|
|
204
|
+
|| options.after !== undefined && Date.parse(String(value.createdAt)) <= Date.parse(options.after))
|
|
205
|
+
continue;
|
|
206
|
+
records.push({ ref: entry.ref, summary: summarize(value),
|
|
207
|
+
...(retired === undefined ? {} : { history: retired.has(entry.ref.refId) ? "retired" : "active" }),
|
|
208
|
+
...Object.fromEntries(["status", "kind", "type", "createdAt", "roleName", "assignee", "purpose", "workItemId", "reviewRoundId"]
|
|
209
|
+
.filter(key => typeof value[key] === "string").map(key => [key, value[key]])) });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
167
212
|
}
|
|
168
|
-
return
|
|
213
|
+
return recordPage(records, `task:${taskId}/${family}/${JSON.stringify({
|
|
214
|
+
status: options.status, after: options.after, workItemId: options.workItemId
|
|
215
|
+
})}`, options);
|
|
169
216
|
});
|
|
170
217
|
}
|
|
218
|
+
export const CONTEXT_FAMILIES = [
|
|
219
|
+
"task", "task-brief", "remote-delivery", "role", "role-profile",
|
|
220
|
+
"project-policy", "project-knowledge", "input-request", "work-item",
|
|
221
|
+
"accepted-work-item", "candidate", "environment-preparation", "managed-workspace",
|
|
222
|
+
"change-set", "task-message", "run", "source-run", "review-round", "mailbox",
|
|
223
|
+
"task-decision", "task-milestone", "job", "publication", "task-event"
|
|
224
|
+
];
|
|
171
225
|
export function listContextMessages(store, taskId, environment = {}) {
|
|
172
226
|
return store.readTransaction((reader) => {
|
|
173
227
|
const { allow } = authorizeContext(reader, taskId, environment);
|
|
@@ -220,8 +274,7 @@ export function resolveContextReader(store, environment) {
|
|
|
220
274
|
throw usageError("Only Operator may read Task context from a global Session.");
|
|
221
275
|
}
|
|
222
276
|
if (caller === undefined && environment.YUI_SESSION_SCOPE === undefined
|
|
223
|
-
&& (environment
|
|
224
|
-
|| environment.YUI_TASK_ID !== undefined || environment.YUI_AGENT_ID !== undefined)) {
|
|
277
|
+
&& hasManagedIdentity(environment)) {
|
|
225
278
|
throw usageError("Incomplete managed Context caller identity.");
|
|
226
279
|
}
|
|
227
280
|
if (environment.YUI_SESSION_SCOPE !== undefined && !["task", "global"].includes(environment.YUI_SESSION_SCOPE)) {
|
|
@@ -241,7 +294,7 @@ function authorizeContext(store, taskId, environment) {
|
|
|
241
294
|
if (caller.currentRunId === undefined)
|
|
242
295
|
throw usageError("A managed Role needs a current AgentRun to read its scoped Context.");
|
|
243
296
|
const pack = buildRunContextPack(store, taskId, caller.currentRunId);
|
|
244
|
-
allow = new Set(pack.
|
|
297
|
+
allow = new Set(pack.pointers.map((ref) => `${ref.store}:${ref.refId}`));
|
|
245
298
|
allow.add(`role:${caller.roleName}`);
|
|
246
299
|
allow.add(`turn:${caller.currentRunId}`);
|
|
247
300
|
// Shared current user intent and this exact Assignment's live steers augment
|
|
@@ -389,31 +442,35 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
|
|
|
389
442
|
default: return null;
|
|
390
443
|
}
|
|
391
444
|
}
|
|
392
|
-
function authorizedSources(store, { task, allow, caller }) {
|
|
445
|
+
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily) {
|
|
393
446
|
const taskId = task.id;
|
|
394
447
|
const sources = [];
|
|
395
448
|
const add = (family, id, value) => {
|
|
396
|
-
if (value !== null && isAllowed(allow, family, id)) {
|
|
397
|
-
sources.push({ count: 1, read: () => [materialize(family, id, value)] });
|
|
449
|
+
if ((requestedFamily === undefined || requestedFamily === family) && value !== null && isAllowed(allow, family, id)) {
|
|
450
|
+
sources.push({ family, count: 1, read: () => [materialize(family, id, value)] });
|
|
398
451
|
}
|
|
399
452
|
};
|
|
400
453
|
const query = (family, options = {}) => {
|
|
454
|
+
if (requestedFamily !== undefined && requestedFamily !== family)
|
|
455
|
+
return;
|
|
401
456
|
const selection = { ...options, family, ids: allowedIds(allow, family) };
|
|
402
457
|
const { count } = store.queryContextRecords(taskId, { ...selection, limit: 0 });
|
|
403
|
-
sources.push({ count, *read() {
|
|
458
|
+
sources.push({ family, count, *read(offset = 0) {
|
|
404
459
|
for (const { id, value } of store.queryContextRecords(taskId, {
|
|
405
|
-
...selection, limit: MAX_RECORDS
|
|
460
|
+
...selection, offset, limit: current ? 8 : MAX_RECORDS
|
|
406
461
|
}).records)
|
|
407
462
|
yield materialize(family, id, value);
|
|
408
463
|
} });
|
|
409
464
|
};
|
|
410
465
|
add("task", taskId, task);
|
|
411
466
|
add("task-brief", taskId, store.getTaskBrief(taskId));
|
|
412
|
-
if (allow === undefined)
|
|
467
|
+
if (allow === undefined && (requestedFamily === undefined || requestedFamily === "remote-delivery")) {
|
|
413
468
|
add("remote-delivery", taskId, projectTaskRemoteDeliveryFromStore(store, task));
|
|
469
|
+
}
|
|
414
470
|
for (const role of store.listRoles(taskId)) {
|
|
415
471
|
add("role", role.name, role);
|
|
416
|
-
|
|
472
|
+
if (!current)
|
|
473
|
+
add("role-profile", role.name, roleProfile(role));
|
|
417
474
|
}
|
|
418
475
|
for (const binding of task.projectBindings) {
|
|
419
476
|
const project = store.getProject(binding.projectId);
|
|
@@ -427,42 +484,48 @@ function authorizedSources(store, { task, allow, caller }) {
|
|
|
427
484
|
}
|
|
428
485
|
// Current responsibilities and unanswered inputs precede historical bulk.
|
|
429
486
|
query("input-request", { statuses: ["open"] });
|
|
430
|
-
query("work-item");
|
|
487
|
+
query("work-item", current ? { excludeStatuses: ["accepted", "retired"] } : {});
|
|
431
488
|
for (const id of allowedIds(allow, "accepted-work-item") ?? []) {
|
|
432
489
|
add("accepted-work-item", id, store.getWorkItem(taskId, id));
|
|
433
490
|
}
|
|
434
|
-
|
|
491
|
+
if (!current)
|
|
492
|
+
query("candidate");
|
|
435
493
|
// File artifacts are not enumerated into the synchronous Context listing: the
|
|
436
494
|
// core cursor does not cover file edits and an ambient directory index here
|
|
437
495
|
// would be the forbidden update-time mirror (§3.7). Current artifacts are
|
|
438
496
|
// listed on demand via the async `artifact.list` capability; frozen Candidate
|
|
439
497
|
// artifacts remain reachable as commit-pinned pointers under their Candidate.
|
|
440
|
-
|
|
498
|
+
if (!current)
|
|
499
|
+
query("environment-preparation");
|
|
441
500
|
query("managed-workspace");
|
|
442
501
|
if (caller?.currentRunId !== undefined) {
|
|
443
502
|
add("managed-workspace", `${taskId}/${caller.roleName}`, store.getRun(taskId, caller.currentRunId)?.workspace ?? null);
|
|
444
503
|
}
|
|
445
|
-
|
|
504
|
+
if (!current)
|
|
505
|
+
query("change-set");
|
|
446
506
|
query("task-message", { descending: true });
|
|
447
|
-
query("run", { descending: true });
|
|
507
|
+
query("run", { descending: true, ...(current ? { statuses: ["active"] } : {}) });
|
|
448
508
|
for (const id of allowedIds(allow, "source-turn") ?? []) {
|
|
449
509
|
const run = store.getRun(taskId, id);
|
|
450
510
|
if (run !== null)
|
|
451
511
|
add("source-run", id, sourceRunContextValue(run));
|
|
452
512
|
}
|
|
453
|
-
query("review-round", { descending: true });
|
|
454
|
-
|
|
455
|
-
|
|
513
|
+
query("review-round", { descending: true, ...(current ? { statuses: ["pending", "running"] } : {}) });
|
|
514
|
+
if (!current)
|
|
515
|
+
query("input-request", { excludeStatuses: ["open"] });
|
|
516
|
+
if (!current && allow === undefined) {
|
|
456
517
|
add("mailbox", "task", store.getWorkMailbox({ kind: "task", taskId }));
|
|
457
518
|
for (const role of store.listRoles(taskId)) {
|
|
458
519
|
add("mailbox", `role:${role.name}`, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }));
|
|
459
520
|
}
|
|
460
521
|
}
|
|
461
|
-
query("task-decision");
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
522
|
+
query("task-decision", current ? { statuses: ["active"] } : {});
|
|
523
|
+
if (!current) {
|
|
524
|
+
query("task-milestone");
|
|
525
|
+
query("publication");
|
|
526
|
+
query("task-event", { descending: true });
|
|
527
|
+
}
|
|
528
|
+
query("job", current ? { statuses: ["queued", "running", "unknown-needs-attention"] } : {});
|
|
466
529
|
return sources;
|
|
467
530
|
}
|
|
468
531
|
export function materialize(store, refId, value) {
|
|
@@ -512,7 +575,7 @@ function freeze(value) {
|
|
|
512
575
|
}
|
|
513
576
|
function summarize(value) {
|
|
514
577
|
const record = value;
|
|
515
|
-
return ["title", "objective", "summary", "body", "status"]
|
|
578
|
+
return ["title", "objective", "question", "summary", "body", "status", "type"]
|
|
516
579
|
.flatMap((key) => typeof record[key] === "string" ? [`${key}: ${record[key]}`] : [])
|
|
517
580
|
.join("; ").slice(0, 400);
|
|
518
581
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { describeCliFailure } from "../errors/cliFailure.js";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { findLiveControllerProcessForHome, inspectLiveControllerProcess } from "../core/controllerProcessIdentity.js";
|
|
4
5
|
import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
|
|
@@ -77,7 +78,7 @@ export async function ensureFileTaskController(home, options = {}) {
|
|
|
77
78
|
? findLiveControllerProcessForHome(readHomeFilesystemId(home))
|
|
78
79
|
: undefined;
|
|
79
80
|
const startupPid = existing?.pid
|
|
80
|
-
?? spawnController(home, controllerSpawnEnvironment(home, options.environment ?? process.env));
|
|
81
|
+
?? await spawnController(home, controllerSpawnEnvironment(home, options.environment ?? process.env));
|
|
81
82
|
const deadline = Date.now() + timeoutMs;
|
|
82
83
|
for (;;) {
|
|
83
84
|
try {
|
|
@@ -129,15 +130,15 @@ function assertControllerRuntimeProcessIdentity(value, expected) {
|
|
|
129
130
|
|| actual.controllerProtocolVersion !== expected.controllerProtocolVersion
|
|
130
131
|
|| actual.storageVersion !== expected.storageVersion
|
|
131
132
|
|| actual.minimumStorageVersion !== expected.minimumStorageVersion) {
|
|
132
|
-
throw new
|
|
133
|
+
throw new ControllerClientError("CONTROLLER_IDENTITY_MISMATCH", "Authenticated Controller runtime identity does not match the captured executable, argv, package, protocol and storage versions; refusing readiness.");
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
function assertCompatibleControllerStatus(status, expectedIdentity) {
|
|
136
137
|
const statusRecord = isJsonRecord(status) && status.running === true ? status : null;
|
|
137
138
|
const actual = statusRecord?.protocolVersion;
|
|
138
139
|
if (statusRecord === null || actual !== FILE_TASK_CONTROLLER_PROTOCOL_VERSION) {
|
|
139
|
-
throw new
|
|
140
|
-
+ "
|
|
140
|
+
throw new ControllerClientError("CONTROLLER_PROTOCOL_MISMATCH", `Controller protocol is incompatible (expected ${FILE_TASK_CONTROLLER_PROTOCOL_VERSION}, found ${typeof actual === "number" ? actual : "unknown"}). `
|
|
141
|
+
+ "Verify Home, CLI and Controller identity before an authorized restart.");
|
|
141
142
|
}
|
|
142
143
|
const actualVersion = statusRecord.version;
|
|
143
144
|
const current = yuiVersionIdentity();
|
|
@@ -146,14 +147,21 @@ function assertCompatibleControllerStatus(status, expectedIdentity) {
|
|
|
146
147
|
version: typeof actualVersion === "string" ? actualVersion : current.version
|
|
147
148
|
} : parseControllerIdentity(expectedIdentity));
|
|
148
149
|
}
|
|
149
|
-
function spawnDetachedFileTaskController(_home, environment) {
|
|
150
|
+
async function spawnDetachedFileTaskController(_home, environment) {
|
|
150
151
|
const child = spawn(process.execPath, [fileURLToPath(new URL("./controllerMain.js", import.meta.url))], {
|
|
151
152
|
env: environment,
|
|
152
153
|
detached: true,
|
|
153
154
|
stdio: "ignore"
|
|
154
155
|
});
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
// Route asynchronous spawn failures back through the command's normal error
|
|
157
|
+
// boundary instead of Node's unhandled "error" event / raw stack output.
|
|
158
|
+
return await new Promise((resolve, reject) => {
|
|
159
|
+
child.once("error", error => reject(new Error("Controller process could not start.", { cause: error })));
|
|
160
|
+
child.once("spawn", () => {
|
|
161
|
+
child.unref();
|
|
162
|
+
resolve(child.pid);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
157
165
|
}
|
|
158
166
|
/** Stops the per-home Controller and waits until its owned discovery is gone. */
|
|
159
167
|
export async function stopFileTaskController(home, options = {}) {
|
|
@@ -398,7 +406,7 @@ function classifyRefreshFailure(error, options) {
|
|
|
398
406
|
options.onError?.(error);
|
|
399
407
|
return {
|
|
400
408
|
status: "failed",
|
|
401
|
-
message: error
|
|
409
|
+
message: describeCliFailure(error).message
|
|
402
410
|
};
|
|
403
411
|
}
|
|
404
412
|
/** Foreground command bridge. It never reads or writes Agent terminal bytes. */
|
|
@@ -6,6 +6,7 @@ import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
|
6
6
|
import { interruptThenTerminalState, prepareMessageContinuations } from "../message/messageContinuation.js";
|
|
7
7
|
import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
|
|
8
8
|
import { deferProviderRetry, providerRetryPending, providerRetryProjection, recordProviderFailure } from "../runtime/providerRetry.js";
|
|
9
|
+
import { createTaskMessage } from "../message/message.js";
|
|
9
10
|
import { AgentHostObservationDeferred, recordAgentHostConnection, resolveAgentHostObservation } from "./agentHostObservation.js";
|
|
10
11
|
import { admitOwnedProviderInput, ownedProviderInput } from "./providerRetryAdmission.js";
|
|
11
12
|
import { RuntimeHookRunFenceError } from "./runtimeHookRunFence.js";
|
|
@@ -1243,6 +1244,29 @@ export class FileSchedulerStoreAdapter {
|
|
|
1243
1244
|
}
|
|
1244
1245
|
}
|
|
1245
1246
|
binding = admitOwnedProviderInput(store, binding, input);
|
|
1247
|
+
// The Host's human console is a user ingress. Persist its original text
|
|
1248
|
+
// before the Provider write; provider-visible userMessage items alone
|
|
1249
|
+
// are NOT proof of human authorship (managed prompts use those too).
|
|
1250
|
+
// This shares the exact writer/session admission and its transaction.
|
|
1251
|
+
if (input.authorityOwner === "human" && input.roleName === "leader"
|
|
1252
|
+
&& input.runId === undefined && input.boundedText !== undefined) {
|
|
1253
|
+
const prior = store.listEvents(input.taskId).find(event => event.type === "message.native-user-input" && event.payload.attemptId === input.attemptId);
|
|
1254
|
+
if (prior !== undefined) {
|
|
1255
|
+
const message = store.listMessages(input.taskId).find(entry => entry.id === prior.payload.messageId);
|
|
1256
|
+
if (message?.body !== input.boundedText || prior.payload.nativeSessionId !== input.nativeSessionId) {
|
|
1257
|
+
throw new AgentHostProviderTurnFenceError("Native user input identity was reused with different content.");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
const message = createTaskMessage(store.nextMessageId(input.taskId), input.taskId, input.boundedText, "user", { type: "user" }, input.now, { intent: "record" });
|
|
1262
|
+
store.saveMessage(input.taskId, message);
|
|
1263
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "message.native-user-input", {
|
|
1264
|
+
messageId: message.id, attemptId: input.attemptId, nativeSessionId: input.nativeSessionId,
|
|
1265
|
+
authorityEpoch: String(input.authorityEpoch), holderId: input.holderId
|
|
1266
|
+
}, input.now));
|
|
1267
|
+
// No mailbox enqueue: this very input is already being delivered.
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1246
1270
|
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
|
|
1247
1271
|
...(input.runId === undefined ? {} : { runId: input.runId }),
|
|
1248
1272
|
attemptId: input.attemptId,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { archiveLeaderTask } from "../task/leaderArchive.js";
|
|
3
|
+
import { TaskWorkspaceCoordinator } from "../repository/taskWorkspaceCoordinator.js";
|
|
2
4
|
import { AGENT_OPERATIONAL_ENVIRONMENT_NAMES, NATIVE_AGENT_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES } from "../agent/launchEnvironment.js";
|
|
3
5
|
import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
4
6
|
import { reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveRuntimeHealth, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
@@ -359,6 +361,10 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
359
361
|
// the poll interval.
|
|
360
362
|
const kernel = createKernelPorts(store, createLinuxProcessPort(), (taskId) => {
|
|
361
363
|
runningRuntime?.signal(`task:${taskId}`);
|
|
364
|
+
}, [], async (taskId, environment, request) => {
|
|
365
|
+
if (!(workspacePreparer instanceof FileTaskWorkspacePreparer))
|
|
366
|
+
throw new Error("Archive workspace owner unavailable.");
|
|
367
|
+
return archiveLeaderTask(store, new TaskWorkspaceCoordinator(store, workspacePreparer, webWorkflow), taskId, environment, request);
|
|
362
368
|
});
|
|
363
369
|
closeKernel = () => kernel.close();
|
|
364
370
|
const webWorkflow = new FileTaskWorkflowRuntime(home, store, schedulerStore, planner, tmux, workspacePreparer, {
|
|
@@ -8,9 +8,11 @@ import { readHomeFilesystemId } from "./homeFilesystemIdentity.js";
|
|
|
8
8
|
import { CONTROLLER_DISCOVERY_PATH, ControllerProtocolError, encodeControllerRequest, FILE_TASK_CONTROLLER_PROTOCOL_VERSION, MAX_CONTROLLER_MESSAGE_BYTES, parseControllerDiscovery, parseControllerResponse } from "./protocol.js";
|
|
9
9
|
export class ControllerClientError extends Error {
|
|
10
10
|
code;
|
|
11
|
-
|
|
11
|
+
diagnostic;
|
|
12
|
+
constructor(code, message, cause, diagnostic) {
|
|
12
13
|
super(message, { cause });
|
|
13
14
|
this.code = code;
|
|
15
|
+
this.diagnostic = diagnostic;
|
|
14
16
|
this.name = "ControllerClientError";
|
|
15
17
|
}
|
|
16
18
|
}
|
|
@@ -33,12 +35,14 @@ export async function readControllerDiscovery(home) {
|
|
|
33
35
|
try {
|
|
34
36
|
const metadata = await lstat(discoveryPath);
|
|
35
37
|
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
36
|
-
if (!metadata.isFile()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (!metadata.isFile())
|
|
39
|
+
throw invalidDiscovery(new Error("Discovery path is not a regular file."));
|
|
40
|
+
if ((metadata.mode & 0o077) !== 0)
|
|
41
|
+
throw invalidDiscovery(new Error("Discovery record permissions are not owner-only."));
|
|
42
|
+
if (metadata.size > 4_096)
|
|
43
|
+
throw invalidDiscovery(new Error("Discovery record exceeds the 4096-byte limit."));
|
|
44
|
+
if (uid !== undefined && metadata.uid !== uid)
|
|
45
|
+
throw invalidDiscovery(new Error("Discovery record belongs to a different user."));
|
|
42
46
|
const value = JSON.parse(await readFile(discoveryPath, "utf8"));
|
|
43
47
|
const socketPath = discoverySocketPath(value);
|
|
44
48
|
return parseControllerDiscovery(value, {
|
|
@@ -50,7 +54,7 @@ export async function readControllerDiscovery(home) {
|
|
|
50
54
|
if (error instanceof ControllerClientError)
|
|
51
55
|
throw error;
|
|
52
56
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
53
|
-
throw new ControllerClientError("CONTROLLER_NOT_RUNNING", withOriginalError("Controller is
|
|
57
|
+
throw new ControllerClientError("CONTROLLER_NOT_RUNNING", withOriginalError("Controller discovery record is missing; process state is unverified.", error), error, { delivery: "not-sent" });
|
|
54
58
|
}
|
|
55
59
|
if (isNodeError(error) && (error.code === "EPERM" || error.code === "EACCES")) {
|
|
56
60
|
throw controllerAccessDenied(error, "discovery record");
|
|
@@ -72,12 +76,22 @@ function discoverySocketPath(value) {
|
|
|
72
76
|
return socketPath;
|
|
73
77
|
}
|
|
74
78
|
export async function callController(home, method, params = {}, options = {}) {
|
|
75
|
-
const
|
|
79
|
+
const id = options.id ?? randomUUID();
|
|
80
|
+
let discovery;
|
|
81
|
+
try {
|
|
82
|
+
discovery = await readControllerDiscovery(home);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (!(error instanceof ControllerClientError))
|
|
86
|
+
throw error;
|
|
87
|
+
throw new ControllerClientError(error.code, error.message, error.cause, {
|
|
88
|
+
delivery: "not-sent", method, requestId: id
|
|
89
|
+
});
|
|
90
|
+
}
|
|
76
91
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
77
92
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) {
|
|
78
93
|
throw new TypeError("Controller timeout must be a positive integer.");
|
|
79
94
|
}
|
|
80
|
-
const id = options.id ?? randomUUID();
|
|
81
95
|
let requestLine;
|
|
82
96
|
try {
|
|
83
97
|
requestLine = encodeControllerRequest({
|
|
@@ -93,11 +107,13 @@ export async function callController(home, method, params = {}, options = {}) {
|
|
|
93
107
|
}
|
|
94
108
|
catch (error) {
|
|
95
109
|
if (error instanceof ControllerProtocolError) {
|
|
96
|
-
throw new ControllerClientError(error.code, error.message, error
|
|
110
|
+
throw new ControllerClientError(error.code, error.message, error, {
|
|
111
|
+
delivery: "not-sent", method, requestId: id
|
|
112
|
+
});
|
|
97
113
|
}
|
|
98
|
-
throw new ControllerClientError("INVALID_REQUEST", withOriginalError("Invalid controller request.", error), error);
|
|
114
|
+
throw new ControllerClientError("INVALID_REQUEST", withOriginalError("Invalid controller request.", error), error, { delivery: "not-sent", method, requestId: id });
|
|
99
115
|
}
|
|
100
|
-
return exchange(discovery.socketPath, requestLine, id, timeoutMs);
|
|
116
|
+
return exchange(discovery.socketPath, requestLine, id, timeoutMs, method);
|
|
101
117
|
}
|
|
102
118
|
/**
|
|
103
119
|
* Explicit restart recovery for an exact Controller whose discovery record
|
|
@@ -151,7 +167,7 @@ export async function stopOrphanedFileTaskController(home, timeoutMs, options =
|
|
|
151
167
|
}
|
|
152
168
|
return Object.freeze({ pid: candidate.pid });
|
|
153
169
|
}
|
|
154
|
-
function exchange(socketPath, requestLine, expectedId, timeoutMs) {
|
|
170
|
+
function exchange(socketPath, requestLine, expectedId, timeoutMs, method) {
|
|
155
171
|
return new Promise((resolve, reject) => {
|
|
156
172
|
const socket = createConnection(socketPath);
|
|
157
173
|
let buffer = Buffer.alloc(0);
|
|
@@ -174,7 +190,12 @@ function exchange(socketPath, requestLine, expectedId, timeoutMs) {
|
|
|
174
190
|
settled = true;
|
|
175
191
|
clearTimeout(timer);
|
|
176
192
|
socket.destroy();
|
|
177
|
-
reject(error
|
|
193
|
+
reject(new ControllerClientError(error.code, error.message, error.cause, {
|
|
194
|
+
delivery: error.diagnostic?.delivery ?? (deliveryStarted ? "unconfirmed" : "not-sent"),
|
|
195
|
+
method,
|
|
196
|
+
requestId: expectedId,
|
|
197
|
+
socketPath
|
|
198
|
+
}));
|
|
178
199
|
};
|
|
179
200
|
socket.on("connect", () => {
|
|
180
201
|
// Once write begins, a missing response cannot prove that the Controller
|
|
@@ -202,7 +223,9 @@ function exchange(socketPath, requestLine, expectedId, timeoutMs) {
|
|
|
202
223
|
if (response.ok)
|
|
203
224
|
finish(response.result);
|
|
204
225
|
else
|
|
205
|
-
fail(new ControllerClientError(response.error.code, response.error.message
|
|
226
|
+
fail(new ControllerClientError(response.error.code, response.error.message, undefined, {
|
|
227
|
+
delivery: "response-received"
|
|
228
|
+
}));
|
|
206
229
|
}
|
|
207
230
|
catch (error) {
|
|
208
231
|
fail(invalidResponse(error));
|
|
@@ -219,22 +242,22 @@ function exchange(socketPath, requestLine, expectedId, timeoutMs) {
|
|
|
219
242
|
}
|
|
220
243
|
export function classifyControllerSocketError(error, deliveryStarted) {
|
|
221
244
|
if (deliveryStarted) {
|
|
222
|
-
return new ControllerClientError("CONTROLLER_DELIVERY_UNKNOWN", withOriginalError("Controller request delivery is unknown.", error), error);
|
|
245
|
+
return new ControllerClientError("CONTROLLER_DELIVERY_UNKNOWN", withOriginalError("Controller request delivery is unknown.", error), error, { delivery: "unconfirmed" });
|
|
223
246
|
}
|
|
224
247
|
if (isNodeError(error) && (error.code === "EPERM" || error.code === "EACCES")) {
|
|
225
248
|
return controllerAccessDenied(error, "socket");
|
|
226
249
|
}
|
|
227
|
-
return new ControllerClientError("CONTROLLER_UNAVAILABLE", withOriginalError("Controller is unavailable.", error), error);
|
|
250
|
+
return new ControllerClientError("CONTROLLER_UNAVAILABLE", withOriginalError("Controller is unavailable.", error), error, { delivery: "not-sent" });
|
|
228
251
|
}
|
|
229
252
|
function controllerAccessDenied(error, target) {
|
|
230
253
|
return new ControllerClientError("CONTROLLER_ACCESS_DENIED", `Controller ${target} access was denied (${error.code}) before any request was sent. `
|
|
231
254
|
+ "In Codex, request sandbox escalation to rerun the same Yui CLI command outside the sandbox "
|
|
232
|
-
+ `if authorized. If escalation is denied, report that to the user.${originalErrorDetail(error)}`, error);
|
|
255
|
+
+ `if authorized. If escalation is denied, report that to the user.${originalErrorDetail(error)}`, error, { delivery: "not-sent" });
|
|
233
256
|
}
|
|
234
257
|
function invalidDiscovery(error) {
|
|
235
258
|
return new ControllerClientError("CONTROLLER_DISCOVERY_INVALID", error === undefined
|
|
236
259
|
? "Controller discovery is invalid."
|
|
237
|
-
: withOriginalError("Controller discovery is invalid.", error), error);
|
|
260
|
+
: withOriginalError("Controller discovery is invalid.", error), error, { delivery: "not-sent" });
|
|
238
261
|
}
|
|
239
262
|
function invalidResponse(error) {
|
|
240
263
|
return new ControllerClientError("INVALID_RESPONSE", error === undefined
|
|
@@ -248,7 +271,9 @@ function originalErrorDetail(error) {
|
|
|
248
271
|
const code = isNodeError(error) && typeof error.code === "string"
|
|
249
272
|
? ` (${error.code})`
|
|
250
273
|
: "";
|
|
251
|
-
return ` Original error${code}: ${error instanceof
|
|
274
|
+
return ` Original error${code}: ${error instanceof SyntaxError
|
|
275
|
+
? "Input could not be parsed; raw input omitted."
|
|
276
|
+
: error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown cause."}`;
|
|
252
277
|
}
|
|
253
278
|
function isNodeError(error) {
|
|
254
279
|
return error instanceof Error && "code" in error;
|
package/dist/core/protocol.js
CHANGED
|
@@ -129,11 +129,16 @@ export function parseControllerDiscovery(value, expected) {
|
|
|
129
129
|
"processStartIdentity",
|
|
130
130
|
"socketPath",
|
|
131
131
|
"token"
|
|
132
|
-
])
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
])) {
|
|
133
|
+
throw new ControllerProtocolError("CONTROLLER_DISCOVERY_INVALID", "Controller discovery fields are invalid.");
|
|
134
|
+
}
|
|
135
|
+
if (value.schemaVersion !== 1 || value.protocolVersion !== FILE_TASK_CONTROLLER_PROTOCOL_VERSION) {
|
|
136
|
+
throw new ControllerProtocolError("CONTROLLER_DISCOVERY_INVALID", "Controller discovery schema/protocol version does not match this CLI; verify the matching installation.");
|
|
137
|
+
}
|
|
138
|
+
if (value.homeFilesystemId !== expected.homeFilesystemId || value.socketPath !== expected.socketPath) {
|
|
139
|
+
throw new ControllerProtocolError("CONTROLLER_DISCOVERY_INVALID", "Controller discovery Home/socket identity does not match this invocation.");
|
|
140
|
+
}
|
|
141
|
+
if (!isHomeId(value.homeId)
|
|
137
142
|
|| !isHomeFilesystemId(value.homeFilesystemId)
|
|
138
143
|
|| typeof value.controllerInstanceId !== "string"
|
|
139
144
|
|| !/^[a-f0-9]{32}$/u.test(value.controllerInstanceId)
|
|
@@ -141,10 +146,10 @@ export function parseControllerDiscovery(value, expected) {
|
|
|
141
146
|
|| value.pid < 1
|
|
142
147
|
|| typeof value.processStartIdentity !== "string"
|
|
143
148
|
|| !/^[0-9]{1,32}$/u.test(value.processStartIdentity)
|
|
144
|
-
|| value.socketPath !==
|
|
149
|
+
|| typeof value.socketPath !== "string"
|
|
145
150
|
|| typeof value.token !== "string"
|
|
146
151
|
|| !/^[a-f0-9]{64}$/u.test(value.token)) {
|
|
147
|
-
throw new ControllerProtocolError("CONTROLLER_DISCOVERY_INVALID", "Controller discovery is invalid.");
|
|
152
|
+
throw new ControllerProtocolError("CONTROLLER_DISCOVERY_INVALID", "Controller discovery process, Home or authentication identity is invalid; raw record omitted.");
|
|
148
153
|
}
|
|
149
154
|
return Object.freeze({
|
|
150
155
|
schemaVersion: 1,
|
package/dist/doctor/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { operationalAgentEnvironment } from "../agent/launchEnvironment.js";
|
|
|
7
7
|
import { resolveTmuxBin } from "../config/yuiConfig.js";
|
|
8
8
|
import { compileRoleSessionContext } from "../context/roleSessionContext.js";
|
|
9
9
|
import { callController, ControllerClientError } from "../core/controllerClient.js";
|
|
10
|
+
import { describeCliFailure } from "../errors/cliFailure.js";
|
|
10
11
|
import { EPHEMERAL_DOMAIN_GRACE_MS, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "../controller/domainIdentity.js";
|
|
11
12
|
import { usageError } from "../errors/cliError.js";
|
|
12
13
|
import { inspectAgentCapabilities, resolveAgentAdapter } from "../executor/agentAdapter.js";
|
|
@@ -162,19 +163,19 @@ export async function checkController(home, probe = callController) {
|
|
|
162
163
|
}
|
|
163
164
|
catch (error) {
|
|
164
165
|
if (error instanceof ControllerClientError && error.code === "CONTROLLER_NOT_RUNNING") {
|
|
165
|
-
return { name: "controller", status: "missing", detail:
|
|
166
|
+
return { name: "controller", status: "missing", detail: describeCliFailure(error).message };
|
|
166
167
|
}
|
|
167
168
|
if (error instanceof ControllerClientError && error.code === "CONTROLLER_UNAVAILABLE") {
|
|
168
169
|
return {
|
|
169
170
|
name: "controller",
|
|
170
171
|
status: "invalid",
|
|
171
|
-
detail:
|
|
172
|
+
detail: describeCliFailure(error).message
|
|
172
173
|
};
|
|
173
174
|
}
|
|
174
175
|
return {
|
|
175
176
|
name: "controller",
|
|
176
177
|
status: "invalid",
|
|
177
|
-
detail:
|
|
178
|
+
detail: describeCliFailure(error).message
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
}
|