@zq-silk/yui 0.6.16 → 0.7.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 +3 -7
- package/dist/cli.js +12 -33
- package/dist/commands/executionAuditCommands.js +19 -0
- package/dist/commands/globalRoleCommands.js +70 -0
- package/dist/commands/taskActor.js +3 -2
- package/dist/commands/taskCommands.js +160 -41
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskInputCommands.js +3 -2
- package/dist/commands/taskRoleRuntimeStatus.js +3 -3
- package/dist/context/contextSnapshot.js +228 -0
- package/dist/context/roleSessionContext.js +3 -1
- package/dist/context/runContextContract.js +162 -0
- package/dist/context/runContextPack.js +322 -0
- package/dist/context/sessionBootstrapManifest.js +81 -0
- package/dist/context/sessionProtocolIdentity.js +23 -0
- package/dist/controller/agentRuntimeObserver.js +6 -1
- package/dist/controller/controller.js +4 -3
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -145
- package/dist/controller/jobControl.js +2 -1
- package/dist/controller/runtime.js +83 -0
- package/dist/controller/runtimeHookRunFence.js +6 -2
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/executor/agentAdapter.js +7 -2
- package/dist/executor/agentExecutor.js +23 -0
- package/dist/executor/effectiveLaunch.js +24 -0
- package/dist/executor/executorRegistry.js +7 -1
- package/dist/executor/fileRoleLaunchPlanner.js +73 -27
- package/dist/lifecycle/exactRunTerminalization.js +2 -3
- package/dist/lifecycle/providerErrorClass.js +8 -3
- package/dist/observability/executionAudit.js +87 -2
- package/dist/repository/taskWorkspacePreparer.js +2 -2
- package/dist/run/agentRun.js +101 -16
- package/dist/run/providerRetry.js +167 -56
- package/dist/run/providerRetryConfig.js +5 -1
- package/dist/run/runControlRequest.js +50 -0
- package/dist/runtime/agentDriver.js +47 -0
- package/dist/runtime/agentHost.js +327 -0
- package/dist/runtime/builtinAgentDrivers.js +23 -1
- package/dist/runtime/builtinTranscriptObserver.js +4 -0
- package/dist/runtime/builtinTranscriptUsage.js +2 -0
- package/dist/runtime/exactControlPlane.js +2 -2
- package/dist/runtime/globalProcessExitStore.js +38 -0
- package/dist/runtime/launchBroker.js +95 -0
- package/dist/runtime/processExitObservation.js +60 -0
- package/dist/runtime/runtimeBinding.js +6 -0
- package/dist/runtime/runtimeObservation.js +27 -6
- package/dist/runtime/runtimeProjection.js +6 -3
- package/dist/runtime/runtimeStopReceipt.js +42 -0
- package/dist/runtime/sessionTerminationGuard.js +13 -0
- package/dist/runtime/tmuxAdapters.js +203 -220
- package/dist/scheduler/activeRoleRunDelivery.js +24 -3
- package/dist/scheduler/leaderWakeupProcessor.js +18 -60
- package/dist/scheduler/roleRunLiveness.js +61 -27
- package/dist/storage/migration/productionRegistry.js +264 -0
- package/dist/storage/sqliteSchema.js +23 -2
- package/dist/storage/sqliteStore.js +39 -2
- package/dist/storage/taskStore.js +54 -5
- package/dist/storage/upgrade/recordVersions.js +3 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +10 -0
- package/dist/task/taskRecordReference.js +1 -0
- package/dist/tmux/tmuxManager.js +15 -4
- package/dist/web/assets/client/components.js +1 -1
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +10 -5
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +61 -0
- package/skills/yui-worker/SKILL.md +82 -218
- package/dist/executor/managedClaudeRunner.js +0 -121
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { normalizedUniqueText, optionalText, requireIdentity, requirePositiveInteger, requireText, requireTimestamp } from "../domain/validation.js";
|
|
3
|
+
export const CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
4
|
+
export const CONTEXT_SNAPSHOT_MAX_RESOURCES = 256;
|
|
5
|
+
export const CONTEXT_SNAPSHOT_MAX_RESOURCE_BYTES = 4 * 1024 * 1024;
|
|
6
|
+
export const CONTEXT_SNAPSHOT_MAX_BYTES = 8 * 1024 * 1024;
|
|
7
|
+
export const CONTEXT_REF_LAYERS = ["L1", "L2", "L3", "L4"];
|
|
8
|
+
export const CONTEXT_SNAPSHOT_SCOPES = ["task", "workitem", "stage"];
|
|
9
|
+
export function createContextSnapshot(input) {
|
|
10
|
+
const identity = snapshotIdentity(input);
|
|
11
|
+
const refs = normalizeContextRefs(input.refs);
|
|
12
|
+
const resources = normalizeSnapshotResources(input.resources, refs);
|
|
13
|
+
const acceptRefs = Object.freeze([...normalizedUniqueText(input.acceptRefs, "Context Snapshot acceptance reference")]
|
|
14
|
+
.sort((left, right) => left.localeCompare(right)));
|
|
15
|
+
const repoCommit = optionalText(input.repoCommit, "Context Snapshot repository commit");
|
|
16
|
+
const parentRef = input.parentRef === undefined
|
|
17
|
+
? undefined
|
|
18
|
+
: validateContextSnapshotRef(input.parentRef);
|
|
19
|
+
if (parentRef !== undefined) {
|
|
20
|
+
if (parentRef.taskId !== identity.taskId) {
|
|
21
|
+
throw new Error("Context Snapshot parent belongs to another Task.");
|
|
22
|
+
}
|
|
23
|
+
if (parentRef.sequence >= identity.sequence) {
|
|
24
|
+
throw new Error("Context Snapshot parent sequence must precede its child.");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (input.frozenBy !== "leader" && input.frozenBy !== "controller") {
|
|
28
|
+
throw new Error("Context Snapshot frozenBy is invalid.");
|
|
29
|
+
}
|
|
30
|
+
const frozenAt = requireTimestamp(input.frozenAt.toISOString(), "Context Snapshot frozenAt");
|
|
31
|
+
const digest = contentDigest({
|
|
32
|
+
taskId: identity.taskId,
|
|
33
|
+
scope: identity.scope,
|
|
34
|
+
...(identity.scopeRef === undefined ? {} : { scopeRef: identity.scopeRef }),
|
|
35
|
+
sequence: identity.sequence,
|
|
36
|
+
refs,
|
|
37
|
+
resources,
|
|
38
|
+
...(repoCommit === undefined ? {} : { repoCommit }),
|
|
39
|
+
acceptRefs,
|
|
40
|
+
...(parentRef === undefined ? {} : { parentRef }),
|
|
41
|
+
frozenAt,
|
|
42
|
+
frozenBy: input.frozenBy
|
|
43
|
+
});
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
schemaVersion: CONTEXT_SNAPSHOT_SCHEMA_VERSION,
|
|
46
|
+
...identity,
|
|
47
|
+
digest,
|
|
48
|
+
refs,
|
|
49
|
+
resources,
|
|
50
|
+
...(repoCommit === undefined ? {} : { repoCommit }),
|
|
51
|
+
acceptRefs,
|
|
52
|
+
...(parentRef === undefined ? {} : { parentRef }),
|
|
53
|
+
frozenAt,
|
|
54
|
+
frozenBy: input.frozenBy
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function validateContextSnapshot(snapshot) {
|
|
58
|
+
if (snapshot.schemaVersion !== CONTEXT_SNAPSHOT_SCHEMA_VERSION) {
|
|
59
|
+
throw new Error("Context Snapshot must use schemaVersion 1.");
|
|
60
|
+
}
|
|
61
|
+
const identity = snapshotIdentity(snapshot);
|
|
62
|
+
const refs = normalizeContextRefs(snapshot.refs);
|
|
63
|
+
if (JSON.stringify(refs) !== JSON.stringify(snapshot.refs)) {
|
|
64
|
+
throw new Error("Context Snapshot refs must use canonical order.");
|
|
65
|
+
}
|
|
66
|
+
const resources = normalizeSnapshotResources(snapshot.resources, refs);
|
|
67
|
+
if (JSON.stringify(resources) !== JSON.stringify(snapshot.resources)) {
|
|
68
|
+
throw new Error("Context Snapshot resources must use canonical ref order.");
|
|
69
|
+
}
|
|
70
|
+
const acceptRefs = Object.freeze([...normalizedUniqueText(snapshot.acceptRefs, "Context Snapshot acceptance reference")]
|
|
71
|
+
.sort((left, right) => left.localeCompare(right)));
|
|
72
|
+
if (JSON.stringify(acceptRefs) !== JSON.stringify(snapshot.acceptRefs)) {
|
|
73
|
+
throw new Error("Context Snapshot acceptance refs must use canonical order.");
|
|
74
|
+
}
|
|
75
|
+
const repoCommit = optionalText(snapshot.repoCommit, "Context Snapshot repository commit");
|
|
76
|
+
const parentRef = snapshot.parentRef === undefined
|
|
77
|
+
? undefined
|
|
78
|
+
: validateContextSnapshotRef(snapshot.parentRef);
|
|
79
|
+
if (parentRef !== undefined
|
|
80
|
+
&& (parentRef.taskId !== identity.taskId || parentRef.sequence >= identity.sequence)) {
|
|
81
|
+
throw new Error("Context Snapshot parent is invalid.");
|
|
82
|
+
}
|
|
83
|
+
requireTimestamp(snapshot.frozenAt, "Context Snapshot frozenAt");
|
|
84
|
+
if (snapshot.frozenBy !== "leader" && snapshot.frozenBy !== "controller") {
|
|
85
|
+
throw new Error("Context Snapshot frozenBy is invalid.");
|
|
86
|
+
}
|
|
87
|
+
requireDigest(snapshot.digest, "Context Snapshot digest");
|
|
88
|
+
const expected = contentDigest({
|
|
89
|
+
taskId: identity.taskId,
|
|
90
|
+
scope: identity.scope,
|
|
91
|
+
...(identity.scopeRef === undefined ? {} : { scopeRef: identity.scopeRef }),
|
|
92
|
+
sequence: identity.sequence,
|
|
93
|
+
refs,
|
|
94
|
+
resources,
|
|
95
|
+
...(repoCommit === undefined ? {} : { repoCommit }),
|
|
96
|
+
acceptRefs,
|
|
97
|
+
...(parentRef === undefined ? {} : { parentRef }),
|
|
98
|
+
frozenAt: snapshot.frozenAt,
|
|
99
|
+
frozenBy: snapshot.frozenBy
|
|
100
|
+
});
|
|
101
|
+
if (snapshot.digest !== expected) {
|
|
102
|
+
throw new Error("Context Snapshot digest does not match its content.");
|
|
103
|
+
}
|
|
104
|
+
return snapshot;
|
|
105
|
+
}
|
|
106
|
+
export function contextSnapshotRef(snapshot) {
|
|
107
|
+
validateContextSnapshot(snapshot);
|
|
108
|
+
return Object.freeze({
|
|
109
|
+
schemaVersion: CONTEXT_SNAPSHOT_SCHEMA_VERSION,
|
|
110
|
+
id: snapshot.id,
|
|
111
|
+
taskId: snapshot.taskId,
|
|
112
|
+
scope: snapshot.scope,
|
|
113
|
+
...(snapshot.scopeRef === undefined ? {} : { scopeRef: snapshot.scopeRef }),
|
|
114
|
+
sequence: snapshot.sequence,
|
|
115
|
+
digest: snapshot.digest
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export function validateContextSnapshotRef(ref) {
|
|
119
|
+
if (ref.schemaVersion !== CONTEXT_SNAPSHOT_SCHEMA_VERSION) {
|
|
120
|
+
throw new Error("Context Snapshot ref must use schemaVersion 1.");
|
|
121
|
+
}
|
|
122
|
+
const identity = snapshotIdentity(ref);
|
|
123
|
+
requireDigest(ref.digest, "Context Snapshot ref digest");
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
schemaVersion: CONTEXT_SNAPSHOT_SCHEMA_VERSION,
|
|
126
|
+
...identity,
|
|
127
|
+
digest: ref.digest
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function contextContentDigest(value) {
|
|
131
|
+
return contentDigest(value);
|
|
132
|
+
}
|
|
133
|
+
function snapshotIdentity(input) {
|
|
134
|
+
const id = requireIdentity(input.id, "Context Snapshot id");
|
|
135
|
+
const taskId = requireIdentity(input.taskId, "Context Snapshot Task id");
|
|
136
|
+
if (!CONTEXT_SNAPSHOT_SCOPES.includes(input.scope)) {
|
|
137
|
+
throw new Error("Context Snapshot scope is invalid.");
|
|
138
|
+
}
|
|
139
|
+
const scopeRef = optionalText(input.scopeRef, "Context Snapshot scope ref");
|
|
140
|
+
if (input.scope === "task" && scopeRef !== undefined) {
|
|
141
|
+
throw new Error("A task Context Snapshot cannot carry scopeRef.");
|
|
142
|
+
}
|
|
143
|
+
if (input.scope !== "task" && scopeRef === undefined) {
|
|
144
|
+
throw new Error(`A ${input.scope} Context Snapshot requires scopeRef.`);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
id,
|
|
148
|
+
taskId,
|
|
149
|
+
scope: input.scope,
|
|
150
|
+
...(scopeRef === undefined ? {} : { scopeRef }),
|
|
151
|
+
sequence: requirePositiveInteger(input.sequence, "Context Snapshot sequence")
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function normalizeContextRefs(values) {
|
|
155
|
+
if (!Array.isArray(values))
|
|
156
|
+
throw new Error("Context Snapshot refs must be an array.");
|
|
157
|
+
const refs = values.map(validateContextRef).sort((left, right) => (contextRefKey(left).localeCompare(contextRefKey(right))));
|
|
158
|
+
const keys = refs.map(contextRefKey);
|
|
159
|
+
if (new Set(keys).size !== keys.length) {
|
|
160
|
+
throw new Error("Context Snapshot refs must be unique.");
|
|
161
|
+
}
|
|
162
|
+
return Object.freeze(refs);
|
|
163
|
+
}
|
|
164
|
+
function validateContextRef(value) {
|
|
165
|
+
if (!CONTEXT_REF_LAYERS.includes(value.layer)) {
|
|
166
|
+
throw new Error("Context ref layer is invalid.");
|
|
167
|
+
}
|
|
168
|
+
const store = requireIdentity(value.store, "Context ref store");
|
|
169
|
+
const refId = requireText(value.refId, "Context ref id");
|
|
170
|
+
const revision = requireText(value.revision, "Context ref revision");
|
|
171
|
+
const digest = requireDigest(value.digest, "Context ref digest");
|
|
172
|
+
const summary = optionalText(value.summary, "Context ref summary");
|
|
173
|
+
const evidenceOf = optionalText(value.evidenceOf, "Context ref evidence target");
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
layer: value.layer,
|
|
176
|
+
store,
|
|
177
|
+
refId,
|
|
178
|
+
revision,
|
|
179
|
+
digest,
|
|
180
|
+
...(summary === undefined ? {} : { summary }),
|
|
181
|
+
...(evidenceOf === undefined ? {} : { evidenceOf })
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function normalizeSnapshotResources(values, refs) {
|
|
185
|
+
if (!Array.isArray(values))
|
|
186
|
+
throw new Error("Context Snapshot resources must be an array.");
|
|
187
|
+
if (values.length > CONTEXT_SNAPSHOT_MAX_RESOURCES) {
|
|
188
|
+
throw new Error(`Context Snapshot exceeds ${CONTEXT_SNAPSHOT_MAX_RESOURCES} resources.`);
|
|
189
|
+
}
|
|
190
|
+
const byRef = new Map(refs.map((ref) => [contextRefKey(ref), ref]));
|
|
191
|
+
const resources = values.map((resource) => {
|
|
192
|
+
if (resource === null || typeof resource !== "object" || Array.isArray(resource)) {
|
|
193
|
+
throw new Error("Context Snapshot resource must be an object.");
|
|
194
|
+
}
|
|
195
|
+
const ref = validateContextRef(resource.ref);
|
|
196
|
+
const exact = byRef.get(contextRefKey(ref));
|
|
197
|
+
if (exact === undefined || contextContentDigest(resource.value) !== ref.digest) {
|
|
198
|
+
throw new Error(`Context Snapshot resource does not match its ref: ${ref.store}/${ref.refId}.`);
|
|
199
|
+
}
|
|
200
|
+
const serialized = JSON.stringify(resource.value);
|
|
201
|
+
if (serialized === undefined) {
|
|
202
|
+
throw new Error("Context Snapshot resource value is not JSON serializable.");
|
|
203
|
+
}
|
|
204
|
+
if (Buffer.byteLength(serialized, "utf8") > CONTEXT_SNAPSHOT_MAX_RESOURCE_BYTES) {
|
|
205
|
+
throw new Error(`Context Snapshot resource exceeds ${CONTEXT_SNAPSHOT_MAX_RESOURCE_BYTES} bytes.`);
|
|
206
|
+
}
|
|
207
|
+
return Object.freeze({ ref: exact, value: resource.value });
|
|
208
|
+
}).sort((left, right) => contextRefKey(left.ref).localeCompare(contextRefKey(right.ref)));
|
|
209
|
+
if (resources.length !== refs.length
|
|
210
|
+
|| new Set(resources.map(({ ref }) => contextRefKey(ref))).size !== refs.length) {
|
|
211
|
+
throw new Error("Context Snapshot resources must cover every ref exactly once.");
|
|
212
|
+
}
|
|
213
|
+
if (Buffer.byteLength(JSON.stringify(resources), "utf8") > CONTEXT_SNAPSHOT_MAX_BYTES) {
|
|
214
|
+
throw new Error(`Context Snapshot resources exceed ${CONTEXT_SNAPSHOT_MAX_BYTES} bytes.`);
|
|
215
|
+
}
|
|
216
|
+
return Object.freeze(resources);
|
|
217
|
+
}
|
|
218
|
+
function contextRefKey(ref) {
|
|
219
|
+
return [ref.layer, ref.store, ref.refId, ref.revision, ref.digest].join("\0");
|
|
220
|
+
}
|
|
221
|
+
function requireDigest(value, label) {
|
|
222
|
+
if (!/^[0-9a-f]{64}$/u.test(value))
|
|
223
|
+
throw new Error(`${label} must be SHA-256 hex.`);
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
function contentDigest(value) {
|
|
227
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
228
|
+
}
|
|
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { SYSTEM_LEADER_ROLE, SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
6
6
|
const BUILTIN_YUI_SKILLS = new Set([
|
|
7
|
+
"yui-runtime",
|
|
7
8
|
"yui-operator",
|
|
8
9
|
"yui-leader",
|
|
9
10
|
"yui-worker",
|
|
@@ -17,6 +18,7 @@ export function compileRoleSessionContext(yuiHome, role, owner, options = {}) {
|
|
|
17
18
|
const kind = roleSessionKind(role, owner, options.purpose ?? "execution");
|
|
18
19
|
const builtInSkillId = kind === "global" ? undefined : `yui-${kind}`;
|
|
19
20
|
const skillIds = unique([
|
|
21
|
+
"yui-runtime",
|
|
20
22
|
...(builtInSkillId === undefined ? [] : [builtInSkillId]),
|
|
21
23
|
...(role.skills ?? [])
|
|
22
24
|
]);
|
|
@@ -40,7 +42,7 @@ function renderDeveloperInstructions(kind, role, owner) {
|
|
|
40
42
|
].filter((value) => value !== null);
|
|
41
43
|
return [...core, ...profile].join("\n");
|
|
42
44
|
}
|
|
43
|
-
function roleSessionKind(role, owner, purpose) {
|
|
45
|
+
export function roleSessionKind(role, owner, purpose) {
|
|
44
46
|
if (owner.scope === "global") {
|
|
45
47
|
return role.name === SYSTEM_OPERATOR_ROLE ? "operator" : "global";
|
|
46
48
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { normalizedUniqueIdentities, requireIdentity, requireText } from "../domain/validation.js";
|
|
2
|
+
import { validateContextSnapshotRef } from "./contextSnapshot.js";
|
|
3
|
+
export const RUN_CONTEXT_PROTOCOL_VERSION = 1;
|
|
4
|
+
export const RUN_BOOTSTRAP_PROTOCOL = "yui-run/v1";
|
|
5
|
+
export const RUN_BOOTSTRAP_MAX_BYTES = 4 * 1024;
|
|
6
|
+
export const RUN_BOOTSTRAP_MAX_DELTAS = 16;
|
|
7
|
+
export const RUN_ACTIONS = [
|
|
8
|
+
"lead-task",
|
|
9
|
+
"leader-wake",
|
|
10
|
+
"execute-work-item",
|
|
11
|
+
"repair-work-item",
|
|
12
|
+
"review-round",
|
|
13
|
+
"review-delta",
|
|
14
|
+
"global-request"
|
|
15
|
+
];
|
|
16
|
+
export function createRunAssignment(input) {
|
|
17
|
+
const common = normalizeCommon(input);
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
schemaVersion: RUN_CONTEXT_PROTOCOL_VERSION,
|
|
20
|
+
...common,
|
|
21
|
+
...(input.directive === undefined
|
|
22
|
+
? {}
|
|
23
|
+
: { directive: requireText(input.directive, "Run assignment directive") })
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function validateRunAssignment(value) {
|
|
27
|
+
if (value.schemaVersion !== RUN_CONTEXT_PROTOCOL_VERSION) {
|
|
28
|
+
throw new Error("Run assignment must use schemaVersion 1.");
|
|
29
|
+
}
|
|
30
|
+
const normalized = createRunAssignment(value);
|
|
31
|
+
if (JSON.stringify(normalized) !== JSON.stringify(value)) {
|
|
32
|
+
throw new Error("Run assignment is not canonical.");
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
export function createRunBootstrapEnvelope(assignment) {
|
|
37
|
+
validateRunAssignment(assignment);
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
protocol: RUN_BOOTSTRAP_PROTOCOL,
|
|
40
|
+
runId: assignment.runId,
|
|
41
|
+
roleName: assignment.roleName,
|
|
42
|
+
purpose: assignment.purpose,
|
|
43
|
+
action: assignment.action,
|
|
44
|
+
subject: assignment.subject,
|
|
45
|
+
...(assignment.contextSnapshotRef === undefined
|
|
46
|
+
? {}
|
|
47
|
+
: { contextSnapshotRef: assignment.contextSnapshotRef }),
|
|
48
|
+
deltaRefIds: assignment.deltaRefIds
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function validateRunBootstrapEnvelope(value) {
|
|
52
|
+
if (value.protocol !== RUN_BOOTSTRAP_PROTOCOL) {
|
|
53
|
+
throw new Error("Run bootstrap protocol is unsupported.");
|
|
54
|
+
}
|
|
55
|
+
normalizeCommon(value);
|
|
56
|
+
const serialized = serializeRunBootstrapEnvelope(value);
|
|
57
|
+
if (Buffer.byteLength(serialized, "utf8") > RUN_BOOTSTRAP_MAX_BYTES) {
|
|
58
|
+
throw new Error("Run bootstrap envelope exceeds its protocol byte limit.");
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
export function serializeRunBootstrapEnvelope(value) {
|
|
63
|
+
if (value.protocol !== RUN_BOOTSTRAP_PROTOCOL) {
|
|
64
|
+
throw new Error("Run bootstrap protocol is unsupported.");
|
|
65
|
+
}
|
|
66
|
+
const normalized = normalizeCommon(value);
|
|
67
|
+
const subject = Object.entries(normalized.subject)
|
|
68
|
+
.map(([key, id]) => `${key}:${id}`)
|
|
69
|
+
.join(",");
|
|
70
|
+
const snapshot = normalized.contextSnapshotRef;
|
|
71
|
+
const lines = [
|
|
72
|
+
"Yui managed Run. Follow the Session Manifest and injected Skills.",
|
|
73
|
+
`${normalized.subject.taskId === undefined ? "" : `task=${normalized.subject.taskId} `}run=${normalized.runId} role=${normalized.roleName} action=${normalized.action}`,
|
|
74
|
+
`purpose=${normalized.purpose} subject=${subject || "global"} snapshot=${snapshot === undefined ? "none" : `${snapshot.id}@${snapshot.digest}`}`,
|
|
75
|
+
normalized.deltaRefIds.length === 0
|
|
76
|
+
? "delta=none"
|
|
77
|
+
: `delta=${normalized.deltaRefIds.join(",")}`,
|
|
78
|
+
"Load the exact Run context before acting; fail closed if it is unavailable or mismatched."
|
|
79
|
+
];
|
|
80
|
+
const serialized = lines.join("\n");
|
|
81
|
+
if (Buffer.byteLength(serialized, "utf8") > RUN_BOOTSTRAP_MAX_BYTES) {
|
|
82
|
+
throw new Error("Run bootstrap envelope exceeds its protocol byte limit.");
|
|
83
|
+
}
|
|
84
|
+
return serialized;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Bounded infrastructure-recovery input for an already-pushed Run. It resumes
|
|
88
|
+
* the provider-native transcript after Host/child replacement and never
|
|
89
|
+
* replays the Assignment or its directive.
|
|
90
|
+
*/
|
|
91
|
+
export function serializeRunHostRecoveryEnvelope(value) {
|
|
92
|
+
const normalized = normalizeCommon(value);
|
|
93
|
+
const serialized = [
|
|
94
|
+
"Yui managed Host recovery for an existing Run.",
|
|
95
|
+
`${normalized.subject.taskId === undefined ? "" : `task=${normalized.subject.taskId} `}run=${normalized.runId} role=${normalized.roleName}`,
|
|
96
|
+
"Resume the same native conversation from its latest durable state. Do not repeat completed work or replay the original Assignment.",
|
|
97
|
+
"Load the exact Run context or delta only if needed, then continue toward the existing workflow outcome."
|
|
98
|
+
].join("\n");
|
|
99
|
+
if (Buffer.byteLength(serialized, "utf8") > RUN_BOOTSTRAP_MAX_BYTES) {
|
|
100
|
+
throw new Error("Run Host recovery envelope exceeds its protocol byte limit.");
|
|
101
|
+
}
|
|
102
|
+
return serialized;
|
|
103
|
+
}
|
|
104
|
+
function normalizeCommon(input) {
|
|
105
|
+
if (!["execution", "review", "global"].includes(input.purpose)) {
|
|
106
|
+
throw new Error("Run assignment purpose is invalid.");
|
|
107
|
+
}
|
|
108
|
+
if (!RUN_ACTIONS.includes(input.action)) {
|
|
109
|
+
throw new Error("Run assignment action is invalid.");
|
|
110
|
+
}
|
|
111
|
+
const subject = normalizeSubject(input.subject);
|
|
112
|
+
const snapshot = input.contextSnapshotRef === undefined
|
|
113
|
+
? undefined
|
|
114
|
+
: validateContextSnapshotRef(input.contextSnapshotRef);
|
|
115
|
+
if (snapshot !== undefined && snapshot.taskId !== subject.taskId) {
|
|
116
|
+
throw new Error("Run assignment Context Snapshot belongs to another Task.");
|
|
117
|
+
}
|
|
118
|
+
if (input.purpose !== "global" && subject.taskId === undefined) {
|
|
119
|
+
throw new Error("A Task Run assignment requires a Task subject.");
|
|
120
|
+
}
|
|
121
|
+
if (input.purpose === "review" && subject.reviewRoundId === undefined) {
|
|
122
|
+
throw new Error("A review Run assignment requires a ReviewRound subject.");
|
|
123
|
+
}
|
|
124
|
+
const deltaRefIds = normalizedUniqueIdentities(input.deltaRefIds, "Run assignment delta ref");
|
|
125
|
+
if (deltaRefIds.length > RUN_BOOTSTRAP_MAX_DELTAS) {
|
|
126
|
+
throw new Error(`Run assignment supports at most ${RUN_BOOTSTRAP_MAX_DELTAS} delta refs.`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
runId: requireIdentity(input.runId, "Run assignment Run id"),
|
|
130
|
+
roleName: requireIdentity(input.roleName, "Run assignment Role name"),
|
|
131
|
+
purpose: input.purpose,
|
|
132
|
+
action: input.action,
|
|
133
|
+
subject,
|
|
134
|
+
...(snapshot === undefined ? {} : { contextSnapshotRef: snapshot }),
|
|
135
|
+
deltaRefIds
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function normalizeSubject(subject) {
|
|
139
|
+
const normalized = Object.fromEntries(Object.entries(subject).map(([key, value]) => [
|
|
140
|
+
key,
|
|
141
|
+
requireIdentity(value, `Run assignment ${key}`)
|
|
142
|
+
]));
|
|
143
|
+
const allowed = [
|
|
144
|
+
"taskId",
|
|
145
|
+
"workItemId",
|
|
146
|
+
"reviewRoundId",
|
|
147
|
+
"executionGroupId",
|
|
148
|
+
"executionLaneId"
|
|
149
|
+
];
|
|
150
|
+
if (Object.keys(normalized).some((key) => !allowed.includes(key))) {
|
|
151
|
+
throw new Error("Run assignment subject contains an unknown field.");
|
|
152
|
+
}
|
|
153
|
+
if ((normalized.executionGroupId === undefined) !== (normalized.executionLaneId === undefined)) {
|
|
154
|
+
throw new Error("Run assignment execution lineage is incomplete.");
|
|
155
|
+
}
|
|
156
|
+
if (normalized.taskId === undefined
|
|
157
|
+
&& (normalized.workItemId !== undefined || normalized.reviewRoundId !== undefined
|
|
158
|
+
|| normalized.executionGroupId !== undefined)) {
|
|
159
|
+
throw new Error("Run assignment child subject requires a Task id.");
|
|
160
|
+
}
|
|
161
|
+
return Object.freeze(normalized);
|
|
162
|
+
}
|