@zq-silk/yui 0.13.7 → 0.13.8

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/role/role.js CHANGED
@@ -19,19 +19,22 @@ export function createRole(taskId, name, bindings, activeAgentId, workspace, now
19
19
  const owner = createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess);
20
20
  return validateTaskRole({
21
21
  ...owner,
22
- taskId: requireSafeIdentity(taskId, "Task id"),
23
- status: "idle"
22
+ schemaVersion: 4,
23
+ taskId: requireSafeIdentity(taskId, "Task id")
24
24
  });
25
25
  }
26
26
  export function createGlobalRole(name, bindings, activeAgentId, workspace, now, profile = {}, defaultAccess = "write") {
27
- return createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess);
27
+ return validateGlobalRole({
28
+ ...createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess),
29
+ schemaVersion: 3
30
+ });
28
31
  }
29
32
  export function copyGlobalRoleToTaskRole(globalRole, taskId, now, name = globalRole.name) {
30
33
  validateGlobalRole(globalRole);
31
34
  const timestamp = now.toISOString();
32
35
  return validateTaskRole({
33
36
  ...cloneProfile(globalRole),
34
- schemaVersion: 3,
37
+ schemaVersion: 4,
35
38
  launchRevision: 1,
36
39
  defaultAccess: globalRole.defaultAccess,
37
40
  taskId: requireSafeIdentity(taskId, "Task id"),
@@ -39,7 +42,6 @@ export function copyGlobalRoleToTaskRole(globalRole, taskId, now, name = globalR
39
42
  activeAgentId: globalRole.activeAgentId,
40
43
  agentBindings: cloneBindings(globalRole.agentBindings),
41
44
  workspace: globalRole.workspace,
42
- status: "idle",
43
45
  createdAt: timestamp,
44
46
  updatedAt: timestamp
45
47
  });
@@ -76,12 +78,6 @@ export function updateRole(role, patch, now) {
76
78
  : role.launchRevision + 1;
77
79
  return validateTaskRole(updated);
78
80
  }
79
- export function updateRoleStatus(role, status, now) {
80
- validateTaskRole(role);
81
- if (!isRoleStatus(status))
82
- throw new Error(`Role status is invalid: ${status}.`);
83
- return { ...role, status, updatedAt: now.toISOString() };
84
- }
85
81
  export function switchActiveRoleAgent(role, sessions, targetAgentId, runtime, now) {
86
82
  validateRoleOwner(role);
87
83
  const normalizedTarget = requireSafeIdentity(targetAgentId, "Target Agent id");
@@ -175,8 +171,6 @@ export function validateGlobalRole(role) {
175
171
  }
176
172
  export function validateTaskRole(role) {
177
173
  requireSafeIdentity(role.taskId, "Task id");
178
- if (!isRoleStatus(role.status))
179
- throw new Error(`Role status is invalid: ${role.status}.`);
180
174
  return validateRoleOwner(role);
181
175
  }
182
176
  function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile, defaultAccess) {
@@ -189,9 +183,8 @@ function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile,
189
183
  mappedBindings[binding.agentId] = binding;
190
184
  }
191
185
  const timestamp = now.toISOString();
192
- return validateGlobalRole({
186
+ return {
193
187
  ...cloneProfile(profile),
194
- schemaVersion: 3,
195
188
  launchRevision: 1,
196
189
  defaultAccess,
197
190
  name: requireSafeIdentity(name, "Role name"),
@@ -200,11 +193,13 @@ function createRoleOwner(name, bindings, activeAgentId, workspace, now, profile,
200
193
  workspace: requireText(workspace, "Role workspace"),
201
194
  createdAt: timestamp,
202
195
  updatedAt: timestamp
203
- });
196
+ };
204
197
  }
205
198
  function validateRoleOwner(role) {
206
- if (role.schemaVersion !== 3)
199
+ const expectedSchemaVersion = "taskId" in role ? 4 : 3;
200
+ if (role.schemaVersion !== expectedSchemaVersion) {
207
201
  throw new Error("Role schema version is invalid.");
202
+ }
208
203
  if (!Number.isSafeInteger(role.launchRevision) || role.launchRevision < 1) {
209
204
  throw new Error("Role launch revision must be a positive integer.");
210
205
  }
@@ -333,6 +328,3 @@ function requireText(value, label) {
333
328
  throw new Error(`${label} is required.`);
334
329
  return normalized;
335
330
  }
336
- function isRoleStatus(value) {
337
- return ["idle", "running", "detached", "exited", "failed"].includes(value);
338
- }
@@ -14,7 +14,7 @@ export { formatRuntimeLaunchDiagnostic, redactLaunchArgument, redactLaunchText,
14
14
  export { DEFAULT_FORCED_GRACE_MS, DEFAULT_GRACEFUL_GRACE_MS, terminateSessionOwners } from "./sessionTerminationGuard.js";
15
15
  export { ProviderContinuationReconciliationService } from "./providerContinuationReconciliationService.js";
16
16
  export { codexNotificationBoundary, codexAppServerErrorIsMissing, CodexAppServerRequestError, CodexAppServerRuntime } from "./codexAppServerRuntime.js";
17
- export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn, currentProviderActivation, currentProviderAuthority, currentProviderConversation, endProviderActivation, markProviderTurnDeliveryUnknown, rebindProviderRuntimeRun, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, validateProviderRuntimeBinding } from "./providerRuntimeIdentity.js";
17
+ export { createProviderRuntimeBinding, acceptProviderTurn, beginProviderTurn, currentProviderActivation, currentProviderAuthority, currentProviderConversation, endProviderActivation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, transferProviderAuthority, updateProviderConversationRecoverability, validateProviderRuntimeBinding } from "./providerRuntimeIdentity.js";
18
18
  export { FencedProviderControl } from "./providerControl.js";
19
19
  export { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
20
20
  export { decideProviderRecovery } from "./providerRecoveryDecision.js";
@@ -1,10 +1,9 @@
1
1
  export function createProviderRuntimeBinding(input) {
2
2
  const startedAt = timestamp(input.startedAt, "Provider Activation startedAt");
3
3
  return validateProviderRuntimeBinding({
4
- schemaVersion: 2,
4
+ schemaVersion: 3,
5
5
  providerNamespace: identity(input.providerNamespace, "Provider namespace"),
6
6
  accountScope: identity(input.accountScope, "Provider account scope"),
7
- runId: identity(input.runId, "Run id"),
8
7
  currentConversationEpoch: 1,
9
8
  conversations: [{
10
9
  conversationId: identity(input.conversationId, "Provider Conversation id"),
@@ -40,17 +39,6 @@ export function currentProviderActivation(binding) {
40
39
  const conversation = currentProviderConversation(binding);
41
40
  return [...binding.activations].reverse().find((entry) => (entry.conversationId === conversation.conversationId && entry.status === "active")) ?? null;
42
41
  }
43
- /** Rebinds the live Conversation state to the next Yui Run without resetting authority. */
44
- export function rebindProviderRuntimeRun(raw, runId) {
45
- const binding = validateProviderRuntimeBinding(raw);
46
- if (providerTurnIsActive(binding.turn)) {
47
- throw new Error("Provider Runtime cannot bind another Run while a Turn is unsettled.");
48
- }
49
- return validateProviderRuntimeBinding({
50
- ...binding,
51
- runId: identity(runId, "Run id")
52
- });
53
- }
54
42
  export function startProviderActivation(raw, input) {
55
43
  const binding = validateProviderRuntimeBinding(raw);
56
44
  if (currentProviderActivation(binding) !== null) {
@@ -158,8 +146,10 @@ export function transferProviderAuthority(raw, input) {
158
146
  }
159
147
  export function beginProviderTurn(raw, input) {
160
148
  const binding = validateProviderRuntimeBinding(raw);
149
+ const runId = identity(input.runId, "Run id");
161
150
  const attemptId = identity(input.attemptId, "Provider input attempt id");
162
- if (binding.turn?.attemptId === attemptId
151
+ if (binding.turn?.runId === runId
152
+ && binding.turn.attemptId === attemptId
163
153
  && binding.turn.authorityEpoch === input.authorityEpoch
164
154
  && binding.turn.status === "submitting") {
165
155
  return binding;
@@ -176,6 +166,7 @@ export function beginProviderTurn(raw, input) {
176
166
  return validateProviderRuntimeBinding({
177
167
  ...binding,
178
168
  turn: {
169
+ runId,
179
170
  attemptId,
180
171
  authorityEpoch: input.authorityEpoch,
181
172
  status: "submitting",
@@ -350,11 +341,10 @@ export function supersedeProviderConversation(raw, input) {
350
341
  });
351
342
  }
352
343
  export function validateProviderRuntimeBinding(value) {
353
- if (value.schemaVersion !== 2)
354
- throw new Error("Provider Runtime Binding schemaVersion must be 2.");
344
+ if (value.schemaVersion !== 3)
345
+ throw new Error("Provider Runtime Binding schemaVersion must be 3.");
355
346
  identity(value.providerNamespace, "Provider namespace");
356
347
  identity(value.accountScope, "Provider account scope");
357
- identity(value.runId, "Run id");
358
348
  integer(value.currentConversationEpoch, 1, "Current Provider Conversation epoch");
359
349
  if (!Array.isArray(value.conversations) || value.conversations.length === 0) {
360
350
  throw new Error("Provider Runtime Binding requires a Conversation.");
@@ -459,6 +449,7 @@ export function validateProviderRuntimeBinding(value) {
459
449
  return value;
460
450
  }
461
451
  function validateProviderTurn(turn, currentAuthorityEpoch) {
452
+ identity(turn.runId, "Run id");
462
453
  identity(turn.attemptId, "Provider input attempt id");
463
454
  integer(turn.authorityEpoch, 1, "Provider Turn authority epoch");
464
455
  if (turn.authorityEpoch > currentAuthorityEpoch) {
@@ -497,17 +497,9 @@ async function processActiveRunContinuation(store, delivery, task, role, run, no
497
497
  };
498
498
  }
499
499
  // Managed input has one write path: a new structured Turn at a settled
500
- // boundary. Corrections retain their priority while the current Turn runs.
500
+ // boundary. AgentHost owns Provider serialization; a busy send releases the
501
+ // claim and leaves the durable correction pending for the next pass.
501
502
  const mode = "followup";
502
- if (session.status === "running") {
503
- return {
504
- taskId: task.id,
505
- roleName: role.name,
506
- runId: run.id,
507
- status: "skipped",
508
- reason: "not-ready"
509
- };
510
- }
511
503
  if (writer === null) {
512
504
  return {
513
505
  taskId: task.id,
@@ -25,8 +25,6 @@ export function repairOrphanedActiveTasks(store, now, selection) {
25
25
  const run = store.getActiveAgentRun(task.id, role.name);
26
26
  return run === null ? [] : [run];
27
27
  });
28
- const hasInFlightTurn = roles.some((role) => store.hasInFlightTurn(task.id, role.name));
29
- const hasLeaderInFlightTurn = store.hasInFlightTurn(task.id, "leader");
30
28
  const leaderTarget = { kind: "role", taskId: task.id, roleName: "leader" };
31
29
  const leaderMailbox = store.getWorkMailbox(leaderTarget);
32
30
  const projection = projectTaskExecution({
@@ -41,8 +39,7 @@ export function repairOrphanedActiveTasks(store, now, selection) {
41
39
  && !activeRuns.some(({ roleName }) => roleName === "leader")
42
40
  && activeRuns.some(({ roleName }) => roleName !== "leader")
43
41
  && projection.status === "waiting-on-agents";
44
- if ((queuedAlongsideActiveSibling ? hasLeaderInFlightTurn : hasInFlightTurn)
45
- || store.hasOpenInputRequest(task.id)
42
+ if (store.hasOpenInputRequest(task.id)
46
43
  || store.getLeaderFailure(task.id) !== null
47
44
  || (projection.status !== "needs-leader-action" && !queuedAlongsideActiveSibling)
48
45
  || hasUnclaimedLeaderWork(store, task.id, leaderMailbox)) {
@@ -42,11 +42,6 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
42
42
  results.push({ taskId: task.id, status: "skipped", reason: "busy" });
43
43
  continue;
44
44
  }
45
- if (typeof store.hasInFlightTurn === "function"
46
- && store.hasInFlightTurn(task.id, role.name)) {
47
- results.push({ taskId: task.id, status: "skipped", reason: "busy" });
48
- continue;
49
- }
50
45
  const reopening = wakeup.reasons.includes("task-reopened");
51
46
  let existingSession = store.getRoleSession(task.id, role.name, reopening ? undefined : role.effective.agentId);
52
47
  let effectiveSession = existingSession;
@@ -786,7 +786,7 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
786
786
  continue;
787
787
  const evidenceKey = [
788
788
  kind,
789
- stallEvidenceKey(candidate.role.status, candidate.session?.status),
789
+ stallEvidenceKey(candidate.session?.status),
790
790
  classification,
791
791
  ...(candidate.role.name === "leader"
792
792
  ? [leaderStallEvidence(store, candidate.task.id, observed, now, windowMs)]
@@ -1001,8 +1001,8 @@ function leaderStallEvidence(store, taskId, observed, now, windowMs) {
1001
1001
  : "recent";
1002
1002
  return `downstream=active:${active},healthy:${healthy},stalled:${stalled}:leader-mailbox=${pendingAge},leader-processing=${processingAge}`;
1003
1003
  }
1004
- function stallEvidenceKey(roleStatus, sessionStatus) {
1005
- return `live-pane-no-progress:role=${roleStatus}:session=${sessionStatus ?? "unknown"}`;
1004
+ function stallEvidenceKey(sessionStatus) {
1005
+ return `live-pane-no-progress:session=${sessionStatus ?? "unknown"}`;
1006
1006
  }
1007
1007
  function exactLiveStatuses(statuses, candidates) {
1008
1008
  const expected = new Set(candidates.map(({ task, role }) => `${task.id}\0${role.name}`));
@@ -99,10 +99,14 @@ const RELEASE_WORKFLOW_FROM_VERSION = 0;
99
99
  const RELEASE_WORKFLOW_TO_VERSION = 1;
100
100
  const WORK_MAILBOX_FROM_VERSION = 1;
101
101
  const WORK_MAILBOX_TO_VERSION = 2;
102
+ const TASK_ROLE_FROM_VERSION = 3;
103
+ const TASK_ROLE_TO_VERSION = 4;
102
104
  const TASK_ROLE_SESSION_SET_FROM_VERSION = 4;
103
105
  const TASK_ROLE_SESSION_SET_TO_VERSION = 5;
104
106
  const STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION = 5;
105
107
  const STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION = 6;
108
+ const AGENT_FIRST_SESSION_SET_FROM_VERSION = 6;
109
+ const AGENT_FIRST_SESSION_SET_TO_VERSION = 7;
106
110
  const PUBLICATION_REFERENCE_FROM_VERSION = 0;
107
111
  const PUBLICATION_REFERENCE_TO_VERSION = 1;
108
112
  const CONFIG_FROM_VERSION = 1;
@@ -203,12 +207,68 @@ export function createProductionStorageRegistry() {
203
207
  .registerOfflineMigration(capabilityGrantIntroductionStep())
204
208
  .registerOfflineMigration(releaseWorkflowIntroductionStep())
205
209
  .registerOfflineMigration(workMailboxV2Step())
210
+ .registerOfflineMigration(agentRunOwnedTaskRoleStep())
206
211
  .registerOfflineMigration(taskRoleSessionSetV5Step())
207
212
  .registerOfflineMigration(structuredProviderSessionSetV6Step())
213
+ .registerOfflineMigration(agentFirstSessionSetV7Step())
208
214
  .registerOfflineMigration(publicationReferenceIntroductionStep());
209
215
  assertRegistryCoversBaselineToCurrent(registry);
210
216
  return registry;
211
217
  }
218
+ /** Removes the independently writable Role runtime projection; AgentRun owns workflow activity. */
219
+ function agentRunOwnedTaskRoleStep() {
220
+ return {
221
+ axis: "record",
222
+ recordKind: "taskRole",
223
+ fromVersion: TASK_ROLE_FROM_VERSION,
224
+ toVersion: TASK_ROLE_TO_VERSION,
225
+ preconditions: requireTaskRoleV3Family,
226
+ transform: migrateTaskRoleV3ToV4,
227
+ declaredEffects: []
228
+ };
229
+ }
230
+ function requireTaskRoleV3Family(snapshot) {
231
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
232
+ if (versions.taskRole !== TASK_ROLE_FROM_VERSION) {
233
+ throw new Error(`Record taskRole migration requires manifest version ${TASK_ROLE_FROM_VERSION}.`);
234
+ }
235
+ if (snapshot.state === null)
236
+ return;
237
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
238
+ for (const [taskId, rawAggregate] of Object.entries(tasks)) {
239
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
240
+ const roles = asObject(aggregate.roles, `Task Roles ${taskId}`);
241
+ for (const [roleName, rawRole] of Object.entries(roles)) {
242
+ const role = asObject(rawRole, `Task Role ${taskId}/${roleName}`);
243
+ if (role.schemaVersion !== TASK_ROLE_FROM_VERSION) {
244
+ throw new Error(`Task Role ${taskId}/${roleName} must use schemaVersion ${TASK_ROLE_FROM_VERSION}.`);
245
+ }
246
+ }
247
+ }
248
+ }
249
+ function migrateTaskRoleV3ToV4(snapshot) {
250
+ requireTaskRoleV3Family(snapshot);
251
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
252
+ const schemaManifest = {
253
+ ...snapshot.schemaManifest,
254
+ recordVersions: { ...versions, taskRole: TASK_ROLE_TO_VERSION }
255
+ };
256
+ if (snapshot.state === null)
257
+ return { schemaManifest, state: null };
258
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
259
+ const nextTasks = Object.fromEntries(Object.entries(tasks).map(([taskId, rawAggregate]) => {
260
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
261
+ const roles = asObject(aggregate.roles, `Task Roles ${taskId}`);
262
+ const nextRoles = Object.fromEntries(Object.entries(roles).map(([roleName, rawRole]) => {
263
+ const role = asObject(rawRole, `Task Role ${taskId}/${roleName}`);
264
+ const { status: _status, ...retained } = role;
265
+ void _status;
266
+ return [roleName, { ...retained, schemaVersion: TASK_ROLE_TO_VERSION }];
267
+ }));
268
+ return [taskId, { ...aggregate, roles: nextRoles }];
269
+ }));
270
+ return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
271
+ }
212
272
  function configV2Step() {
213
273
  return {
214
274
  axis: "record",
@@ -2718,6 +2778,89 @@ function structuredProviderSessionSetV6Step() {
2718
2778
  declaredEffects: []
2719
2779
  };
2720
2780
  }
2781
+ /**
2782
+ * A Provider binding describes one reusable Conversation. Run correlation now
2783
+ * belongs to the concrete Provider Turn instead of making the Conversation a
2784
+ * second writable projection of the current AgentRun.
2785
+ */
2786
+ function agentFirstSessionSetV7Step() {
2787
+ return {
2788
+ axis: "record",
2789
+ recordKind: "taskRoleSessionSet",
2790
+ fromVersion: AGENT_FIRST_SESSION_SET_FROM_VERSION,
2791
+ toVersion: AGENT_FIRST_SESSION_SET_TO_VERSION,
2792
+ preconditions: requireTaskRoleSessionSetV6Family,
2793
+ transform: migrateTaskRoleSessionSetV6ToV7,
2794
+ declaredEffects: []
2795
+ };
2796
+ }
2797
+ function requireTaskRoleSessionSetV6Family(snapshot) {
2798
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
2799
+ if (versions.taskRoleSessionSet !== AGENT_FIRST_SESSION_SET_FROM_VERSION) {
2800
+ throw new Error(`Record taskRoleSessionSet migration requires manifest version ${AGENT_FIRST_SESSION_SET_FROM_VERSION}.`);
2801
+ }
2802
+ if (snapshot.state === null)
2803
+ return;
2804
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
2805
+ for (const [taskId, rawAggregate] of Object.entries(tasks)) {
2806
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
2807
+ const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
2808
+ for (const [roleName, rawSet] of Object.entries(sets)) {
2809
+ const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
2810
+ if (set.schemaVersion !== AGENT_FIRST_SESSION_SET_FROM_VERSION) {
2811
+ throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${AGENT_FIRST_SESSION_SET_FROM_VERSION}.`);
2812
+ }
2813
+ if (set.providerBinding !== null) {
2814
+ const binding = asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`);
2815
+ if (binding.schemaVersion !== 2 || typeof binding.runId !== "string") {
2816
+ throw new Error(`Provider Binding ${taskId}/${roleName} must use schemaVersion 2.`);
2817
+ }
2818
+ }
2819
+ }
2820
+ }
2821
+ }
2822
+ function migrateTaskRoleSessionSetV6ToV7(snapshot) {
2823
+ requireTaskRoleSessionSetV6Family(snapshot);
2824
+ const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
2825
+ const schemaManifest = {
2826
+ ...snapshot.schemaManifest,
2827
+ recordVersions: {
2828
+ ...versions,
2829
+ taskRoleSessionSet: AGENT_FIRST_SESSION_SET_TO_VERSION
2830
+ }
2831
+ };
2832
+ if (snapshot.state === null)
2833
+ return { schemaManifest, state: null };
2834
+ const tasks = asObject(snapshot.state.tasks, "state tasks");
2835
+ const nextTasks = {};
2836
+ for (const [taskId, rawAggregate] of Object.entries(tasks)) {
2837
+ const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
2838
+ const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
2839
+ const nextSets = {};
2840
+ for (const [roleName, rawSet] of Object.entries(rawSets)) {
2841
+ const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
2842
+ let providerBinding = null;
2843
+ if (set.providerBinding !== null) {
2844
+ const binding = asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`);
2845
+ const { runId, ...conversation } = binding;
2846
+ providerBinding = {
2847
+ ...conversation,
2848
+ schemaVersion: 3,
2849
+ turn: binding.turn === null
2850
+ ? null
2851
+ : { ...asObject(binding.turn, `Provider Turn ${taskId}/${roleName}`), runId }
2852
+ };
2853
+ }
2854
+ nextSets[roleName] = {
2855
+ ...set,
2856
+ schemaVersion: AGENT_FIRST_SESSION_SET_TO_VERSION,
2857
+ providerBinding
2858
+ };
2859
+ }
2860
+ nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
2861
+ }
2862
+ return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
2863
+ }
2721
2864
  function requireTaskRoleSessionSetV5Family(snapshot) {
2722
2865
  const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
2723
2866
  if (versions.taskRoleSessionSet !== STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION) {
@@ -64,7 +64,7 @@ export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 3;
64
64
  export const CURRENT_TASK_SCHEMA_VERSION = 6;
65
65
  export const CURRENT_TASK_BRIEF_SCHEMA_VERSION = 2;
66
66
  export const CURRENT_CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
67
- export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 3;
67
+ export const CURRENT_TASK_ROLE_SCHEMA_VERSION = 4;
68
68
  export const CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION = 2;
69
69
  export const CURRENT_WORK_ITEM_SCHEMA_VERSION = 12;
70
70
  export const CURRENT_REVIEW_ROUND_SCHEMA_VERSION = 6;
@@ -118,7 +118,7 @@ export const CURRENT_STORED_TASK_SCHEMA_VERSION = 18;
118
118
  * Keep these named at the storage boundary so the upgrade record-axis map can
119
119
  * assert it is classifying the same bytes the store reads and writes.
120
120
  */
121
- export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 6;
121
+ export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 7;
122
122
  /**
123
123
  * v7 combines optional Issue 04 retry/receipt fields and Issue 05 Leader
124
124
  * actionability fields. All are optional, so the v6→v7 migration is a
@@ -92,6 +92,9 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
92
92
  const effectiveLaunch = activeRun?.effective ?? activeSession?.effective ?? null;
93
93
  return {
94
94
  ...role,
95
+ // Presentation only: workflow activity is derived from AgentRun; the
96
+ // native Session contributes lifecycle detail when no Run is active.
97
+ status: activeRun === undefined ? activeSession?.status ?? "idle" : "running",
95
98
  sessionTokens: projectSessionTokenMetrics(events, resolveSessionTokenIdentity(activeSession === undefined
96
99
  ? null
97
100
  : { taskId, roleName: role.name, ...activeSession })),
@@ -2,9 +2,15 @@
2
2
 
3
3
  # Yui
4
4
 
5
- Yui 是面向持久 Codex/Claude 工作的本地控制平面。用户只需和 Operator
6
- 对话;Operator 将不同 Project 的需求、Bug、审查和问题咨询路由到对应
7
- Task,Leader 再负责拆解、执行选择、验收和安全集成。
5
+ Yui 是面向智能 Codex/Claude Agent 的本地控制平面。它持久保存用户意图、
6
+ Project Knowledge、Task、交接和结果,并提供上下文、消息、委派、工作区、
7
+ Session、审查和集成等小而原子的能力。Agent 组合这些能力,自主决定规划、
8
+ 顺序、委派、重试和恢复。
9
+
10
+ Yui 不把 Agent 的判断固化成确定性的工作流引擎。核心只负责持久身份、用户
11
+ 授权、工作区隔离和原子状态变更;Provider Session 与运行时观测用于执行和
12
+ 连续性,但不是 Task 事实的另一套来源。用户只需和 Operator 对话,Operator
13
+ 负责路由,Leader 负责目标拆解、执行选择、验收和集成。
8
14
 
9
15
  当前实现保留实用的 Role/Agent/session 与 CLI 框架,不恢复后期膨胀的数据维护、租约、定时调度和恢复账本体系。
10
16
 
@@ -514,6 +520,8 @@ Task 生命周期的交互选择只展示有效来源状态:activate 只展示
514
520
 
515
521
  Run、Conversation、Activation 与 Turn 是四个独立身份。Conversation 可以跨多个 Run 和客户端连接;Activation 只代表 Yui 当前的连接,而不是对 Provider thread 的独占所有权;Turn 在写入前先持久化。写入超时或结果不明确会进入 `delivery-unknown`,不会自动重发。Codex 已存在的原生 active Turn 只会让 Yui 暂时等待,不会导致 Yui Run 失败;Claude 等独立进程 Provider 继续通过 Yui 的 view/takeover 边界进行人工控制。
516
522
 
523
+ AgentRun 是 Role 是否有工作正在执行的唯一持久调度状态。Conversation 不再维护另一份 current Run;Provider Turn 中的 Run id 只用于关联接收回执和终态事件。TaskRole 本身只保存身份和期望启动配置,不再保存可写的运行状态;CLI/Web 展示的 Role 状态由活动 AgentRun 派生,并叠加 Session/Driver 生命周期事实用于诊断。Agent 可以在 Provider 终态到达前声明旧 Run 的语义结果,下一批 mailbox 工作也可以先形成新的 AgentRun;Agent Host 仍会串行等待旧 Turn 结束,再投递被保留的新输入,不回滚事件,也不把旧 Conversation 强行换绑到新 Run。
524
+
517
525
  Task Role 使用以下显式入口:
518
526
 
519
527
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "0.13.7",
3
+ "version": "0.13.8",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -12,10 +12,11 @@ workspace layout, native transcript, or an earlier Run.
12
12
  For every managed Task Run:
13
13
 
14
14
  1. Read the exact Run identity from the newest Bootstrap Envelope.
15
- 2. Before acting, load its authorized pack with the ordinary Yui CLI command:
15
+ 2. Before acting, load its authorized pack with the Session CLI named by the
16
+ current Session Manifest:
16
17
 
17
18
  ```sh
18
- yui task run context "$YUI_TASK_ID/<run-id>" --json
19
+ "$YUI_SESSION_CLI" task run context "$YUI_TASK_ID/<run-id>" --json
19
20
  ```
20
21
 
21
22
  3. Verify that the returned Task, Run, Role, purpose, Snapshot digest, workspace,
@@ -27,7 +28,7 @@ For every managed Task Run:
27
28
  `refId`:
28
29
 
29
30
  ```sh
30
- yui task run context expand "$YUI_TASK_ID/<run-id>" <ref-id> --store <store> --mode full --json
31
+ "$YUI_SESSION_CLI" task run context expand "$YUI_TASK_ID/<run-id>" <ref-id> --store <store> --mode full --json
31
32
  ```
32
33
 
33
34
  A bare `<ref-id>` remains supported only when it identifies exactly one
@@ -46,7 +47,7 @@ For a global Operator or custom GlobalRole Session, load the stable authorized v
46
47
  before routing or acting:
47
48
 
48
49
  ```sh
49
- yui session context "$YUI_ROLE" --json
50
+ "$YUI_SESSION_CLI" session context "$YUI_ROLE" --json
50
51
  ```
51
52
 
52
53
  Global context grants no Task implementation workspace. Read a Task only after