@zq-silk/yui 0.8.1 → 0.8.3
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 +27 -28
- package/README.md +57 -46
- package/dist/cli/commandCatalog.js +57 -17
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +106 -27
- package/dist/commands/taskCommands.js +458 -77
- package/dist/commands/taskCompletionGate.js +152 -0
- package/dist/context/runContextPack.js +19 -4
- package/dist/context/sessionBootstrapManifest.js +1 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
- package/dist/controller/resourceInventory.js +9 -5
- package/dist/controller/runtime.js +80 -7
- package/dist/controller/runtimeLaunchCoordinator.js +18 -78
- package/dist/controller/structuredProviderObservation.js +273 -0
- package/dist/executor/agentAdapter.js +40 -0
- package/dist/executor/agentExecutor.js +31 -7
- package/dist/executor/executorRegistry.js +11 -49
- package/dist/executor/fileRoleLaunchPlanner.js +115 -37
- package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
- package/dist/repository/gitWorkspace.js +7 -4
- package/dist/repository/taskBaseFreshness.js +4 -2
- package/dist/run/agentRun.js +4 -4
- package/dist/runtime/agentHost.js +767 -158
- package/dist/runtime/builtinAgentDrivers.js +1 -5
- package/dist/runtime/codexAppServerRuntime.js +67 -60
- package/dist/runtime/exactControlPlane.js +7 -2
- package/dist/runtime/index.js +6 -2
- package/dist/runtime/launchBroker.js +30 -8
- package/dist/runtime/providerAuthorityFence.js +24 -0
- package/dist/runtime/providerControl.js +63 -0
- package/dist/runtime/providerRecoveryDecision.js +55 -0
- package/dist/runtime/providerRuntimeIdentity.js +269 -19
- package/dist/runtime/runtimeBinding.js +20 -11
- package/dist/runtime/structuredProviderHost.js +476 -0
- package/dist/runtime/tmuxAdapters.js +143 -42
- package/dist/scheduler/activeRoleRunDelivery.js +206 -120
- package/dist/scheduler/leaderWakeupProcessor.js +141 -16
- package/dist/scheduler/wakeReason.js +1 -0
- package/dist/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/sqliteStore.js +2 -0
- package/dist/storage/taskStore.js +3 -1
- package/dist/task/completionReadiness.js +43 -0
- package/dist/task/nextAction.js +6 -4
- package/dist/task/publicationReference.js +1 -0
- package/dist/tmux/tmuxManager.js +1 -1
- package/dist/workItem/workItem.js +12 -0
- package/dist/workspace/workItemChangeSetManager.js +2 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -3
- package/skills/yui-runtime/SKILL.md +7 -2
|
@@ -2,7 +2,159 @@ import { usageError } from "../errors/cliError.js";
|
|
|
2
2
|
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
3
3
|
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
4
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
5
|
+
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
5
6
|
import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
|
|
7
|
+
/**
|
|
8
|
+
* Verify the one supported ancestry waiver before `task complete` mutates any
|
|
9
|
+
* durable state. The explicit Publication must be the current verified merged
|
|
10
|
+
* record, bind the exact reviewed physical Task head, and name an
|
|
11
|
+
* ancestry-divergent commit with the exact same Git tree.
|
|
12
|
+
*/
|
|
13
|
+
export async function verifyTaskCompletionPublishedTree(taskId, publicationId, store, options = {}) {
|
|
14
|
+
const task = store.getTask(taskId);
|
|
15
|
+
if (task === null)
|
|
16
|
+
throw usageError(`Task not found: ${taskId}.`);
|
|
17
|
+
if (task.status !== "active") {
|
|
18
|
+
throw usageError(`Task is not active: ${task.id}.`);
|
|
19
|
+
}
|
|
20
|
+
const publication = requireCurrentVerifiedPublication(store, task.id, publicationId);
|
|
21
|
+
const binding = task.projectBindings.find(({ projectId }) => (projectId === publication.projectId));
|
|
22
|
+
if (binding === undefined) {
|
|
23
|
+
throw usageError(`Publication ${publication.id} Project is not bound to Task ${task.id}: `
|
|
24
|
+
+ `${publication.projectId}.`);
|
|
25
|
+
}
|
|
26
|
+
if (publication.localCommit === undefined || publication.remoteCommit === undefined) {
|
|
27
|
+
throw usageError(`Publication ${publication.id} must record exact local and remote commits.`);
|
|
28
|
+
}
|
|
29
|
+
const workspace = requireTaskWorkspace(store, task);
|
|
30
|
+
const latestReview = latestTaskFinalReview(store, task.id);
|
|
31
|
+
if (latestReview === undefined
|
|
32
|
+
|| latestReview.status !== "completed"
|
|
33
|
+
|| latestReview.taskCandidate === undefined) {
|
|
34
|
+
throw usageError(`Task ${task.id} requires a latest completed Task-final Review before accepting a published tree.`);
|
|
35
|
+
}
|
|
36
|
+
const git = options.git ?? new NodeGitWorkspace();
|
|
37
|
+
const actualHeads = new Map();
|
|
38
|
+
for (const taskBinding of task.projectBindings) {
|
|
39
|
+
const entry = workspaceProjectEntry(workspace, taskBinding.projectId);
|
|
40
|
+
if (entry === undefined || entry.access !== "write") {
|
|
41
|
+
throw usageError(`Task ${task.id} has no writable managed main workspace for Project ${taskBinding.projectId}.`);
|
|
42
|
+
}
|
|
43
|
+
const reviewedCommit = latestReview.taskCandidate.projects.find(({ projectId }) => (projectId === taskBinding.projectId))?.commit;
|
|
44
|
+
if (reviewedCommit === undefined) {
|
|
45
|
+
throw usageError(`Task-final Review ${latestReview.id} omitted Project ${taskBinding.projectId}.`);
|
|
46
|
+
}
|
|
47
|
+
const actualCommit = (await git.inspect(entry.path, "HEAD")).baseCommit;
|
|
48
|
+
if (actualCommit !== reviewedCommit) {
|
|
49
|
+
throw usageError(`Task-final Review ${latestReview.id} does not match Task head `
|
|
50
|
+
+ `${taskBinding.projectId}@${actualCommit}.`);
|
|
51
|
+
}
|
|
52
|
+
actualHeads.set(taskBinding.projectId, actualCommit);
|
|
53
|
+
}
|
|
54
|
+
if (actualHeads.size !== latestReview.taskCandidate.projects.length) {
|
|
55
|
+
throw usageError(`Task-final Review ${latestReview.id} Project set does not match Task ${task.id}.`);
|
|
56
|
+
}
|
|
57
|
+
const entry = workspaceProjectEntry(workspace, publication.projectId);
|
|
58
|
+
const localCommit = actualHeads.get(publication.projectId);
|
|
59
|
+
if (publication.localCommit !== localCommit) {
|
|
60
|
+
throw usageError(`Publication ${publication.id} local commit ${publication.localCommit} `
|
|
61
|
+
+ `does not match Task head ${localCommit}.`);
|
|
62
|
+
}
|
|
63
|
+
let remoteCommit;
|
|
64
|
+
let localTree;
|
|
65
|
+
let remoteTree;
|
|
66
|
+
try {
|
|
67
|
+
remoteCommit = (await git.inspect(entry.path, publication.remoteCommit)).baseCommit;
|
|
68
|
+
[localTree, remoteTree] = await Promise.all([
|
|
69
|
+
git.resolveTree(entry.path, localCommit),
|
|
70
|
+
git.resolveTree(entry.path, remoteCommit)
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw usageError(`Publication ${publication.id} commit/tree evidence is unavailable: `
|
|
75
|
+
+ `${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
if (remoteCommit !== publication.remoteCommit) {
|
|
78
|
+
throw usageError(`Publication ${publication.id} remote commit changed: expected `
|
|
79
|
+
+ `${publication.remoteCommit}, found ${remoteCommit}.`);
|
|
80
|
+
}
|
|
81
|
+
const [localContainsRemote, remoteContainsLocal] = await Promise.all([
|
|
82
|
+
git.isAncestor(entry.path, remoteCommit, localCommit),
|
|
83
|
+
git.isAncestor(entry.path, localCommit, remoteCommit)
|
|
84
|
+
]);
|
|
85
|
+
if (localContainsRemote || remoteContainsLocal) {
|
|
86
|
+
throw usageError(`Publication ${publication.id} is not ancestry-divergent from Task head ${localCommit}; `
|
|
87
|
+
+ "use normal Task completion.");
|
|
88
|
+
}
|
|
89
|
+
if (localTree !== remoteTree) {
|
|
90
|
+
throw usageError(`Publication ${publication.id} Git trees differ: `
|
|
91
|
+
+ `${localCommit}^{tree}=${localTree}, ${remoteCommit}^{tree}=${remoteTree}.`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
taskId: task.id,
|
|
95
|
+
projectId: publication.projectId,
|
|
96
|
+
publicationId: publication.id,
|
|
97
|
+
reviewRoundId: latestReview.id,
|
|
98
|
+
localCommit,
|
|
99
|
+
remoteCommit,
|
|
100
|
+
tree: localTree
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Re-derive every durable half of the asynchronous Git proof under the Task
|
|
104
|
+
* completion transaction. The physical heads are the CLI snapshot captured
|
|
105
|
+
* immediately before mutation; any record or head drift fails closed. */
|
|
106
|
+
export function assertTaskCompletionPublishedTreeProof(store, task, publicationId, proof, actualCandidate) {
|
|
107
|
+
if (proof === undefined
|
|
108
|
+
|| proof.taskId !== task.id
|
|
109
|
+
|| proof.publicationId !== publicationId) {
|
|
110
|
+
throw usageError(`Published-tree completion proof is missing or mismatched for ${task.id}/${publicationId}.`);
|
|
111
|
+
}
|
|
112
|
+
const publication = requireCurrentVerifiedPublication(store, task.id, publicationId);
|
|
113
|
+
if (publication.projectId !== proof.projectId
|
|
114
|
+
|| publication.localCommit !== proof.localCommit
|
|
115
|
+
|| publication.remoteCommit !== proof.remoteCommit) {
|
|
116
|
+
throw usageError(`Publication evidence changed before Task completion: ${publication.id}.`);
|
|
117
|
+
}
|
|
118
|
+
const latestReview = latestTaskFinalReview(store, task.id);
|
|
119
|
+
if (latestReview === undefined
|
|
120
|
+
|| latestReview.id !== proof.reviewRoundId
|
|
121
|
+
|| latestReview.status !== "completed"
|
|
122
|
+
|| latestReview.taskCandidate === undefined
|
|
123
|
+
|| !sameTaskCandidate(latestReview.taskCandidate, actualCandidate)) {
|
|
124
|
+
throw usageError(`Task-final Review evidence changed before published-tree completion: ${task.id}.`);
|
|
125
|
+
}
|
|
126
|
+
const actualCommit = actualCandidate.projects.find(({ projectId }) => (projectId === proof.projectId))?.commit;
|
|
127
|
+
if (actualCommit !== proof.localCommit) {
|
|
128
|
+
throw usageError(`Task head changed before published-tree completion: `
|
|
129
|
+
+ `${proof.projectId}@${actualCommit ?? "missing"}.`);
|
|
130
|
+
}
|
|
131
|
+
return proof;
|
|
132
|
+
}
|
|
133
|
+
function requireCurrentVerifiedPublication(store, taskId, publicationId) {
|
|
134
|
+
const publication = store.getPublicationReference(taskId, publicationId);
|
|
135
|
+
if (publication === null) {
|
|
136
|
+
throw usageError(`Publication reference not found: ${taskId}/${publicationId}.`);
|
|
137
|
+
}
|
|
138
|
+
const current = store.findPublicationReferenceByExternalKey(publicationExternalKey(publication));
|
|
139
|
+
if (current === null || current.taskId !== taskId || current.id !== publication.id) {
|
|
140
|
+
throw usageError(`Publication ${publication.id} is not the current unsuperseded record for its external identity.`);
|
|
141
|
+
}
|
|
142
|
+
if (publication.state !== "merged" || publication.verification !== "verified") {
|
|
143
|
+
throw usageError(`Publication ${publication.id} must be merged and verified before Task completion.`);
|
|
144
|
+
}
|
|
145
|
+
return publication;
|
|
146
|
+
}
|
|
147
|
+
function latestTaskFinalReview(store, taskId) {
|
|
148
|
+
return store.listReviewRounds(taskId)
|
|
149
|
+
.filter((round) => (round.scope ?? "work-item") === "task")
|
|
150
|
+
.sort((left, right) => (left.id.localeCompare(right.id, undefined, { numeric: true })))
|
|
151
|
+
.at(-1);
|
|
152
|
+
}
|
|
153
|
+
function sameTaskCandidate(left, right) {
|
|
154
|
+
return left.projects.length === right.projects.length
|
|
155
|
+
&& left.projects.every((project, index) => (project.projectId === right.projects[index]?.projectId
|
|
156
|
+
&& project.commit === right.projects[index]?.commit));
|
|
157
|
+
}
|
|
6
158
|
/**
|
|
7
159
|
* Reconcile configured remote baselines before a Task completion attempt.
|
|
8
160
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
1
2
|
import { RUN_BOOTSTRAP_MAX_DELTAS } from "./runContextContract.js";
|
|
2
3
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
|
|
3
4
|
export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
|
|
@@ -123,11 +124,12 @@ export function buildRunContextPack(store, taskId, runId) {
|
|
|
123
124
|
});
|
|
124
125
|
return pack;
|
|
125
126
|
}
|
|
126
|
-
export function expandRunContextRef(store, taskId, runId, refId) {
|
|
127
|
+
export function expandRunContextRef(store, taskId, runId, refId, refStore) {
|
|
127
128
|
const pack = buildRunContextPack(store, taskId, runId);
|
|
128
|
-
const authorized = pack.pointers.filter((ref) => ref.refId === refId);
|
|
129
|
+
const authorized = pack.pointers.filter((ref) => (ref.refId === refId && (refStore === undefined || ref.store === refStore)));
|
|
130
|
+
const selector = refStore === undefined ? refId : `${refStore}/${refId}`;
|
|
129
131
|
if (authorized.length !== 1) {
|
|
130
|
-
throw new Error(`Run Context ref is not uniquely authorized: ${
|
|
132
|
+
throw new Error(`Run Context ref is not uniquely authorized: ${selector}.`);
|
|
131
133
|
}
|
|
132
134
|
const run = requireExactRun(store, taskId, runId);
|
|
133
135
|
const snapshotRef = run.assignment.contextSnapshotRef;
|
|
@@ -135,7 +137,7 @@ export function expandRunContextRef(store, taskId, runId, refId) {
|
|
|
135
137
|
? collectAuthorizedContext(store, run).find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])))
|
|
136
138
|
: store.getContextSnapshot(taskId, snapshotRef.id)?.resources.find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])));
|
|
137
139
|
if (materialized === undefined || materialized.ref.digest !== authorized[0].digest) {
|
|
138
|
-
throw new Error(`Run Context ref is unavailable or drifted: ${
|
|
140
|
+
throw new Error(`Run Context ref is unavailable or drifted: ${selector}.`);
|
|
139
141
|
}
|
|
140
142
|
const bytes = Buffer.byteLength(JSON.stringify(materialized.value), "utf8");
|
|
141
143
|
if (bytes > RUN_CONTEXT_EXPAND_MAX_BYTES) {
|
|
@@ -261,6 +263,19 @@ function collectAuthorizedContext(store, run) {
|
|
|
261
263
|
for (const message of store.listMessages(task.id).slice(-16)) {
|
|
262
264
|
result.push(materialize("L4", "task-message", message.id, message));
|
|
263
265
|
}
|
|
266
|
+
const publishedTreeAuthorizations = [];
|
|
267
|
+
const events = store.listEvents(task.id);
|
|
268
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
269
|
+
const event = events[index];
|
|
270
|
+
if (event.type === "task.completed" || event.type === "task.reopened")
|
|
271
|
+
break;
|
|
272
|
+
if (event.type === TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT) {
|
|
273
|
+
publishedTreeAuthorizations.push(event);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const event of publishedTreeAuthorizations.reverse().slice(-16)) {
|
|
277
|
+
result.push(materialize("L4", "task-event", event.id, event));
|
|
278
|
+
}
|
|
264
279
|
for (const request of store.listOpenInputRequests([task.id])) {
|
|
265
280
|
result.push(materialize("L4", "input-request", request.id, request));
|
|
266
281
|
}
|
|
@@ -57,7 +57,7 @@ export function materializeSessionBootstrap(input) {
|
|
|
57
57
|
}
|
|
58
58
|
: {
|
|
59
59
|
loadCommand: `\"${sessionCliPath}\" task run context \"$YUI_TASK_ID/<run-id>\" --json`,
|
|
60
|
-
expandCommand: `\"${sessionCliPath}\" task run context expand \"$YUI_TASK_ID/<run-id>\" <ref-id> --mode full --json`
|
|
60
|
+
expandCommand: `\"${sessionCliPath}\" task run context expand \"$YUI_TASK_ID/<run-id>\" <ref-id> --store <store> --mode full --json`
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
const manifest = Object.freeze({ ...body, digest: digest(body) });
|