@zq-silk/yui 2.8.1 → 2.9.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/dist/cli/commandCatalog.js +35 -12
- package/dist/cli/interactionPolicy.js +2 -2
- package/dist/commands/durableJobCommands.js +50 -0
- package/dist/commands/projectCommands.js +4 -33
- package/dist/commands/scheduleCommands.js +45 -0
- package/dist/commands/taskActivationCommands.js +19 -5
- package/dist/commands/taskCommands.js +2 -1
- package/dist/commands/taskFactCommands.js +24 -12
- package/dist/commands/taskPublicationCommands.js +6 -3
- package/dist/commands/taskPublicationVerifyCommand.js +74 -28
- package/dist/context/runContextPack.js +24 -4
- package/dist/context/taskContext.js +52 -15
- package/dist/context/taskPhase.js +99 -0
- package/dist/controlPlaneCli.js +6 -0
- package/dist/controller/controller.js +35 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +16 -1
- package/dist/controller/jobControl.js +42 -12
- package/dist/controller/jobDefinitionControl.js +304 -0
- package/dist/controller/jobSupervisor.js +47 -32
- package/dist/controller/runtime.js +11 -2
- package/dist/controller/scheduleControl.js +176 -0
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/external/githubPublicationVerifier.js +14 -5
- package/dist/external/gitlabPublicationVerifier.js +2 -1
- package/dist/interaction/notificationPresentation.js +3 -3
- package/dist/interaction/operatorPresentation.js +5 -0
- package/dist/job/builtinJob.js +58 -0
- package/dist/job/durableJob.js +72 -49
- package/dist/job/jobDefinition.js +68 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/projectRefreshRunner.js +48 -0
- package/dist/repository/projectRefresh.js +58 -0
- package/dist/repository/taskWorkspacePreparer.js +2 -1
- package/dist/schedule/schedule.js +108 -0
- package/dist/scheduler/operatorEvent.js +2 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +8 -0
- package/dist/storage/contextRecords.js +8 -0
- package/dist/storage/jobSchema.js +15 -0
- package/dist/storage/maintenanceCaller.js +1 -1
- package/dist/storage/recordValidation.js +5 -0
- package/dist/storage/scheduleSchema.js +12 -0
- package/dist/storage/sqliteSchema.js +74 -24
- package/dist/storage/sqliteStore.js +110 -9
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +5 -0
- package/dist/storage/upgrade/jobDefinitionMigration.js +22 -0
- package/dist/task/publicationToolObservation.js +128 -0
- package/dist/task/taskActivationService.js +35 -0
- package/dist/web/assets/client/app/settings.js +7 -1
- package/dist/web/assets/client/i18n/en.js +61 -0
- package/dist/web/assets/client/i18n/zh-CN.js +61 -0
- package/dist/web/assets/client/index.js +2 -0
- package/dist/web/assets/client/views/jobs.js +222 -0
- package/dist/web/assets/client/views/task/page.js +11 -1
- package/dist/web/assets/shared/manageNav.js +1 -0
- package/dist/web/assets/shared/taskTabs.js +2 -2
- package/dist/web/assets/shell/settingsContent.js +3 -0
- package/dist/web/assets/styles/index.js +2 -0
- package/dist/web/assets/styles/views/jobs.js +19 -0
- package/dist/web/webJobs.js +52 -0
- package/dist/web/webServer.js +31 -0
- package/docs/cli-information-contract.md +43 -2
- package/docs/cli-information-contract.zh-CN.md +32 -1
- package/docs/job-runs.md +224 -0
- package/docs/release-workflow.md +43 -0
- package/docs/release-workflow.zh-CN.md +33 -0
- package/docs/schedules.md +132 -0
- package/docs/storage-baseline.md +18 -1
- package/docs/storage-baseline.zh-CN.md +15 -1
- package/docs/task-delivery.md +68 -2
- package/docs/task-delivery.zh-CN.md +33 -1
- package/docs/testing/verification-levels.md +6 -2
- package/docs/testing/verification-levels.zh-CN.md +6 -3
- package/package.json +4 -3
- package/skills/yui-leader/SKILL.md +18 -1
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/planning.md +9 -1
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-reviewer/VERSION +1 -1
- package/skills/yui-runtime/SKILL.md +9 -3
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/context-ordering.md +103 -0
- package/skills/yui-runtime/references/publication.md +44 -4
- package/skills/yui-worker/SKILL.md +5 -0
- package/skills/yui-worker/VERSION +1 -1
|
@@ -314,8 +314,8 @@ const taskChildren = [
|
|
|
314
314
|
name: "request", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
315
315
|
summary: "Record an Activation request. Every active Task gets a managed main workspace. empty adds no execution environment; scratch adopts a separate scratch environment; local requires a registered Resource and grant, not a Project ID.",
|
|
316
316
|
usage: "yui task activation request <task> --request-id <id> "
|
|
317
|
-
+ "--environment <empty|scratch|local:<resource>:<read|write>>",
|
|
318
|
-
options: ["--request-id", "--environment"],
|
|
317
|
+
+ "--environment <empty|scratch|local:<resource>:<read|write>> [--source-message <id> | --source-text <exact-input>]",
|
|
318
|
+
options: ["--request-id", "--environment", "--source-message", "--source-text"],
|
|
319
319
|
examples: [
|
|
320
320
|
"yui task activation request <task> --request-id start-1 --environment empty",
|
|
321
321
|
"yui task activation request <task> --request-id start-1 --environment scratch"
|
|
@@ -643,8 +643,8 @@ const taskChildren = [
|
|
|
643
643
|
{
|
|
644
644
|
name: "upsert", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
645
645
|
summary: "Create or immutably update an external PR/MR and its publication state.",
|
|
646
|
-
usage: "yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--head-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported
|
|
647
|
-
options: ["--project", "--provider", "--repository", "--kind", "--id", "--url", "--title", "--source-branch", "--target-branch", "--local-commit", "--head-commit", "--remote-commit", "--state", "--reported", "--
|
|
646
|
+
usage: "yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--head-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported] [--evidence <text>] [--merged-at <iso-timestamp>]",
|
|
647
|
+
options: ["--project", "--provider", "--repository", "--kind", "--id", "--url", "--title", "--source-branch", "--target-branch", "--local-commit", "--head-commit", "--remote-commit", "--state", "--reported", "--evidence", "--merged-at"]
|
|
648
648
|
},
|
|
649
649
|
{
|
|
650
650
|
name: "diff", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
@@ -660,8 +660,9 @@ const taskChildren = [
|
|
|
660
660
|
},
|
|
661
661
|
{
|
|
662
662
|
name: "verify", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
663
|
-
summary: "
|
|
664
|
-
usage: "yui task publication verify (<task>/<publication-id> | <task> <publication-id>)"
|
|
663
|
+
summary: "Record exact authorized tool evidence, or query the optional gh/glab adapter; verification is separate from Task coverage.",
|
|
664
|
+
usage: "yui task publication verify (<task>/<publication-id> | <task> <publication-id>) [--observation <json>]",
|
|
665
|
+
options: ["--observation"]
|
|
665
666
|
},
|
|
666
667
|
{
|
|
667
668
|
name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
@@ -1016,15 +1017,15 @@ const taskChildren = [
|
|
|
1016
1017
|
},
|
|
1017
1018
|
{
|
|
1018
1019
|
name: "brief", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
1019
|
-
summary: "Manage the Task
|
|
1020
|
+
summary: "Manage the Leader-maintained Task summary; live lifecycle and authority remain separate facts.",
|
|
1020
1021
|
sections: [{ id: "manage", title: "Commands", entries: ["show", "update"] }],
|
|
1021
1022
|
children: [
|
|
1022
1023
|
{ name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show the Task Brief.", usage: "yui task brief show <task> [--cursor <cursor>]", options: ["--cursor"] },
|
|
1023
1024
|
{
|
|
1024
1025
|
name: "update", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
1025
1026
|
summary: "Create or update the Task Brief.",
|
|
1026
|
-
usage: "yui task brief update <task> [--objective <text>] [--boundary <text> ...] [--approach <text>] [--focus <text>] [--leader-summary <text>]",
|
|
1027
|
-
options: ["--objective", "--boundary", "--approach", "--focus", "--leader-summary"]
|
|
1027
|
+
usage: "yui task brief update <task> --if-current <writeGuard> [--objective <text>] [--boundary <text> ...] [--approach <text>] [--focus <text>] [--leader-summary <text>]",
|
|
1028
|
+
options: ["--if-current", "--objective", "--boundary", "--approach", "--focus", "--leader-summary"]
|
|
1028
1029
|
}
|
|
1029
1030
|
]
|
|
1030
1031
|
},
|
|
@@ -1136,7 +1137,7 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1136
1137
|
] },
|
|
1137
1138
|
{ id: "workflow", title: "Workflow", entries: ["agent", "operator", "role", "inbox", "project", "task"] },
|
|
1138
1139
|
{ id: "configuration", title: "Configuration", entries: ["config"] },
|
|
1139
|
-
{ id: "operations", title: "Operations", entries: ["web", "controller", "session", "execution", "capability", "job", "jobs", "telemetry", "release"] },
|
|
1140
|
+
{ id: "operations", title: "Operations", entries: ["web", "controller", "session", "execution", "capability", "job", "schedule", "jobs", "telemetry", "release"] },
|
|
1140
1141
|
{ id: "resources", title: "Resources", entries: ["resources"] },
|
|
1141
1142
|
{ id: "internal", title: "Internal", entries: ["internal"] }
|
|
1142
1143
|
],
|
|
@@ -1641,15 +1642,37 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1641
1642
|
},
|
|
1642
1643
|
{
|
|
1643
1644
|
name: "job", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
|
|
1644
|
-
summary: "
|
|
1645
|
-
sections: [{ id: "manage", title: "Commands", entries: ["start", "get", "cancel", "acknowledge"] }],
|
|
1645
|
+
summary: "Manage stable Job definitions and exact execution Runs (not scheduler wakes).",
|
|
1646
|
+
sections: [{ id: "manage", title: "Commands", entries: ["define", "update", "list", "show", "run", "runs", "run-get", "logs", "run-cancel", "run-acknowledge", "start", "get", "cancel", "acknowledge"] }],
|
|
1646
1647
|
children: [
|
|
1648
|
+
...["define", "update", "list", "show", "run", "runs", "run-get", "logs", "run-cancel", "run-acknowledge"].map(name => ({
|
|
1649
|
+
name, discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
|
|
1650
|
+
summary: `${name} a stable Job or exact Run.`,
|
|
1651
|
+
usage: `yui job ${name} (--task <id>|--global)${name === "list" ? "" : " --job <id>"}`
|
|
1652
|
+
+ (name === "define" || name === "update" ? " --title <text> --input <configuration-json>" : "")
|
|
1653
|
+
+ (name === "update" ? " --revision <current>" : "")
|
|
1654
|
+
+ (name === "run" ? " --request-id <key> [--retry-of <run-id>]" : "")
|
|
1655
|
+
+ (["run-get", "logs", "run-cancel", "run-acknowledge"].includes(name) ? " --run <run-id>" : "")
|
|
1656
|
+
})),
|
|
1647
1657
|
{ name: "start", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Start a DurableJob under an explicit request identity.", usage: "yui job start --request-id <id> --task <id> --project <project> --head <sha> --workspace <dir> --step <name>=<command> [--step ...] [--owner task|work-item:<id>|integration-attempt:<id>] [--env <k=v>...]" },
|
|
1648
1658
|
{ name: "get", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Show a DurableJob record and its terminal result.", usage: "yui job get --task <id> --job <job-id>" },
|
|
1649
1659
|
{ name: "cancel", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Request cancellation of a running or queued DurableJob.", usage: "yui job cancel --task <id> --job <job-id>" },
|
|
1650
1660
|
{ name: "acknowledge", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Acknowledge an unknown-needs-attention DurableJob so Task lifecycle gates can proceed.", usage: "yui job acknowledge --task <id> --job <job-id>" }
|
|
1651
1661
|
]
|
|
1652
1662
|
},
|
|
1663
|
+
{
|
|
1664
|
+
name: "schedule", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
|
|
1665
|
+
summary: "Manage persistent time plans referencing scoped Jobs.",
|
|
1666
|
+
sections: [{ id: "manage", title: "Commands", entries: ["create", "list", "show", "update", "enable", "disable"] }],
|
|
1667
|
+
children: ["create", "list", "show", "update", "enable", "disable"].map(name => ({
|
|
1668
|
+
name, discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
|
|
1669
|
+
summary: `${name} a Schedule; disabling never cancels an admitted Run.`,
|
|
1670
|
+
usage: `yui schedule ${name} (--global|--task <id>)${name === "list" ? "" : " --schedule <id>"}`
|
|
1671
|
+
+ (name === "create" ? " --job <id> --rule <json>" : "")
|
|
1672
|
+
+ (name === "update" ? " --rule <json> [--job <id>]" : "")
|
|
1673
|
+
+ (["update", "enable", "disable"].includes(name) ? " --revision <observed>" : "")
|
|
1674
|
+
}))
|
|
1675
|
+
},
|
|
1653
1676
|
{
|
|
1654
1677
|
name: "jobs", discovery: { surface: "public", audiences: ["public", "operator"] },
|
|
1655
1678
|
summary: "Inspect scheduler wake and recovery records.",
|
|
@@ -185,7 +185,7 @@ export const INTERACTION_POLICIES = Object.freeze([
|
|
|
185
185
|
actionTarget: true,
|
|
186
186
|
statuses: ["draft"]
|
|
187
187
|
}],
|
|
188
|
-
trailingOptions: { "--request-id": "value", "--environment": "value" }
|
|
188
|
+
trailingOptions: { "--request-id": "value", "--environment": "value", "--source-message": "value", "--source-text": "value" }
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
191
|
commandPath: ["task", "activation", "cancel"],
|
|
@@ -615,7 +615,7 @@ export const INTERACTION_POLICIES = Object.freeze([
|
|
|
615
615
|
].map(([group, command]) => {
|
|
616
616
|
const trailingOptions = {};
|
|
617
617
|
if (group === "brief" && command === "update") {
|
|
618
|
-
Object.assign(trailingOptions, { "--objective": "value", "--boundary": "value", "--approach": "value", "--focus": "value", "--leader-summary": "value" });
|
|
618
|
+
Object.assign(trailingOptions, { "--if-current": "value", "--objective": "value", "--boundary": "value", "--approach": "value", "--focus": "value", "--leader-summary": "value" });
|
|
619
619
|
}
|
|
620
620
|
else if (group === "decision" && command === "record") {
|
|
621
621
|
Object.assign(trailingOptions, { "--title": "value", "--rationale": "value" });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
2
|
import { ensureFileTaskController } from "../controller/clientRuntime.js";
|
|
3
|
+
import { callFileTaskController } from "../controller/clientRuntime.js";
|
|
3
4
|
import { acknowledgeDurableJob, cancelDurableJob, getDurableJob, startDurableJob } from "../controller/jobClient.js";
|
|
4
5
|
import { resolveJobCaller, taskLocalActor } from "../task/taskAuthority.js";
|
|
5
6
|
/**
|
|
@@ -14,6 +15,9 @@ export const JOB_OWNER_USAGE = "task|work-item:<id>|integration-attempt:<id>";
|
|
|
14
15
|
*/
|
|
15
16
|
export async function runDurableJobCommand(args, options) {
|
|
16
17
|
const [command, ...rest] = args;
|
|
18
|
+
if (command !== undefined && ["define", "update", "list", "show", "run", "runs", "run-get", "run-cancel", "run-acknowledge", "logs"].includes(command)) {
|
|
19
|
+
return definitionCommand(command, rest, options);
|
|
20
|
+
}
|
|
17
21
|
if (command === "start")
|
|
18
22
|
return startJob(rest, options);
|
|
19
23
|
if (command === "get")
|
|
@@ -31,6 +35,52 @@ export async function runDurableJobCommand(args, options) {
|
|
|
31
35
|
+ "yui job cancel --task <id> --job <job-id>\n"
|
|
32
36
|
+ "yui job acknowledge --task <id> --job <job-id>");
|
|
33
37
|
}
|
|
38
|
+
async function definitionCommand(command, args, options) {
|
|
39
|
+
const flags = new Map();
|
|
40
|
+
for (let i = 0; i < args.length; i++) {
|
|
41
|
+
const flag = args[i];
|
|
42
|
+
if (!["--global", "--task", "--job", "--title", "--input", "--revision", "--request-id", "--retry-of", "--run"].includes(flag)
|
|
43
|
+
|| flags.has(flag))
|
|
44
|
+
throw usageError(`Unknown or repeated Job flag: ${flag}.`);
|
|
45
|
+
if (flag === "--global") {
|
|
46
|
+
flags.set(flag, "true");
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const value = args[++i];
|
|
50
|
+
if (value === undefined || value.startsWith("--"))
|
|
51
|
+
throw usageError(`Missing value: ${flag}.`);
|
|
52
|
+
flags.set(flag, value);
|
|
53
|
+
}
|
|
54
|
+
if (flags.has("--global") === flags.has("--task"))
|
|
55
|
+
throw usageError("Choose exactly one of --task <id> or --global.");
|
|
56
|
+
const taskId = flags.get("--task");
|
|
57
|
+
const caller = resolveJobCaller(options.environment, taskId ?? "");
|
|
58
|
+
const scope = taskId === undefined ? { kind: "global" } : { kind: "task", taskId };
|
|
59
|
+
let configuration;
|
|
60
|
+
if (flags.has("--input")) {
|
|
61
|
+
try {
|
|
62
|
+
configuration = JSON.parse(flags.get("--input"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw usageError("--input must be a JSON Job configuration.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const requestId = flags.get("--request-id");
|
|
69
|
+
if (command === "run" && !requestId)
|
|
70
|
+
throw usageError("job run requires --request-id.");
|
|
71
|
+
const params = {
|
|
72
|
+
op: command, scope, caller,
|
|
73
|
+
...(flags.has("--job") ? { id: flags.get("--job") } : {}),
|
|
74
|
+
...(flags.has("--title") ? { title: flags.get("--title") } : {}),
|
|
75
|
+
...(configuration === undefined ? {} : { configuration }),
|
|
76
|
+
...(flags.has("--revision") ? { expectedRevision: Number(flags.get("--revision")) } : {}),
|
|
77
|
+
...(flags.has("--run") ? { runId: flags.get("--run") } : {}),
|
|
78
|
+
...(flags.has("--retry-of") ? { retryOf: flags.get("--retry-of") } : {}),
|
|
79
|
+
...(command === "run" ? { trigger: { source: flags.has("--retry-of") ? "retry" : "manual", key: requestId } } : {})
|
|
80
|
+
};
|
|
81
|
+
const result = await callFileTaskController(options.home, "job.definition", params, { environment: options.environment });
|
|
82
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
83
|
+
}
|
|
34
84
|
async function startJob(args, options) {
|
|
35
85
|
const parsed = parseStartArgs(args);
|
|
36
86
|
await ensureFileTaskController(options.home, { environment: options.environment });
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { readdir, rename, rm } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { usageError } from "../errors/cliError.js";
|
|
5
5
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
6
6
|
import { boundedDocument, readOptions, recordPage } from "../output/boundedRead.js";
|
|
7
7
|
import { healCheckoutSwap, restoreCheckoutSwap, swapManagedCheckout } from "../repository/checkoutSwap.js";
|
|
8
|
-
import {
|
|
8
|
+
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
9
9
|
import { acquireProjectMaintenanceLock } from "../repository/projectMaintenanceLock.js";
|
|
10
|
+
import { refreshRegisteredProject } from "../repository/projectRefresh.js";
|
|
10
11
|
import { addProjectKnowledge, addKnowledgeProposal, assertProjectActive, decideKnowledgeProposal, createProject, findKnowledgeProposal, findKnowledgeProposalByFingerprint, knowledgeEvidenceDigest, knowledgeProposalFingerprint, managedProjectPath, planKnowledgeAcceptance, retireProject, retireProjectKnowledge, resolveProject, updateProjectKnowledge, updateProjectMetadata, validateProject, validateProjectName } from "../repository/project.js";
|
|
11
12
|
import { managedWorkspacesRoot } from "../storage/homeLayout.js";
|
|
12
13
|
import { projectActor } from "../task/taskAuthority.js";
|
|
@@ -90,37 +91,7 @@ async function refreshProject(args, store, options) {
|
|
|
90
91
|
throw usageError("Project refresh usage: yui project refresh <project>.");
|
|
91
92
|
}
|
|
92
93
|
const project = requireProject(store, args[0]);
|
|
93
|
-
|
|
94
|
-
if (project.remoteUrl === undefined) {
|
|
95
|
-
throw usageError(`Project refresh requires a remote URL: ${project.id}.`);
|
|
96
|
-
}
|
|
97
|
-
if (project.stableBranch !== project.developmentBranch) {
|
|
98
|
-
throw usageError(`Project refresh requires matching stable and development branches: ${project.id}.`);
|
|
99
|
-
}
|
|
100
|
-
// RFC Phase 1: hold the per-Project maintenance fence for the whole refresh
|
|
101
|
-
// so Task workspace preparation and other maintenance cannot interleave
|
|
102
|
-
// with the canonical branch/working-tree move.
|
|
103
|
-
const releaseMaintenance = await acquireProjectMaintenanceLock(store.rootDirectory(), project.id, { signal: options.signal });
|
|
104
|
-
try {
|
|
105
|
-
const current = requireUnchangedMaintenanceProject(store, project, "refresh");
|
|
106
|
-
const refreshed = await (options.git ?? new NodeGitWorkspace()).refresh({
|
|
107
|
-
repositoryPath: current.path,
|
|
108
|
-
remoteUrl: current.remoteUrl,
|
|
109
|
-
stableRef: current.stableBranch
|
|
110
|
-
});
|
|
111
|
-
return { project: current, ...refreshed };
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
if (error instanceof GitWorkspaceRefreshError) {
|
|
115
|
-
throw new CliError("RUNTIME_ERROR", error.message, undefined, {
|
|
116
|
-
projectId: project.id, refresh: error.result
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
throw error;
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
releaseMaintenance();
|
|
123
|
-
}
|
|
94
|
+
return refreshRegisteredProject(store, project.id, options);
|
|
124
95
|
}
|
|
125
96
|
/**
|
|
126
97
|
* RFC Phase 1: diagnose a managed Project's divergence without mutating it.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { usageError } from "../errors/cliError.js";
|
|
2
|
+
import { callFileTaskController } from "../controller/clientRuntime.js";
|
|
3
|
+
import { resolveJobCaller } from "../task/taskAuthority.js";
|
|
4
|
+
export async function runScheduleCommand(args, options) {
|
|
5
|
+
const [op, ...rest] = args;
|
|
6
|
+
if (op === undefined || !["create", "list", "show", "update", "enable", "disable"].includes(op)) {
|
|
7
|
+
throw usageError("Schedule command must be create, list, show, update, enable, or disable.");
|
|
8
|
+
}
|
|
9
|
+
const flags = new Map();
|
|
10
|
+
for (let i = 0; i < rest.length; i++) {
|
|
11
|
+
const flag = rest[i];
|
|
12
|
+
if (!["--global", "--task", "--schedule", "--job", "--rule", "--revision"].includes(flag) || flags.has(flag)) {
|
|
13
|
+
throw usageError(`Unknown or repeated Schedule flag: ${flag}.`);
|
|
14
|
+
}
|
|
15
|
+
if (flag === "--global") {
|
|
16
|
+
flags.set(flag, "true");
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const value = rest[++i];
|
|
20
|
+
if (value === undefined || value.startsWith("--"))
|
|
21
|
+
throw usageError(`Missing value: ${flag}.`);
|
|
22
|
+
flags.set(flag, value);
|
|
23
|
+
}
|
|
24
|
+
if (flags.has("--global") === flags.has("--task"))
|
|
25
|
+
throw usageError("Choose exactly one of --global or --task.");
|
|
26
|
+
const taskId = flags.get("--task");
|
|
27
|
+
let rule;
|
|
28
|
+
if (flags.has("--rule")) {
|
|
29
|
+
try {
|
|
30
|
+
rule = JSON.parse(flags.get("--rule"));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw usageError("--rule must be JSON.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const result = await callFileTaskController(options.home, "schedule.control", {
|
|
37
|
+
op, scope: taskId === undefined ? { kind: "global" } : { kind: "task", taskId },
|
|
38
|
+
caller: resolveJobCaller(options.environment, taskId ?? ""),
|
|
39
|
+
...(flags.has("--schedule") ? { id: flags.get("--schedule") } : {}),
|
|
40
|
+
...(flags.has("--job") ? { jobId: flags.get("--job") } : {}),
|
|
41
|
+
...(rule === undefined ? {} : { rule }),
|
|
42
|
+
...(flags.has("--revision") ? { expectedRevision: Number(flags.get("--revision")) } : {})
|
|
43
|
+
}, { environment: options.environment });
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
@@ -6,6 +6,7 @@ import { currentManagedRuntime } from "../runtime/managedCaller.js";
|
|
|
6
6
|
import { describeEnvironmentPlan } from "../task/taskActivation.js";
|
|
7
7
|
import { cancelTaskActivation, requestTaskActivation, taskActivationOperationRef } from "../task/taskActivationService.js";
|
|
8
8
|
import { taskLocalActor } from "../task/taskAuthority.js";
|
|
9
|
+
import { taskPhase } from "../context/taskPhase.js";
|
|
9
10
|
/**
|
|
10
11
|
* Explicit Activation requests.
|
|
11
12
|
*
|
|
@@ -20,7 +21,7 @@ export function runTaskActivationCommand(args, store, options) {
|
|
|
20
21
|
switch (command) {
|
|
21
22
|
case "request": return requestActivation(rest, store, options);
|
|
22
23
|
case "cancel": return cancelActivation(rest, store, options);
|
|
23
|
-
case "show": return showActivation(rest,
|
|
24
|
+
case "show": return store.readTransaction((reader) => showActivation(rest, reader));
|
|
24
25
|
default:
|
|
25
26
|
throw usageError(command === undefined
|
|
26
27
|
? "Task activation command is required."
|
|
@@ -28,7 +29,8 @@ export function runTaskActivationCommand(args, store, options) {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
const REQUEST_USAGE = "Task activation request usage: yui task activation request <task> "
|
|
31
|
-
+ "--request-id <id> --environment <empty|scratch|local:<resource>:<read|write
|
|
32
|
+
+ "--request-id <id> --environment <empty|scratch|local:<resource>:<read|write>> "
|
|
33
|
+
+ "[--source-message <message-id> | --source-text <exact-input>].";
|
|
32
34
|
/**
|
|
33
35
|
* Records the request and returns its reference immediately.
|
|
34
36
|
*
|
|
@@ -39,19 +41,26 @@ const REQUEST_USAGE = "Task activation request usage: yui task activation reques
|
|
|
39
41
|
* current then.
|
|
40
42
|
*/
|
|
41
43
|
function requestActivation(args, store, options) {
|
|
42
|
-
const parsed = parseTail(args, new Set(["--request-id", "--environment"]), REQUEST_USAGE);
|
|
44
|
+
const parsed = parseTail(args, new Set(["--request-id", "--environment", "--source-message", "--source-text"]), REQUEST_USAGE);
|
|
43
45
|
exactPositionals(parsed.positionals, 1, REQUEST_USAGE);
|
|
44
46
|
const taskId = parsed.positionals[0];
|
|
45
47
|
const requestId = requiredOption(parsed.options, "--request-id", REQUEST_USAGE);
|
|
46
48
|
const environmentPlan = parseEnvironmentPlan(requiredOption(parsed.options, "--environment", REQUEST_USAGE), REQUEST_USAGE);
|
|
47
49
|
const now = options.now?.() ?? new Date();
|
|
48
50
|
const caller = activationCaller(store, options, taskId);
|
|
51
|
+
const sourceMessageId = parsed.options.get("--source-message");
|
|
52
|
+
const sourceText = parsed.options.get("--source-text");
|
|
53
|
+
if (caller.actorId.startsWith("task:") && sourceMessageId === undefined && sourceText === undefined) {
|
|
54
|
+
throw usageError("Leader activation requires --source-message or --source-text with the exact new user input. Read the original; do not substitute a Brief summary.", REQUEST_USAGE);
|
|
55
|
+
}
|
|
49
56
|
const result = attempt(REQUEST_USAGE, () => requestTaskActivation(store, {
|
|
50
57
|
taskId,
|
|
51
58
|
requestId,
|
|
52
59
|
actorId: caller.actorId,
|
|
53
60
|
authorityRef: caller.authorityRef,
|
|
54
61
|
environmentPlan,
|
|
62
|
+
sourceMessageId, sourceText,
|
|
63
|
+
sourceNativeSessionId: caller.nativeSessionId,
|
|
55
64
|
...(caller.planningRunId === undefined ? {} : { callerRunId: caller.planningRunId })
|
|
56
65
|
}, now));
|
|
57
66
|
// An immediate request has nothing left to wait for, so ask the Controller to
|
|
@@ -64,12 +73,14 @@ function requestActivation(args, store, options) {
|
|
|
64
73
|
void options.runtime?.notifyMailboxChanged({ kind: "task", taskId });
|
|
65
74
|
}
|
|
66
75
|
const request = result.request;
|
|
76
|
+
const source = taskPhase(store, { ...requireTask(store, taskId), activationRequest: request }).activation?.source;
|
|
67
77
|
const lines = [
|
|
68
78
|
`${result.created ? "Requested" : "Existing"} activation ${result.operationRef}`,
|
|
69
79
|
`Task: ${taskId} (draft)`,
|
|
70
80
|
`Start: ${result.startMode}${result.afterPlanningRun === undefined ? "" : ` after ${taskId}/${result.afterPlanningRun}`}`,
|
|
71
81
|
`Environment: ${describeEnvironmentPlan(request.environmentPlan)}`,
|
|
72
82
|
`Disposition: ${request.disposition}`,
|
|
83
|
+
...(source?.read === undefined ? [] : [`Source: yui ${source.read}`]),
|
|
73
84
|
result.startMode === "after-planning-turn"
|
|
74
85
|
? "This reference is returned now; the request is adopted after the planning Turn ends."
|
|
75
86
|
: `Adopt it with: yui task activate ${taskId}`
|
|
@@ -81,7 +92,8 @@ function requestActivation(args, store, options) {
|
|
|
81
92
|
...(result.afterPlanningRun === undefined
|
|
82
93
|
? {}
|
|
83
94
|
: { afterPlanningRun: result.afterPlanningRun }),
|
|
84
|
-
request
|
|
95
|
+
request,
|
|
96
|
+
source
|
|
85
97
|
});
|
|
86
98
|
}
|
|
87
99
|
/** A cancelled request is never adopted, even if the same id is retried. */
|
|
@@ -139,7 +151,8 @@ function showActivation(args, store) {
|
|
|
139
151
|
`Requested at: ${request.requestedAt}`,
|
|
140
152
|
...settledLines
|
|
141
153
|
];
|
|
142
|
-
return output(`${lines.join("\n")}\n`, { task, request, settledRequests: settled
|
|
154
|
+
return output(`${lines.join("\n")}\n`, { task, request, settledRequests: settled,
|
|
155
|
+
phase: taskPhase(store, task) });
|
|
143
156
|
}
|
|
144
157
|
/**
|
|
145
158
|
* Resolves who is requesting, and whether they are running inside this Task's
|
|
@@ -161,6 +174,7 @@ function activationCaller(store, options, taskId) {
|
|
|
161
174
|
: store.getRun(taskId, runtime.currentRunId);
|
|
162
175
|
return {
|
|
163
176
|
actorId: `task:${taskId}/role:${SYSTEM_LEADER_ROLE}`,
|
|
177
|
+
nativeSessionId: runtime.nativeSessionId,
|
|
164
178
|
// Non-secret binding fingerprint of the live Session, matching the Job
|
|
165
179
|
// boundary: reattaching the same Session keeps it, replacing it does not.
|
|
166
180
|
authorityRef: fingerprint([
|
|
@@ -665,6 +665,7 @@ function routeUserSubmission(tx, task, actor, body, intent, now, submissionKey,
|
|
|
665
665
|
requestId: `submit-${message.id}`,
|
|
666
666
|
actorId: identity.actorId,
|
|
667
667
|
authorityRef: identity.authorityRef,
|
|
668
|
+
sourceMessageId: message.id,
|
|
668
669
|
environmentPlan: routing.environmentPlan
|
|
669
670
|
}, now);
|
|
670
671
|
activationRef = taskActivationOperationRef(task.id, `submit-${message.id}`);
|
|
@@ -7008,7 +7009,7 @@ function taskWakeInspectionCommand(args, store) {
|
|
|
7008
7009
|
const windowEvents = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id) || reasonEvent(e));
|
|
7009
7010
|
const events = history ? windowEvents : windowEvents.filter(e => referenced("event", e.id)
|
|
7010
7011
|
|| reasonEvent(e) || ["run.completed", "run.failed", "run.cancelled"].includes(e.type));
|
|
7011
|
-
const messages = wakeMessages(wake, store.listMessages(task.id));
|
|
7012
|
+
const messages = wakeMessages(wake, store.listMessages(task.id), history);
|
|
7012
7013
|
const allRuns = store.listRuns(task.id);
|
|
7013
7014
|
const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
|
|
7014
7015
|
for (const run of allRuns)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
|
|
2
|
-
import { assertContextRecordReadable } from "../context/taskContext.js";
|
|
2
|
+
import { assertContextRecordReadable, resolveContextReader } from "../context/taskContext.js";
|
|
3
3
|
import { taskRecordList } from "./taskContextCommand.js";
|
|
4
4
|
import { materialize } from "../context/taskContext.js";
|
|
5
5
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
@@ -8,15 +8,22 @@ import { dataError, usageError } from "../errors/cliError.js";
|
|
|
8
8
|
import { createMilestone } from "../milestone/milestone.js";
|
|
9
9
|
import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, leaderMailbox, notifyMailbox, output, parseMultiValueTail, parseTail, presentTime, recordTaskEvent, requiredOption, requiredText, requireTask, taskActor, taskMailbox, taskRef } from "./taskCommandSupport.js";
|
|
10
10
|
import { failureCapabilitiesCommand } from "../runtime/agentFailureContext.js";
|
|
11
|
+
import { taskBriefGuard, taskPhase } from "../context/taskPhase.js";
|
|
11
12
|
export function taskBriefCommand(args, store, options) {
|
|
12
13
|
const [command, ...rest] = args;
|
|
13
14
|
if (command === "show") {
|
|
14
15
|
exactPositionals(rest, 1, "Task brief show usage: yui task brief show <task>.");
|
|
15
|
-
const task =
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const { task, brief, writeGuard, phase } = store.readTransaction(reader => {
|
|
17
|
+
const task = requireTask(reader, rest[0]);
|
|
18
|
+
assertContextRecordReadable(reader, task.id, "task-brief", task.id, options.environment);
|
|
19
|
+
const caller = resolveContextReader(reader, options.environment ?? {});
|
|
20
|
+
const shared = caller === undefined || caller.roleName === "leader";
|
|
21
|
+
return { task, brief: reader.getTaskBrief(task.id),
|
|
22
|
+
writeGuard: shared ? taskBriefGuard(reader, task) : undefined,
|
|
23
|
+
phase: shared ? taskPhase(reader, task) : undefined };
|
|
24
|
+
});
|
|
18
25
|
if (brief === null) {
|
|
19
|
-
return output(`Task ${task.id} has no brief.\n`, { taskId: task.id, brief: null });
|
|
26
|
+
return output(`Task ${task.id} has no brief.\n${writeGuard === undefined ? "" : `Write guard: ${writeGuard}\n`}`, { taskId: task.id, brief: null, writeGuard, phase });
|
|
20
27
|
}
|
|
21
28
|
const timeZone = store.getConfig().timeZone;
|
|
22
29
|
const working = task.status === "draft" || task.status === "active";
|
|
@@ -32,12 +39,13 @@ export function taskBriefCommand(args, store, options) {
|
|
|
32
39
|
`${working ? "Current focus" : "Last working focus"}: ${brief.currentFocus}`,
|
|
33
40
|
`${working ? "Leader summary" : "Last working summary"}: ${brief.leaderSummary}`,
|
|
34
41
|
`Updated by: ${brief.updatedBy}`,
|
|
35
|
-
`Updated at: ${presentTime(brief.updatedAt, timeZone)}
|
|
36
|
-
|
|
42
|
+
`Updated at: ${presentTime(brief.updatedAt, timeZone)}`,
|
|
43
|
+
...(writeGuard === undefined ? [] : [`Write guard: ${writeGuard}`])
|
|
44
|
+
].join("\n").concat("\n"), { taskId: task.id, brief, writeGuard, phase });
|
|
37
45
|
}
|
|
38
46
|
if (command === "update") {
|
|
39
|
-
const usage = "Task brief update usage: yui task brief update <task> [--objective <text>] [--boundary <text> ...] [--approach <text>] [--focus <text>] [--leader-summary <text>].";
|
|
40
|
-
const parsed = parseMultiValueTail(rest, new Set(["--objective", "--approach", "--focus", "--leader-summary"]), new Set(["--boundary"]), usage);
|
|
47
|
+
const usage = "Task brief update usage: yui task brief update <task> --if-current <writeGuard> [--objective <text>] [--boundary <text> ...] [--approach <text>] [--focus <text>] [--leader-summary <text>].";
|
|
48
|
+
const parsed = parseMultiValueTail(rest, new Set(["--objective", "--approach", "--focus", "--leader-summary", "--if-current"]), new Set(["--boundary"]), usage);
|
|
41
49
|
exactPositionals(parsed.positionals, 1, usage);
|
|
42
50
|
const hasObjective = parsed.options.has("--objective");
|
|
43
51
|
const hasApproach = parsed.options.has("--approach");
|
|
@@ -53,6 +61,9 @@ export function taskBriefCommand(args, store, options) {
|
|
|
53
61
|
assertTaskOpen(task);
|
|
54
62
|
const existing = tx.getTaskBrief(task.id);
|
|
55
63
|
const updatedBy = taskActor(tx, options, task.id);
|
|
64
|
+
if (parsed.options.get("--if-current") !== taskBriefGuard(tx, task)) {
|
|
65
|
+
throw usageError(`Task context or Brief changed, or --if-current is missing; read task brief show ${task.id}, reconcile new input, then retry with its writeGuard.`);
|
|
66
|
+
}
|
|
56
67
|
const brief = existing === null
|
|
57
68
|
? createTaskBrief({
|
|
58
69
|
objective: requiredText(parsed.options.get("--objective"), "--objective"),
|
|
@@ -244,9 +255,10 @@ export function taskEventCommand(args, store, options = {}) {
|
|
|
244
255
|
exactPositionals(rest, 2, "Task event show usage: yui task event show <task> <event>.");
|
|
245
256
|
const task = requireTask(store, rest[0]);
|
|
246
257
|
assertContextRecordReadable(store, task.id, "task-event", rest[1], options.environment);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
258
|
+
const event = store.queryContextRecords(task.id, {
|
|
259
|
+
family: "task-event", ids: [rest[1]], limit: 1
|
|
260
|
+
}).records[0]?.value;
|
|
261
|
+
if (event === undefined)
|
|
250
262
|
throw dataError(`Event not found: ${rest[1]}.`);
|
|
251
263
|
const timeZone = store.getConfig().timeZone;
|
|
252
264
|
const capabilitiesCommand = event.type === "runtime.agent-error" && event.payload.roleName
|
|
@@ -35,7 +35,7 @@ export function runPublicationCommand(args, store, options) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
function upsertPublication(args, store, options) {
|
|
38
|
-
const usage = "Task publication upsert usage: yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--head-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported
|
|
38
|
+
const usage = "Task publication upsert usage: yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--head-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported] [--evidence <text>] [--merged-at <iso-timestamp>].";
|
|
39
39
|
const parsed = parseUpsertArgs(args, usage);
|
|
40
40
|
exactPositionals(parsed.positionals, 1, usage);
|
|
41
41
|
const task = requireTask(store, parsed.positionals[0]);
|
|
@@ -173,8 +173,9 @@ function parseVerification(parsed, usage) {
|
|
|
173
173
|
if (parsed.flags.has("--reported") && parsed.flags.has("--verified")) {
|
|
174
174
|
throw usageError("--reported and --verified are mutually exclusive.", usage);
|
|
175
175
|
}
|
|
176
|
-
if (parsed.flags.has("--verified"))
|
|
177
|
-
|
|
176
|
+
if (parsed.flags.has("--verified")) {
|
|
177
|
+
throw usageError("Use task publication verify with a provider read or --observation tool evidence; upsert cannot assert --verified.");
|
|
178
|
+
}
|
|
178
179
|
if (parsed.flags.has("--reported"))
|
|
179
180
|
return "reported";
|
|
180
181
|
return undefined;
|
|
@@ -210,6 +211,8 @@ function inheritPublicationInput(existing, input) {
|
|
|
210
211
|
&& normalizeCommitForComparison(input.headCommit) !== existing.headCommit)
|
|
211
212
|
|| input.projectId !== existing.projectId
|
|
212
213
|
|| input.externalKind !== existing.externalKind
|
|
214
|
+
|| (input.externalUrl !== undefined && input.externalUrl !== existing.externalUrl)
|
|
215
|
+
|| (input.targetBranch !== undefined && input.targetBranch !== existing.targetBranch)
|
|
213
216
|
|| stateChanged
|
|
214
217
|
|| remoteCommitChanged
|
|
215
218
|
|| evidenceChanged
|