@zq-silk/yui 1.1.2 → 2.0.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 +66 -54
- package/dist/cli/managedDiagnostics.js +2 -1
- package/dist/cli/updatePorts.js +0 -5
- package/dist/cli.js +113 -107
- package/dist/commands/controllerCommands.js +2 -1
- package/dist/commands/globalRoleCommands.js +84 -43
- package/dist/commands/grantCommands.js +48 -8
- package/dist/commands/taskCommands.js +146 -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 +98 -39
- 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/builtinCapabilities.js +60 -8
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/output/boundedRead.js +118 -0
- package/dist/release/releaseWorkflowEngine.js +8 -1
- package/dist/runtime/managedCaller.js +2 -2
- 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/task/leaderArchive.js +111 -0
- package/dist/task/leaderArchiveAuthority.js +24 -0
- package/dist/tmux/commandExecutor.js +7 -5
- 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 +19 -9
- package/docs/release-workflow.zh-CN.md +14 -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 +51 -9
- package/skills/yui-runtime/references/publication.md +3 -1
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
-
import { inspectTaskContext, readTaskContext, readTaskContextDelta } from "../context/taskContext.js";
|
|
2
|
+
import { inspectTaskContext, listTaskContext, readTaskContext, readTaskContextDelta } from "../context/taskContext.js";
|
|
3
|
+
import { readOptions } from "../output/boundedRead.js";
|
|
4
|
+
export function taskRecordList(args, store, family, environment = {}) {
|
|
5
|
+
const parsed = readOptions(args, ["--cursor", "--limit", "--status", "--after", "--work-item"]);
|
|
6
|
+
if (parsed.positionals.length !== 1)
|
|
7
|
+
throw usageError("Record list requires one Task and optional --limit/--cursor/--status/--after/--work-item.");
|
|
8
|
+
const data = listTaskContext(store, parsed.positionals[0], family, {
|
|
9
|
+
...parsed, status: parsed.values.get("--status"), after: parsed.values.get("--after"),
|
|
10
|
+
workItemId: parsed.values.get("--work-item")
|
|
11
|
+
}, environment);
|
|
12
|
+
return { kind: "output", output: `${JSON.stringify(data)}\n`, data };
|
|
13
|
+
}
|
|
3
14
|
/** CLI parsing only; typed callers and capability ingress share the read model. */
|
|
4
15
|
export function runTaskContextCommand(args, store, environment = {}) {
|
|
5
|
-
const explicit = ["read", "delta", "inspect"].includes(args[0] ?? "");
|
|
16
|
+
const explicit = ["read", "delta", "inspect", "list"].includes(args[0] ?? "");
|
|
6
17
|
const action = explicit ? args[0] : "read";
|
|
7
18
|
const taskId = args[explicit ? 1 : 0];
|
|
8
19
|
if (!taskId || taskId.startsWith("--"))
|
|
9
|
-
throw usageError("Task context usage: yui task context [read|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>].");
|
|
20
|
+
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--status <status>] [--work-item <id>].");
|
|
10
21
|
const options = new Map();
|
|
11
22
|
const rest = args.slice(explicit ? 2 : 1);
|
|
12
23
|
const allowed = action === "read" ? [] : action === "delta"
|
|
13
|
-
? ["--after", "--continuation", "--limit"] :
|
|
24
|
+
? ["--after", "--continuation", "--limit"] : action === "list"
|
|
25
|
+
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
14
26
|
for (let i = 0; i < rest.length; i += 2) {
|
|
15
27
|
const flag = rest[i];
|
|
16
28
|
const value = rest[i + 1];
|
|
@@ -22,6 +34,16 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
22
34
|
let data;
|
|
23
35
|
if (action === "read")
|
|
24
36
|
data = readTaskContext(store, taskId, environment);
|
|
37
|
+
else if (action === "list") {
|
|
38
|
+
const family = options.get("--store");
|
|
39
|
+
if (family === undefined)
|
|
40
|
+
throw usageError("Context list requires --store.");
|
|
41
|
+
data = listTaskContext(store, taskId, family, {
|
|
42
|
+
cursor: options.get("--cursor"),
|
|
43
|
+
status: options.get("--status"), after: options.get("--after"), workItemId: options.get("--work-item"),
|
|
44
|
+
...(options.has("--limit") ? { limit: Number(options.get("--limit")) } : {})
|
|
45
|
+
}, environment);
|
|
46
|
+
}
|
|
25
47
|
else if (action === "delta") {
|
|
26
48
|
const after = options.get("--after");
|
|
27
49
|
if (after === undefined)
|
|
@@ -36,7 +58,8 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
36
58
|
const refId = options.get("--ref");
|
|
37
59
|
if (family === undefined || refId === undefined)
|
|
38
60
|
throw usageError("Context inspect requires --store and --ref.");
|
|
39
|
-
data = inspectTaskContext(store, taskId, { store: family, refId, digest: options.get("--digest")
|
|
61
|
+
data = inspectTaskContext(store, taskId, { store: family, refId, digest: options.get("--digest"),
|
|
62
|
+
cursor: options.get("--cursor") }, environment);
|
|
40
63
|
}
|
|
41
|
-
return { kind: "output", output: `${JSON.stringify(data
|
|
64
|
+
return { kind: "output", output: `${JSON.stringify(data)}\n`, data };
|
|
42
65
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
|
|
2
|
-
import { assertContextRecordReadable
|
|
2
|
+
import { assertContextRecordReadable } from "../context/taskContext.js";
|
|
3
|
+
import { taskRecordList } from "./taskContextCommand.js";
|
|
4
|
+
import { materialize } from "../context/taskContext.js";
|
|
3
5
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
4
6
|
import { createDecision, supersedeDecision } from "../decision/decision.js";
|
|
5
7
|
import { dataError, usageError } from "../errors/cliError.js";
|
|
6
8
|
import { createMilestone } from "../milestone/milestone.js";
|
|
7
|
-
import {
|
|
8
|
-
import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, leaderMailbox, notifyMailbox, optionalNonEmptyOption, output, parseMultiValueTail, parseTail, presentTime, recordTaskEvent, requiredOption, requiredText, requireTask, taskActor, taskMailbox, taskRef } from "./taskCommandSupport.js";
|
|
9
|
+
import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, leaderMailbox, notifyMailbox, output, parseMultiValueTail, parseTail, presentTime, recordTaskEvent, requiredOption, requiredText, requireTask, taskActor, taskMailbox, taskRef } from "./taskCommandSupport.js";
|
|
9
10
|
import { failureCapabilitiesCommand } from "../runtime/agentFailureContext.js";
|
|
10
11
|
export function taskBriefCommand(args, store, options) {
|
|
11
12
|
const [command, ...rest] = args;
|
|
@@ -87,7 +88,9 @@ export function taskBriefCommand(args, store, options) {
|
|
|
87
88
|
if (result.task.status === "active") {
|
|
88
89
|
notifyMailbox(options.runtime, leaderMailbox(result.task.id));
|
|
89
90
|
}
|
|
90
|
-
return output(`Updated brief for ${result.task.id}\n`, {
|
|
91
|
+
return output(`Updated brief for ${result.task.id}\n`, {
|
|
92
|
+
taskId: result.task.id, effect: "saved", ref: materialize("task-brief", result.task.id, result.brief).ref
|
|
93
|
+
});
|
|
91
94
|
}
|
|
92
95
|
throw usageError(command === undefined
|
|
93
96
|
? "Task brief command is required."
|
|
@@ -125,29 +128,7 @@ export function taskDecisionCommand(args, store, options) {
|
|
|
125
128
|
return output(`Recorded decision ${result.decision.id} for ${result.task.id}\n`);
|
|
126
129
|
}
|
|
127
130
|
if (command === "list") {
|
|
128
|
-
|
|
129
|
-
const parsed = parseTail(rest, new Set(["--status"]), usage);
|
|
130
|
-
exactPositionals(parsed.positionals, 1, usage);
|
|
131
|
-
const task = requireTask(store, parsed.positionals[0]);
|
|
132
|
-
const readable = contextRecordReader(store, task.id, options.environment);
|
|
133
|
-
let decisions = store.listDecisions(task.id).filter(record => readable("task-decision", record.id));
|
|
134
|
-
const status = parsed.options.get("--status");
|
|
135
|
-
if (status !== undefined) {
|
|
136
|
-
if (status !== "active" && status !== "superseded") {
|
|
137
|
-
throw usageError("--status must be active or superseded.", usage);
|
|
138
|
-
}
|
|
139
|
-
decisions = decisions.filter((d) => d.status === status);
|
|
140
|
-
}
|
|
141
|
-
if (decisions.length === 0) {
|
|
142
|
-
return output(`No decisions found for ${task.id}.\n`, { taskId: task.id, decisions: [] });
|
|
143
|
-
}
|
|
144
|
-
const timeZone = store.getConfig().timeZone;
|
|
145
|
-
return output(`${renderTable(`Decisions: ${task.id}`, [
|
|
146
|
-
{ header: "Decision", minWidth: 8, maxWidth: 18 },
|
|
147
|
-
{ header: "Status", minWidth: 6, maxWidth: 12 },
|
|
148
|
-
{ header: "Title", minWidth: 8, maxWidth: 64 },
|
|
149
|
-
{ header: "Created", minWidth: 10, maxWidth: 28 }
|
|
150
|
-
], decisions.map((d) => [d.id, d.status, d.title, presentTime(d.createdAt, timeZone)]), defaultTableWidth())}\n`, { taskId: task.id, decisions });
|
|
131
|
+
return taskRecordList(rest, store, "task-decision", options.environment);
|
|
151
132
|
}
|
|
152
133
|
if (command === "show") {
|
|
153
134
|
exactPositionals(rest, 2, "Task decision show usage: yui task decision show <task> <decision>.");
|
|
@@ -231,19 +212,7 @@ export function taskMilestoneCommand(args, store, options) {
|
|
|
231
212
|
return output(`Added milestone ${result.milestone.id} for ${result.task.id}\n`);
|
|
232
213
|
}
|
|
233
214
|
if (command === "list") {
|
|
234
|
-
|
|
235
|
-
const task = requireTask(store, rest[0]);
|
|
236
|
-
const readable = contextRecordReader(store, task.id, options.environment);
|
|
237
|
-
const milestones = store.listMilestones(task.id).filter(record => readable("task-milestone", record.id));
|
|
238
|
-
if (milestones.length === 0) {
|
|
239
|
-
return output(`No milestones found for ${task.id}.\n`, { taskId: task.id, milestones: [] });
|
|
240
|
-
}
|
|
241
|
-
const timeZone = store.getConfig().timeZone;
|
|
242
|
-
return output(`${renderTable(`Milestones: ${task.id}`, [
|
|
243
|
-
{ header: "Milestone", minWidth: 9, maxWidth: 18 },
|
|
244
|
-
{ header: "Title", minWidth: 8, maxWidth: 64 },
|
|
245
|
-
{ header: "Created", minWidth: 10, maxWidth: 28 }
|
|
246
|
-
], milestones.map((m) => [m.id, m.title, presentTime(m.createdAt, timeZone)]), defaultTableWidth())}\n`, { taskId: task.id, milestones });
|
|
215
|
+
return taskRecordList(rest, store, "task-milestone", options.environment);
|
|
247
216
|
}
|
|
248
217
|
if (command === "show") {
|
|
249
218
|
exactPositionals(rest, 2, "Task milestone show usage: yui task milestone show <task> <milestone>.");
|
|
@@ -269,35 +238,7 @@ export function taskMilestoneCommand(args, store, options) {
|
|
|
269
238
|
export function taskEventCommand(args, store, options = {}) {
|
|
270
239
|
const [command, ...rest] = args;
|
|
271
240
|
if (command === "list") {
|
|
272
|
-
|
|
273
|
-
const parsed = parseTail(rest, new Set(["--after", "--limit"]), eventListUsage);
|
|
274
|
-
exactPositionals(parsed.positionals, 1, eventListUsage);
|
|
275
|
-
const task = requireTask(store, parsed.positionals[0]);
|
|
276
|
-
const readable = contextRecordReader(store, task.id, options.environment);
|
|
277
|
-
let events = store.listEvents(task.id).filter(record => readable("task-event", record.id));
|
|
278
|
-
const after = optionalNonEmptyOption(parsed.options, "--after");
|
|
279
|
-
if (after !== undefined) {
|
|
280
|
-
const afterMs = Date.parse(after);
|
|
281
|
-
if (!Number.isFinite(afterMs))
|
|
282
|
-
throw usageError("--after must be a valid timestamp.", eventListUsage);
|
|
283
|
-
events = events.filter((e) => Date.parse(e.createdAt) > afterMs);
|
|
284
|
-
}
|
|
285
|
-
const limit = optionalNonEmptyOption(parsed.options, "--limit");
|
|
286
|
-
if (limit !== undefined) {
|
|
287
|
-
const n = Number(limit);
|
|
288
|
-
if (!Number.isSafeInteger(n) || n <= 0)
|
|
289
|
-
throw usageError("--limit must be a positive integer.", eventListUsage);
|
|
290
|
-
events = events.slice(-n);
|
|
291
|
-
}
|
|
292
|
-
if (events.length === 0) {
|
|
293
|
-
return output(`No events found for ${task.id}.\n`, { taskId: task.id, events: [] });
|
|
294
|
-
}
|
|
295
|
-
const timeZone = store.getConfig().timeZone;
|
|
296
|
-
return output(`${renderTable(`Events: ${task.id}`, [
|
|
297
|
-
{ header: "Event", minWidth: 8, maxWidth: 18 },
|
|
298
|
-
{ header: "Type", minWidth: 8, maxWidth: 28 },
|
|
299
|
-
{ header: "Created", minWidth: 10, maxWidth: 28 }
|
|
300
|
-
], events.map((e) => [e.id, e.type, presentTime(e.createdAt, timeZone)]), defaultTableWidth())}\n`, { taskId: task.id, events });
|
|
241
|
+
return taskRecordList(rest, store, "task-event", options.environment);
|
|
301
242
|
}
|
|
302
243
|
if (command === "show") {
|
|
303
244
|
exactPositionals(rest, 2, "Task event show usage: yui task event show <task> <event>.");
|
|
@@ -2,7 +2,9 @@ import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
|
2
2
|
import { dataError, roleNotFound, taskNotFound, usageError } from "../errors/cliError.js";
|
|
3
3
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
4
4
|
import { answerInputRequest, cancelInputRequest, createInputRequest } from "../input/inputRequest.js";
|
|
5
|
-
import {
|
|
5
|
+
import { boundedDocument, recordPage, readOptions } from "../output/boundedRead.js";
|
|
6
|
+
import { materialize, resolveContextReader } from "../context/taskContext.js";
|
|
7
|
+
import { taskRecordList } from "./taskContextCommand.js";
|
|
6
8
|
import { formatTimestamp } from "../output/timePresentation.js";
|
|
7
9
|
import { requireManagedTaskCaller, requireManagedGlobalCaller } from "../runtime/managedCaller.js";
|
|
8
10
|
import { isRoleRunStalled, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
|
|
@@ -12,7 +14,7 @@ export function runTaskInputCommand(args, store, options) {
|
|
|
12
14
|
const [command, ...rest] = args;
|
|
13
15
|
switch (command) {
|
|
14
16
|
case "request": return createRequest(rest, store, options);
|
|
15
|
-
case "list": return listRequests(rest, store);
|
|
17
|
+
case "list": return listRequests(rest, store, options);
|
|
16
18
|
case "show": return showRequest(rest, store, options);
|
|
17
19
|
case "answer": return answerRequest(rest, store, options);
|
|
18
20
|
case "cancel": return cancelRequest(rest, store, options);
|
|
@@ -86,51 +88,40 @@ function createRequest(args, store, options) {
|
|
|
86
88
|
notifyMailbox(options, { kind: "operator" });
|
|
87
89
|
return output(`Created input request ${request.id} for ${request.taskId}\n`, { request });
|
|
88
90
|
}
|
|
89
|
-
function listRequests(args, store) {
|
|
90
|
-
const usage = "Task input list usage: yui task input list [task] [--all].";
|
|
91
|
-
const
|
|
91
|
+
function listRequests(args, store, options) {
|
|
92
|
+
const usage = "Task input list usage: yui task input list [task] [--all] [--limit <n>] [--cursor <cursor>].";
|
|
93
|
+
const all = args.includes("--all");
|
|
94
|
+
if (args.filter(arg => arg === "--all").length > 1)
|
|
95
|
+
throw usageError(usage);
|
|
96
|
+
const parsed = readOptions(args.filter(arg => arg !== "--all"));
|
|
92
97
|
if (parsed.positionals.length > 1)
|
|
93
98
|
throw usageError(usage);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const requests = parsed.options.has("--all")
|
|
101
|
-
? all
|
|
102
|
-
: all.filter((request) => request.status === "open");
|
|
103
|
-
let rendered = "No input requests found.\n";
|
|
104
|
-
if (requests.length > 0) {
|
|
105
|
-
const timeZone = store.getConfig().timeZone;
|
|
106
|
-
rendered = `${renderTable(taskId === undefined ? "Input inbox" : `Input requests: ${taskId}`, [
|
|
107
|
-
{ header: "Input", minWidth: 6, maxWidth: 18 },
|
|
108
|
-
{ header: "Task", minWidth: 6, maxWidth: 18 },
|
|
109
|
-
{ header: "Status", minWidth: 6, maxWidth: 10 },
|
|
110
|
-
{ header: "Policy", minWidth: 8, maxWidth: 12 },
|
|
111
|
-
{ header: "Question", minWidth: 8, maxWidth: 72 },
|
|
112
|
-
{ header: "Created", minWidth: 10, maxWidth: 28 }
|
|
113
|
-
], requests.map((request) => [
|
|
114
|
-
taskId === undefined ? `${request.taskId}/${request.id}` : request.id,
|
|
115
|
-
request.taskId,
|
|
116
|
-
request.status,
|
|
117
|
-
request.policy.kind,
|
|
118
|
-
request.question,
|
|
119
|
-
formatTimestamp(request.createdAt, timeZone)
|
|
120
|
-
]), defaultTableWidth())}\n`;
|
|
99
|
+
const caller = resolveContextReader(store, options.environment ?? {});
|
|
100
|
+
const taskId = parsed.positionals[0] ?? caller?.taskId;
|
|
101
|
+
if (taskId !== undefined) {
|
|
102
|
+
return taskRecordList([taskId, ...(all ? [] : ["--status", "open"]),
|
|
103
|
+
...(parsed.cursor === undefined ? [] : ["--cursor", parsed.cursor]),
|
|
104
|
+
...(parsed.limit === undefined ? [] : ["--limit", String(parsed.limit)])], store, "input-request", options.environment);
|
|
121
105
|
}
|
|
122
|
-
|
|
106
|
+
const requests = store.listAllInputRequests().filter(request => all || request.status === "open");
|
|
107
|
+
const data = recordPage(requests.map(request => ({
|
|
108
|
+
taskId: request.taskId, ref: materialize("input-request", request.id, request).ref,
|
|
109
|
+
status: request.status, summary: request.question.slice(0, 400), policy: request.policy.kind
|
|
110
|
+
})), `input-inbox:${all ? "all" : "open"}`, parsed);
|
|
111
|
+
return output(`${JSON.stringify(data)}\n`, data);
|
|
123
112
|
}
|
|
124
113
|
function showRequest(args, store, options) {
|
|
125
114
|
const usage = "Task input show usage: yui task input show (<task>/<input> | <input> --task <task>).";
|
|
126
|
-
const parsed = parseTail(args, new Set(["--task"]), usage);
|
|
115
|
+
const parsed = parseTail(args, new Set(["--task", "--cursor"]), usage);
|
|
127
116
|
exactPositionals(parsed.positionals, 1, usage);
|
|
128
117
|
const reference = inputRequestReference(store, parsed.positionals[0], optionalNonEmptyOption(parsed.options, "--task"), options.environment);
|
|
129
118
|
const request = store.getInputRequest(reference.taskId, reference.localId);
|
|
130
119
|
if (request === null) {
|
|
131
120
|
throw dataError(`Input request not found: ${reference.taskId}/${reference.localId}.`);
|
|
132
121
|
}
|
|
133
|
-
|
|
122
|
+
const data = boundedDocument({ request }, `input:${reference.taskId}/${reference.localId}`, parsed.options.get("--cursor"));
|
|
123
|
+
return output("contentPage" in data ? `${JSON.stringify(data)}\n`
|
|
124
|
+
: renderInputRequest(request, store.getConfig().timeZone), data);
|
|
134
125
|
}
|
|
135
126
|
function answerRequest(args, store, options) {
|
|
136
127
|
const usage = "Task input answer usage: yui task input answer (<task>/<input> | <input> --task <task>) (--choice <key> | --text <text>).";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { dataError, taskNotFound, usageError } from "../errors/cliError.js";
|
|
3
3
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
4
|
-
import {
|
|
4
|
+
import { taskRecordList } from "./taskContextCommand.js";
|
|
5
5
|
import { formatTimestamp } from "../output/timePresentation.js";
|
|
6
6
|
import { resolveProject } from "../repository/project.js";
|
|
7
7
|
import { createPublicationReference } from "../task/publicationReference.js";
|
|
@@ -26,7 +26,7 @@ export function runPublicationCommand(args, store, options) {
|
|
|
26
26
|
const [command, ...rest] = args;
|
|
27
27
|
switch (command) {
|
|
28
28
|
case "upsert": return upsertPublication(rest, store, options);
|
|
29
|
-
case "list": return
|
|
29
|
+
case "list": return taskRecordList(rest, store, "publication", options.environment);
|
|
30
30
|
case "show": return showPublication(rest, store);
|
|
31
31
|
default:
|
|
32
32
|
throw usageError(command === undefined
|
|
@@ -125,34 +125,6 @@ export function upsertTaskPublication(store, task, input, actor, now) {
|
|
|
125
125
|
recordPublicationEvent(store, reference, now);
|
|
126
126
|
return { reference, idempotent: false };
|
|
127
127
|
}
|
|
128
|
-
function listPublications(args, store) {
|
|
129
|
-
const usage = "Task publication list usage: yui task publication list <task>.";
|
|
130
|
-
const parsed = parseTail(args, new Set(), usage);
|
|
131
|
-
exactPositionals(parsed.positionals, 1, usage);
|
|
132
|
-
const task = requireTask(store, parsed.positionals[0]);
|
|
133
|
-
const references = store.listPublicationReferences(task.id);
|
|
134
|
-
if (references.length === 0) {
|
|
135
|
-
return output("No publication references found.\n", []);
|
|
136
|
-
}
|
|
137
|
-
const rendered = `${renderTable(`Publication references: ${task.id}`, [
|
|
138
|
-
{ header: "ID", minWidth: 12, maxWidth: 22 },
|
|
139
|
-
{ header: "External", minWidth: 16, maxWidth: 40 },
|
|
140
|
-
{ header: "Title", minWidth: 16, maxWidth: 40 },
|
|
141
|
-
{ header: "State", minWidth: 7, maxWidth: 10 },
|
|
142
|
-
{ header: "Verification", minWidth: 11, maxWidth: 14 },
|
|
143
|
-
{ header: "Lineage", minWidth: 18, maxWidth: 58 },
|
|
144
|
-
{ header: "Supersedes", minWidth: 12, maxWidth: 22 }
|
|
145
|
-
], references.map((reference) => [
|
|
146
|
-
reference.id,
|
|
147
|
-
`${reference.provider}/${reference.repository}/${reference.externalId}`,
|
|
148
|
-
reference.title ?? "-",
|
|
149
|
-
reference.state,
|
|
150
|
-
reference.verification,
|
|
151
|
-
lineageSummary(reference),
|
|
152
|
-
reference.supersedes ?? "-"
|
|
153
|
-
]), defaultTableWidth())}\n`;
|
|
154
|
-
return output(rendered, references);
|
|
155
|
-
}
|
|
156
128
|
function showPublication(args, store) {
|
|
157
129
|
const usage = "Task publication show usage: yui task publication show (<task>/<publication-id> | <task> <publication-id>).";
|
|
158
130
|
const parsed = parseTail(args, new Set(), usage);
|
|
@@ -197,11 +169,6 @@ function renderPublication(reference, timeZone) {
|
|
|
197
169
|
`Created: ${formatTimestamp(reference.createdAt, timeZone)}`
|
|
198
170
|
].join("\n").concat("\n");
|
|
199
171
|
}
|
|
200
|
-
function lineageSummary(reference) {
|
|
201
|
-
const local = reference.localCommit ?? "unknown";
|
|
202
|
-
const remote = reference.remoteCommit ?? "unknown";
|
|
203
|
-
return `${local.slice(0, 12)} -> ${reference.externalId} -> ${remote.slice(0, 12)}`;
|
|
204
|
-
}
|
|
205
172
|
function parseVerification(parsed, usage) {
|
|
206
173
|
if (parsed.flags.has("--reported") && parsed.flags.has("--verified")) {
|
|
207
174
|
throw usageError("--reported and --verified are mutually exclusive.", usage);
|
|
@@ -36,8 +36,7 @@ export async function runTaskPublicationVerifyCommand(args, store, options) {
|
|
|
36
36
|
if (error instanceof CliError)
|
|
37
37
|
throw error;
|
|
38
38
|
throw runtimeError(`Publication verification failed for ${request.publication.provider}/`
|
|
39
|
-
+ `${request.publication.repository}/${request.publication.externalId}:
|
|
40
|
-
+ `${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
+ `${request.publication.repository}/${request.publication.externalId}.`, { cause: error });
|
|
41
40
|
}
|
|
42
41
|
assertVerificationObservation(request, observation);
|
|
43
42
|
const result = commitPublicationVerification(store, request, observation, options.now?.() ?? new Date());
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseRepeatable } from "../cli/parseRepeatable.js";
|
|
2
2
|
import { usageError } from "../errors/cliError.js";
|
|
3
|
+
import { describeCliFailure } from "../errors/cliFailure.js";
|
|
3
4
|
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
4
5
|
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
5
6
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
@@ -128,7 +129,7 @@ async function integrateUpstream(args, store, home, options) {
|
|
|
128
129
|
failure = {
|
|
129
130
|
projectId: binding.projectId,
|
|
130
131
|
phase,
|
|
131
|
-
message: error
|
|
132
|
+
message: describeCliFailure(error, { environment: options.environment }).message,
|
|
132
133
|
...(integrationId === undefined ? {} : { integrationId }),
|
|
133
134
|
effect: integrationId === undefined ? "not-started" : "inspect-required"
|
|
134
135
|
};
|
|
@@ -10,9 +10,6 @@ import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
|
10
10
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
|
|
11
11
|
import { MAX_SYNTHESIS_SOURCE_RUNS, sourceRunContextValue } from "./sourceRunContext.js";
|
|
12
12
|
export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
|
|
13
|
-
export const RUN_CONTEXT_PACK_MAX_REFS = 256;
|
|
14
|
-
export const RUN_CONTEXT_PACK_MAX_BYTES = 8 * 1024 * 1024;
|
|
15
|
-
export const RUN_CONTEXT_EXPAND_MAX_BYTES = 4 * 1024 * 1024;
|
|
16
13
|
export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller", baselineRef, sourceRunIds) {
|
|
17
14
|
if (baselineRef !== undefined) {
|
|
18
15
|
const baseline = store.getContextSnapshot(run.taskId, baselineRef.id);
|
|
@@ -212,18 +209,9 @@ export function buildRunContextPack(store, taskId, runId) {
|
|
|
212
209
|
const run = requireExactRun(store, taskId, runId);
|
|
213
210
|
const snapshot = readRunContextSnapshot(store, run);
|
|
214
211
|
const pointers = snapshot.refs;
|
|
215
|
-
if (pointers.length > RUN_CONTEXT_PACK_MAX_REFS) {
|
|
216
|
-
throw new Error(`AgentRun Context exceeds ${RUN_CONTEXT_PACK_MAX_REFS} authorized refs.`);
|
|
217
|
-
}
|
|
218
212
|
const view = contextView(run);
|
|
219
213
|
const writableProjectIds = run.effective.executionAuthority === "planning"
|
|
220
214
|
? [] : run.effective.writeProjectIds;
|
|
221
|
-
const summaries = pointers.map((ref) => Object.freeze({
|
|
222
|
-
refId: ref.refId,
|
|
223
|
-
store: ref.store,
|
|
224
|
-
summary: ref.summary ?? `${ref.store} ${ref.refId}`,
|
|
225
|
-
digest: ref.digest
|
|
226
|
-
}));
|
|
227
215
|
const body = {
|
|
228
216
|
schemaVersion: RUN_CONTEXT_PACK_SCHEMA_VERSION,
|
|
229
217
|
identity: Object.freeze({
|
|
@@ -237,10 +225,10 @@ export function buildRunContextPack(store, taskId, runId) {
|
|
|
237
225
|
}),
|
|
238
226
|
snapshot: contextSnapshotRef(snapshot),
|
|
239
227
|
input: run.inputs[0].input,
|
|
240
|
-
authority: Object.freeze({ view,
|
|
228
|
+
authority: Object.freeze({ view, writableProjectIds }),
|
|
241
229
|
pointers,
|
|
242
|
-
|
|
243
|
-
|
|
230
|
+
deltaRefs: pointers.filter((ref) => run.inputs[0].input.deltaRefIds.includes(ref.refId))
|
|
231
|
+
.map(({ store, refId }) => ({ store, refId })),
|
|
244
232
|
completion: Object.freeze({
|
|
245
233
|
allowedActions: completionActions(view, run.effective.executionAuthority),
|
|
246
234
|
exactRunRef: `${taskId}/${runId}`
|
|
@@ -248,17 +236,12 @@ export function buildRunContextPack(store, taskId, runId) {
|
|
|
248
236
|
};
|
|
249
237
|
const digest = contextContentDigest(body);
|
|
250
238
|
const preliminaryBytes = Buffer.byteLength(JSON.stringify({ ...body, digest }), "utf8");
|
|
251
|
-
if (preliminaryBytes > RUN_CONTEXT_PACK_MAX_BYTES) {
|
|
252
|
-
throw new Error(`AgentRun Context Pack exceeds ${RUN_CONTEXT_PACK_MAX_BYTES} bytes.`);
|
|
253
|
-
}
|
|
254
239
|
const pack = Object.freeze({
|
|
255
240
|
...body,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
returnedBytes: preliminaryBytes,
|
|
261
|
-
truncated: false
|
|
241
|
+
inventory: Object.freeze({
|
|
242
|
+
refs: pointers.length,
|
|
243
|
+
frozenBytes: preliminaryBytes,
|
|
244
|
+
complete: true
|
|
262
245
|
}),
|
|
263
246
|
digest,
|
|
264
247
|
liveTaskState: readLiveTaskState(store, taskId)
|
|
@@ -303,10 +286,6 @@ export function expandRunContextRef(store, taskId, runId, refId, refStore) {
|
|
|
303
286
|
if (materialized === undefined || materialized.ref.digest !== authorized[0].digest) {
|
|
304
287
|
throw new Error(`AgentRun Context ref is unavailable or drifted: ${selector}.`);
|
|
305
288
|
}
|
|
306
|
-
const bytes = Buffer.byteLength(JSON.stringify(materialized.value), "utf8");
|
|
307
|
-
if (bytes > RUN_CONTEXT_EXPAND_MAX_BYTES) {
|
|
308
|
-
throw new Error(`AgentRun Context expansion exceeds ${RUN_CONTEXT_EXPAND_MAX_BYTES} bytes.`);
|
|
309
|
-
}
|
|
310
289
|
return Object.freeze({
|
|
311
290
|
ref: materialized.ref,
|
|
312
291
|
value: materialized.value,
|
|
@@ -330,7 +309,7 @@ export function buildRunContextDelta(store, taskId, runId, after) {
|
|
|
330
309
|
schemaVersion: 1,
|
|
331
310
|
after,
|
|
332
311
|
cursor: pack.digest,
|
|
333
|
-
refs: pack.
|
|
312
|
+
refs: pack.pointers.filter(ref => pack.deltaRefs.some(delta => delta.store === ref.store && delta.refId === ref.refId))
|
|
334
313
|
});
|
|
335
314
|
}
|
|
336
315
|
function requireExactRun(store, taskId, runId) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { chmodSync,
|
|
3
|
-
import {
|
|
2
|
+
import { chmodSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
5
5
|
import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
6
6
|
export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
@@ -13,27 +13,10 @@ export function withSessionContextPointer(text, environment) {
|
|
|
13
13
|
return [
|
|
14
14
|
`Read the Yui Session Manifest at ${manifest}.`,
|
|
15
15
|
"Follow its referenced Role Skills and role profile. It gives the exact Context load command.",
|
|
16
|
-
...(environment.YUI_SESSION_CLI === undefined ? [] : [
|
|
17
|
-
`Use this absolute CLI entry for Yui commands: ${environment.YUI_SESSION_CLI}`
|
|
18
|
-
]),
|
|
19
16
|
"",
|
|
20
17
|
text
|
|
21
18
|
].join("\n");
|
|
22
19
|
}
|
|
23
|
-
/**
|
|
24
|
-
* A managed Session wrapper answers exactly one question: which installation
|
|
25
|
-
* runs this command. It therefore carries only the resolved entry point, never
|
|
26
|
-
* a package or build identity. Current Sessions may survive a compatible
|
|
27
|
-
* installation relocation; this is not a cross-version runtime adapter.
|
|
28
|
-
*/
|
|
29
|
-
function renderSessionCli(entryPoint) {
|
|
30
|
-
return [
|
|
31
|
-
"#!/bin/sh",
|
|
32
|
-
`exec ${quoteShellWord(entryPoint.executable)} `
|
|
33
|
-
+ `${quoteShellWord(entryPoint.cliEntry)} \"$@\"`,
|
|
34
|
-
""
|
|
35
|
-
].join("\n");
|
|
36
|
-
}
|
|
37
20
|
/** Read back one immutable Session Manifest and verify its content digest. */
|
|
38
21
|
export function readSessionBootstrapManifest(path) {
|
|
39
22
|
const source = resolve(path);
|
|
@@ -67,8 +50,6 @@ export function readSessionBootstrapManifest(path) {
|
|
|
67
50
|
&& record.roleKind !== "leader"
|
|
68
51
|
&& record.roleKind !== "worker"
|
|
69
52
|
&& record.roleKind !== "reviewer")
|
|
70
|
-
|| record.controlPlane === null
|
|
71
|
-
|| typeof record.controlPlane !== "object"
|
|
72
53
|
|| !Array.isArray(record.skills)
|
|
73
54
|
|| record.roleProfileRef === null
|
|
74
55
|
|| typeof record.roleProfileRef !== "object"
|
|
@@ -80,8 +61,6 @@ export function readSessionBootstrapManifest(path) {
|
|
|
80
61
|
if (owner.scope === "task" && typeof owner.taskId !== "string") {
|
|
81
62
|
throw new Error("Task Session Manifest owner is invalid.");
|
|
82
63
|
}
|
|
83
|
-
const control = record.controlPlane;
|
|
84
|
-
requireText(control.sessionCliPath, "Session Manifest CLI path");
|
|
85
64
|
for (const skill of record.skills) {
|
|
86
65
|
if (skill === null || typeof skill !== "object") {
|
|
87
66
|
throw new Error("Session Manifest Skill entry is invalid.");
|
|
@@ -103,16 +82,6 @@ export function readSessionBootstrapManifest(path) {
|
|
|
103
82
|
}
|
|
104
83
|
export function materializeSessionBootstrap(input) {
|
|
105
84
|
const home = resolve(input.yuiHome);
|
|
106
|
-
// Provider command runners may rebuild PATH independently of the managed
|
|
107
|
-
// process environment, so a bare `yui` could resolve to another install or
|
|
108
|
-
// Home. The wrapper pins the resolved entry point instead. Package identity
|
|
109
|
-
// stays out of it: current Session authority and the CLI/Controller protocol
|
|
110
|
-
// boundary authorize the command.
|
|
111
|
-
const sessionCliContent = renderSessionCli(input.entryPoint);
|
|
112
|
-
const sessionCliDigest = digest(sessionCliContent);
|
|
113
|
-
const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
|
|
114
|
-
writeImmutableText(sessionCliPath, sessionCliContent);
|
|
115
|
-
chmodSync(sessionCliPath, 0o700);
|
|
116
85
|
const roleProfile = {
|
|
117
86
|
roleName: input.role.name,
|
|
118
87
|
roleKind: input.roleKind,
|
|
@@ -134,9 +103,6 @@ export function materializeSessionBootstrap(input) {
|
|
|
134
103
|
effectiveRevision: input.role.launchRevision,
|
|
135
104
|
roleKind: input.roleKind,
|
|
136
105
|
compatibilityDigest: sessionManifestCompatibilityDigest(input.role.name, input.roleKind, input.role),
|
|
137
|
-
controlPlane: {
|
|
138
|
-
sessionCliPath
|
|
139
|
-
},
|
|
140
106
|
skills: input.skills.map((skill) => Object.freeze({
|
|
141
107
|
id: skill.id,
|
|
142
108
|
path: skill.path,
|
|
@@ -151,10 +117,10 @@ export function materializeSessionBootstrap(input) {
|
|
|
151
117
|
loadCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name)
|
|
152
118
|
}
|
|
153
119
|
: {
|
|
154
|
-
loadCommand:
|
|
155
|
-
expandCommand:
|
|
120
|
+
loadCommand: `yui task run context ${quoteShellWord(`${input.owner.taskId}/<run-id>`)} --json`,
|
|
121
|
+
expandCommand: `yui task run context expand ${quoteShellWord(`${input.owner.taskId}/<run-id>`)} <ref-id> --store <store> --mode full --json`,
|
|
156
122
|
...(input.role.name === "leader" ? {
|
|
157
|
-
currentCommand:
|
|
123
|
+
currentCommand: `yui task context ${quoteShellWord(input.owner.taskId)} --json`
|
|
158
124
|
} : {})
|
|
159
125
|
}
|
|
160
126
|
};
|
|
@@ -164,7 +130,6 @@ export function materializeSessionBootstrap(input) {
|
|
|
164
130
|
return Object.freeze({
|
|
165
131
|
manifest,
|
|
166
132
|
manifestPath,
|
|
167
|
-
sessionCliPath,
|
|
168
133
|
roleProfilePath
|
|
169
134
|
});
|
|
170
135
|
}
|
|
@@ -182,67 +147,6 @@ function renderGlobalContextCommand(entryPoint, home, roleName) {
|
|
|
182
147
|
function quoteShellWord(value) {
|
|
183
148
|
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
184
149
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Retargets current managed wrappers to the resolved installation entry point.
|
|
187
|
-
* Only a valid Session Manifest may nominate a wrapper, and only the exact
|
|
188
|
-
* two-argument shell form generated here is changed.
|
|
189
|
-
*/
|
|
190
|
-
export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
|
|
191
|
-
const home = resolve(homeInput);
|
|
192
|
-
const currentSessionCli = renderSessionCli(entryPoint);
|
|
193
|
-
const manifestDirectory = resolve(join(home, "runtime", "session-manifests"));
|
|
194
|
-
const sessionCliDirectory = resolve(join(home, "runtime", "session-cli"));
|
|
195
|
-
if (!existsSync(manifestDirectory)) {
|
|
196
|
-
return Object.freeze({ refreshed: 0, current: 0, skipped: 0 });
|
|
197
|
-
}
|
|
198
|
-
const wrapperPaths = new Set();
|
|
199
|
-
let skipped = 0;
|
|
200
|
-
for (const name of readdirSync(manifestDirectory).filter((entry) => entry.endsWith(".json"))) {
|
|
201
|
-
const manifestPath = resolve(join(manifestDirectory, name));
|
|
202
|
-
try {
|
|
203
|
-
const manifest = readSessionBootstrapManifest(manifestPath);
|
|
204
|
-
if (manifestPath !== resolve(join(manifestDirectory, `${manifest.digest}.json`))) {
|
|
205
|
-
skipped += 1;
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const wrapperPath = resolve(manifest.controlPlane.sessionCliPath);
|
|
209
|
-
if (dirname(wrapperPath) !== sessionCliDirectory) {
|
|
210
|
-
skipped += 1;
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
wrapperPaths.add(wrapperPath);
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
// An invalid Manifest cannot nominate a wrapper for mutation.
|
|
217
|
-
skipped += 1;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
let refreshed = 0;
|
|
221
|
-
let current = 0;
|
|
222
|
-
for (const wrapperPath of wrapperPaths) {
|
|
223
|
-
if (!existsSync(wrapperPath)) {
|
|
224
|
-
skipped += 1;
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
const content = readFileSync(wrapperPath, "utf8");
|
|
228
|
-
if (content === currentSessionCli) {
|
|
229
|
-
current += 1;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (!isManagedSessionCli(content)) {
|
|
233
|
-
skipped += 1;
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
writeTextFileAtomically(wrapperPath, currentSessionCli);
|
|
237
|
-
chmodSync(wrapperPath, 0o700);
|
|
238
|
-
refreshed += 1;
|
|
239
|
-
}
|
|
240
|
-
return Object.freeze({ refreshed, current, skipped });
|
|
241
|
-
}
|
|
242
|
-
/** Match exactly the two shell-quoted words emitted by renderSessionCli. */
|
|
243
|
-
function isManagedSessionCli(content) {
|
|
244
|
-
return /^#!\/bin\/sh\nexec '(?:[^'\n]|'"'"')*' '(?:[^'\n]|'"'"')*' "\$@"\n$/u.test(content);
|
|
245
|
-
}
|
|
246
150
|
function writeImmutableText(path, content) {
|
|
247
151
|
writeTextFileAtomically(path, content);
|
|
248
152
|
chmodSync(path, 0o600);
|