@zq-silk/yui 0.8.1 → 0.8.2
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 +35 -46
- package/dist/cli/commandCatalog.js +49 -14
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +73 -21
- package/dist/commands/taskCommands.js +108 -53
- 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/run/agentRun.js +2 -2
- 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/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/taskStore.js +1 -1
- package/dist/tmux/tmuxManager.js +1 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
|
@@ -67,6 +67,7 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
67
67
|
let effectiveSession = existingSession;
|
|
68
68
|
let claimed = false;
|
|
69
69
|
let deliveryAttempted = false;
|
|
70
|
+
let providerSubmissionBegun = false;
|
|
70
71
|
let run = null;
|
|
71
72
|
let prepared;
|
|
72
73
|
let preStartFencePersisted = false;
|
|
@@ -183,13 +184,14 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
183
184
|
preStartFencePersisted = true;
|
|
184
185
|
}
|
|
185
186
|
});
|
|
186
|
-
// A managed
|
|
187
|
-
//
|
|
187
|
+
// A managed Host may have submitted the exact Run Turn after its
|
|
188
|
+
// two-phase launch handshake. Once preparation returns that transport
|
|
188
189
|
// fact, any
|
|
189
190
|
// later readiness or aggregate-write failure is delivery uncertainty,
|
|
190
191
|
// not a launch failure: preserve the Run and its reservation for the
|
|
191
192
|
// matching provider Hook instead of terminalizing it.
|
|
192
|
-
deliveryAttempted = prepared.
|
|
193
|
+
deliveryAttempted = prepared.turnAcceptedDuringLaunch === true
|
|
194
|
+
|| prepared.turnDeliveryUnknownDuringLaunch === true;
|
|
193
195
|
// Persist the exact preparation fence before waiting on provider
|
|
194
196
|
// readiness. A pre-input lifecycle Hook may fire during that wait; it
|
|
195
197
|
// must be able to resolve this Run/Session/launch generation from durable
|
|
@@ -211,7 +213,8 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
211
213
|
}
|
|
212
214
|
const ready = await delivery.waitUntilReady(prepared);
|
|
213
215
|
deliveryAttempted = deliveryAttempted
|
|
214
|
-
|| ready.prepared.
|
|
216
|
+
|| ready.prepared.turnAcceptedDuringLaunch === true
|
|
217
|
+
|| ready.prepared.turnDeliveryUnknownDuringLaunch === true;
|
|
215
218
|
const latestTask = store.getTask(task.id);
|
|
216
219
|
if (latestTask === null || latestTask.status !== "active") {
|
|
217
220
|
delivery.forgetPrepared?.({
|
|
@@ -236,12 +239,54 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
236
239
|
: { launchId: ready.prepared.launchId }),
|
|
237
240
|
now
|
|
238
241
|
});
|
|
239
|
-
if (ready.prepared.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
if (ready.prepared.turnRejectedDuringLaunch === true) {
|
|
243
|
+
delivery.forgetPrepared?.({
|
|
244
|
+
taskId: task.id,
|
|
245
|
+
roleName: role.name,
|
|
246
|
+
runId: run.id,
|
|
247
|
+
...(ready.prepared.launchId === undefined
|
|
248
|
+
? {}
|
|
249
|
+
: { launchId: ready.prepared.launchId })
|
|
250
|
+
});
|
|
251
|
+
results.push({
|
|
252
|
+
taskId: task.id,
|
|
253
|
+
runId: run.id,
|
|
254
|
+
status: "skipped",
|
|
255
|
+
reason: "not-ready"
|
|
256
|
+
});
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (ready.prepared.turnDeliveryUnknownDuringLaunch === true) {
|
|
260
|
+
store.saveRoleRunDelivery({
|
|
261
|
+
task,
|
|
262
|
+
role,
|
|
263
|
+
run,
|
|
264
|
+
session: effectiveSession,
|
|
265
|
+
...(ready.prepared.launchId === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: { launchId: ready.prepared.launchId }),
|
|
268
|
+
now
|
|
269
|
+
});
|
|
270
|
+
delivery.forgetPrepared?.({
|
|
271
|
+
taskId: task.id,
|
|
272
|
+
roleName: role.name,
|
|
273
|
+
runId: run.id,
|
|
274
|
+
...(ready.prepared.launchId === undefined
|
|
275
|
+
? {}
|
|
276
|
+
: { launchId: ready.prepared.launchId })
|
|
277
|
+
});
|
|
278
|
+
results.push({
|
|
279
|
+
taskId: task.id,
|
|
280
|
+
runId: run.id,
|
|
281
|
+
status: "skipped",
|
|
282
|
+
reason: "delivery-uncertain"
|
|
283
|
+
});
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (ready.prepared.turnAcceptedDuringLaunch === true) {
|
|
287
|
+
// The Agent Host submitted the exact first Turn through structured
|
|
288
|
+
// Provider control. Persist the transport fence without a second write;
|
|
289
|
+
// the matching structured acknowledgement owns Run acceptance.
|
|
245
290
|
store.saveRoleRunDelivery({
|
|
246
291
|
task,
|
|
247
292
|
role,
|
|
@@ -263,13 +308,41 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
263
308
|
results.push({ taskId: task.id, runId: run.id, status: "dispatched" });
|
|
264
309
|
continue;
|
|
265
310
|
}
|
|
311
|
+
const receiptId = formatAgentRunReceiptId(task.id, run.id);
|
|
312
|
+
if (effectiveSession === null || effectiveSession.launchId === undefined
|
|
313
|
+
|| !hasNativeSession(effectiveSession)
|
|
314
|
+
|| store.beginRoleRunProviderTurn?.({
|
|
315
|
+
taskId: task.id,
|
|
316
|
+
roleName: role.name,
|
|
317
|
+
runId: run.id,
|
|
318
|
+
agentId: run.effective.agentId,
|
|
319
|
+
launchId: effectiveSession.launchId,
|
|
320
|
+
nativeSessionId: effectiveSession.nativeSessionId,
|
|
321
|
+
attemptId: receiptId,
|
|
322
|
+
now
|
|
323
|
+
}) !== true) {
|
|
324
|
+
throw new Error("Provider Turn intent could not be durably fenced before delivery.");
|
|
325
|
+
}
|
|
326
|
+
providerSubmissionBegun = true;
|
|
266
327
|
deliveryAttempted = true;
|
|
267
328
|
const outcome = await delivery.sendOnce({
|
|
268
329
|
delivery: ready,
|
|
269
|
-
receiptId
|
|
330
|
+
receiptId,
|
|
270
331
|
text: serializeRunBootstrapEnvelope(run.bootstrapEnvelope)
|
|
271
332
|
});
|
|
272
333
|
if (outcome === "busy" || outcome === "unavailable") {
|
|
334
|
+
store.resolveRoleRunProviderSubmission?.({
|
|
335
|
+
taskId: task.id,
|
|
336
|
+
roleName: role.name,
|
|
337
|
+
runId: run.id,
|
|
338
|
+
attemptId: receiptId,
|
|
339
|
+
status: "rejected",
|
|
340
|
+
reason: outcome === "busy"
|
|
341
|
+
? "Agent Host was busy before Provider mutation."
|
|
342
|
+
: "Agent Host was unavailable before Provider mutation.",
|
|
343
|
+
now
|
|
344
|
+
});
|
|
345
|
+
providerSubmissionBegun = false;
|
|
273
346
|
results.push({
|
|
274
347
|
taskId: task.id,
|
|
275
348
|
runId: run.id,
|
|
@@ -278,6 +351,49 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
278
351
|
});
|
|
279
352
|
continue;
|
|
280
353
|
}
|
|
354
|
+
if (outcome === "rejected") {
|
|
355
|
+
store.resolveRoleRunProviderSubmission?.({
|
|
356
|
+
taskId: task.id,
|
|
357
|
+
roleName: role.name,
|
|
358
|
+
runId: run.id,
|
|
359
|
+
attemptId: receiptId,
|
|
360
|
+
status: "rejected",
|
|
361
|
+
reason: "Provider returned an exact negative acknowledgement.",
|
|
362
|
+
now
|
|
363
|
+
});
|
|
364
|
+
providerSubmissionBegun = false;
|
|
365
|
+
results.push({ taskId: task.id, runId: run.id, status: "skipped", reason: "not-ready" });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (outcome === "delivery-unknown") {
|
|
369
|
+
store.resolveRoleRunProviderSubmission?.({
|
|
370
|
+
taskId: task.id,
|
|
371
|
+
roleName: role.name,
|
|
372
|
+
runId: run.id,
|
|
373
|
+
attemptId: receiptId,
|
|
374
|
+
status: "delivery-unknown",
|
|
375
|
+
reason: "Provider Turn delivery is ambiguous; automatic retry is fenced.",
|
|
376
|
+
now
|
|
377
|
+
});
|
|
378
|
+
providerSubmissionBegun = false;
|
|
379
|
+
store.saveRoleRunDelivery({
|
|
380
|
+
task,
|
|
381
|
+
role,
|
|
382
|
+
run,
|
|
383
|
+
session: effectiveSession,
|
|
384
|
+
...(ready.prepared.launchId === undefined
|
|
385
|
+
? {}
|
|
386
|
+
: { launchId: ready.prepared.launchId }),
|
|
387
|
+
now
|
|
388
|
+
});
|
|
389
|
+
results.push({
|
|
390
|
+
taskId: task.id,
|
|
391
|
+
runId: run.id,
|
|
392
|
+
status: "skipped",
|
|
393
|
+
reason: "delivery-uncertain"
|
|
394
|
+
});
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
281
397
|
store.saveRoleRunDelivery({
|
|
282
398
|
task,
|
|
283
399
|
role,
|
|
@@ -293,6 +409,17 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
293
409
|
catch (error) {
|
|
294
410
|
const detail = error instanceof Error ? error.message : String(error);
|
|
295
411
|
const message = `Leader dispatch failed: ${detail}`;
|
|
412
|
+
if (providerSubmissionBegun && run !== null) {
|
|
413
|
+
store.resolveRoleRunProviderSubmission?.({
|
|
414
|
+
taskId: task.id,
|
|
415
|
+
roleName: role.name,
|
|
416
|
+
runId: run.id,
|
|
417
|
+
attemptId: formatAgentRunReceiptId(task.id, run.id),
|
|
418
|
+
status: "delivery-unknown",
|
|
419
|
+
reason: detail,
|
|
420
|
+
now
|
|
421
|
+
});
|
|
422
|
+
}
|
|
296
423
|
if (claimed && run !== null) {
|
|
297
424
|
// Scheduler single-flight backpressure: the Role runtime lifecycle
|
|
298
425
|
// lane was busy when the launch was reserved. The claimed Run stays
|
|
@@ -314,11 +441,9 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
314
441
|
});
|
|
315
442
|
continue;
|
|
316
443
|
}
|
|
317
|
-
// A
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
// after the old host disappears. This is runtime backpressure, not a
|
|
321
|
-
// Leader failure and not grounds for allocating another Run.
|
|
444
|
+
// A managed Provider lifecycle operation may still be settling. Keep
|
|
445
|
+
// this newly-claimed Run as the sole mailbox owner; active-Run delivery
|
|
446
|
+
// retries after the lifecycle lane clears.
|
|
322
447
|
if (error instanceof RuntimeLaunchError && error.retryable) {
|
|
323
448
|
results.push({
|
|
324
449
|
taskId: task.id,
|
|
@@ -79,6 +79,8 @@ const WORK_MAILBOX_FROM_VERSION = 1;
|
|
|
79
79
|
const WORK_MAILBOX_TO_VERSION = 2;
|
|
80
80
|
const TASK_ROLE_SESSION_SET_FROM_VERSION = 4;
|
|
81
81
|
const TASK_ROLE_SESSION_SET_TO_VERSION = 5;
|
|
82
|
+
const STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION = 5;
|
|
83
|
+
const STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION = 6;
|
|
82
84
|
const PUBLICATION_REFERENCE_FROM_VERSION = 0;
|
|
83
85
|
const PUBLICATION_REFERENCE_TO_VERSION = 1;
|
|
84
86
|
const CONFIG_FROM_VERSION = 1;
|
|
@@ -161,6 +163,7 @@ export function createProductionStorageRegistry() {
|
|
|
161
163
|
.registerOfflineMigration(releaseWorkflowIntroductionStep())
|
|
162
164
|
.registerOfflineMigration(workMailboxV2Step())
|
|
163
165
|
.registerOfflineMigration(taskRoleSessionSetV5Step())
|
|
166
|
+
.registerOfflineMigration(structuredProviderSessionSetV6Step())
|
|
164
167
|
.registerOfflineMigration(publicationReferenceIntroductionStep());
|
|
165
168
|
assertRegistryCoversBaselineToCurrent(registry);
|
|
166
169
|
return registry;
|
|
@@ -2225,6 +2228,114 @@ function taskRoleSessionSetV5Step() {
|
|
|
2225
2228
|
declaredEffects: []
|
|
2226
2229
|
};
|
|
2227
2230
|
}
|
|
2231
|
+
/**
|
|
2232
|
+
* v6 is a deliberate runtime cutover, not an emulation layer. Existing Task,
|
|
2233
|
+
* Run, Conversation, and Session identities remain as audit evidence, while
|
|
2234
|
+
* every pre-v6 managed process is terminalized locally. A new Agent Host must
|
|
2235
|
+
* establish structured Provider evidence before any further write.
|
|
2236
|
+
*/
|
|
2237
|
+
function structuredProviderSessionSetV6Step() {
|
|
2238
|
+
return {
|
|
2239
|
+
axis: "record",
|
|
2240
|
+
recordKind: "taskRoleSessionSet",
|
|
2241
|
+
fromVersion: STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION,
|
|
2242
|
+
toVersion: STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION,
|
|
2243
|
+
preconditions: requireTaskRoleSessionSetV5Family,
|
|
2244
|
+
transform: migrateTaskRoleSessionSetV5ToV6,
|
|
2245
|
+
declaredEffects: []
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
function requireTaskRoleSessionSetV5Family(snapshot) {
|
|
2249
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2250
|
+
if (versions.taskRoleSessionSet !== STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION) {
|
|
2251
|
+
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION}.`);
|
|
2252
|
+
}
|
|
2253
|
+
if (snapshot.state === null)
|
|
2254
|
+
return;
|
|
2255
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2256
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
2257
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
2258
|
+
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
2259
|
+
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
2260
|
+
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
2261
|
+
if (set.schemaVersion !== STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION) {
|
|
2262
|
+
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION}.`);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
function migrateTaskRoleSessionSetV5ToV6(snapshot) {
|
|
2268
|
+
requireTaskRoleSessionSetV5Family(snapshot);
|
|
2269
|
+
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2270
|
+
const schemaManifest = {
|
|
2271
|
+
...snapshot.schemaManifest,
|
|
2272
|
+
recordVersions: {
|
|
2273
|
+
...versions,
|
|
2274
|
+
taskRoleSessionSet: STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
if (snapshot.state === null)
|
|
2278
|
+
return { schemaManifest, state: null };
|
|
2279
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2280
|
+
const nextTasks = {};
|
|
2281
|
+
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
2282
|
+
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
2283
|
+
const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
2284
|
+
const nextSets = {};
|
|
2285
|
+
for (const [roleName, rawSet] of Object.entries(rawSets)) {
|
|
2286
|
+
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
2287
|
+
const invalidatedAt = String(set.updatedAt);
|
|
2288
|
+
const sessions = asObject(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
2289
|
+
const nextSessions = Object.fromEntries(Object.entries(sessions).map(([agentId, rawSession]) => {
|
|
2290
|
+
const session = asObject(rawSession, `Task Role Session ${taskId}/${roleName}/${agentId}`);
|
|
2291
|
+
return [agentId, session.status === "stopped" || session.status === "broken"
|
|
2292
|
+
? session
|
|
2293
|
+
: { ...session, status: "broken", updatedAt: invalidatedAt }];
|
|
2294
|
+
}));
|
|
2295
|
+
nextSets[roleName] = {
|
|
2296
|
+
...set,
|
|
2297
|
+
schemaVersion: STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION,
|
|
2298
|
+
sessions: nextSessions,
|
|
2299
|
+
providerBinding: set.providerBinding === null
|
|
2300
|
+
? null
|
|
2301
|
+
: invalidateLegacyProviderBinding(asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`), invalidatedAt)
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
|
|
2305
|
+
}
|
|
2306
|
+
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
2307
|
+
}
|
|
2308
|
+
function invalidateLegacyProviderBinding(binding, invalidatedAt) {
|
|
2309
|
+
if (binding.schemaVersion !== 1) {
|
|
2310
|
+
throw new Error("Pre-v6 Provider Runtime Binding must use schemaVersion 1.");
|
|
2311
|
+
}
|
|
2312
|
+
const activations = Array.isArray(binding.activations) ? binding.activations : [];
|
|
2313
|
+
return {
|
|
2314
|
+
...binding,
|
|
2315
|
+
schemaVersion: 2,
|
|
2316
|
+
conversations: (Array.isArray(binding.conversations) ? binding.conversations : []).map((rawConversation) => {
|
|
2317
|
+
const conversation = asObject(rawConversation, "Provider Conversation");
|
|
2318
|
+
return conversation.status === "current"
|
|
2319
|
+
? { ...conversation, recoverability: "unknown" }
|
|
2320
|
+
: conversation;
|
|
2321
|
+
}),
|
|
2322
|
+
activations: activations.map((rawActivation) => {
|
|
2323
|
+
const activation = asObject(rawActivation, "Provider Activation");
|
|
2324
|
+
const { writerLease: _removed, ...withoutLease } = activation;
|
|
2325
|
+
void _removed;
|
|
2326
|
+
return activation.status === "active"
|
|
2327
|
+
? {
|
|
2328
|
+
...withoutLease,
|
|
2329
|
+
status: "failed",
|
|
2330
|
+
endedAt: invalidatedAt,
|
|
2331
|
+
terminalReason: "legacy-managed-runtime-invalidated"
|
|
2332
|
+
}
|
|
2333
|
+
: withoutLease;
|
|
2334
|
+
}),
|
|
2335
|
+
authority: { epoch: 1, owner: "none", changedAt: invalidatedAt },
|
|
2336
|
+
turn: null
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2228
2339
|
function requireTaskRoleSessionSetV4Family(snapshot) {
|
|
2229
2340
|
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2230
2341
|
if (versions.taskRoleSessionSet !== TASK_ROLE_SESSION_SET_FROM_VERSION) {
|
|
@@ -117,7 +117,7 @@ export const CURRENT_STORED_TASK_SCHEMA_VERSION = 17;
|
|
|
117
117
|
* Keep these named at the storage boundary so the upgrade record-axis map can
|
|
118
118
|
* assert it is classifying the same bytes the store reads and writes.
|
|
119
119
|
*/
|
|
120
|
-
export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION =
|
|
120
|
+
export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 6;
|
|
121
121
|
/**
|
|
122
122
|
* v7 combines optional Issue 04 retry/receipt fields and Issue 05 Leader
|
|
123
123
|
* actionability fields. All are optional, so the v6→v7 migration is a
|
package/dist/tmux/tmuxManager.js
CHANGED
|
@@ -1025,7 +1025,7 @@ export class TmuxManager {
|
|
|
1025
1025
|
/**
|
|
1026
1026
|
* Pin the Role window to the largest attached client. The tmux default
|
|
1027
1027
|
* `window-size latest` lets a later-attaching smaller client (e.g. the Web
|
|
1028
|
-
* terminal or a second
|
|
1028
|
+
* terminal or a second Role viewer from a smaller pane) shrink the shared
|
|
1029
1029
|
* Role window, leaving the primary viewer with a TUI pinned to the top of a
|
|
1030
1030
|
* large terminal and no scrollback. `largest` keeps the window at the
|
|
1031
1031
|
* biggest attached client so a compact viewer cannot compress it.
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -435,20 +435,23 @@ Task 生命周期的交互选择只展示有效来源状态:activate 只展示
|
|
|
435
435
|
|
|
436
436
|
## Session 与 tmux
|
|
437
437
|
|
|
438
|
-
|
|
438
|
+
受管理的 Task Agent 统一使用混合 Provider Runtime:Controller 与 Agent Host 通过 Provider 原生结构化协议提交和确认输入;tmux/PTY 只负责保持 Host 存活、展示输出,以及在显式人工接管后提供输入网关。受管理输入绝不会作为终端按键、粘贴文本或启动 argv 发送。Codex 使用持久 App Server JSON-RPC 进程;Claude 使用持久 stream-json 进程,并以精确回放的 user message 作为接收确认。
|
|
439
439
|
|
|
440
|
-
|
|
440
|
+
Run、Conversation、Activation 与 Turn 是四个独立身份。Conversation 可以跨多个 Run 和进程;Activation 只代表一次 Provider 进程存活期;Turn 在写入前先持久化。写入超时或结果不明确会进入 `delivery-unknown`,不会自动重发。Provider 进程退出后结束当前 Activation;恢复同一 Conversation 会创建新 Activation 并推进 authority epoch。
|
|
441
441
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
Global 交互入口在不存在 writer 时保持可写;已有 writer 时自动降级为只读。global Web 对每个 tmux session 只允许一个 writer;Task Web 始终只读。Task CLI 入口除非显式请求 `--read-write`,否则始终只读,避免观察动作改变 Agent 执行。
|
|
442
|
+
Task Role 使用以下显式入口:
|
|
445
443
|
|
|
446
444
|
```sh
|
|
447
445
|
yui session enter <global-role>
|
|
448
|
-
yui task
|
|
449
|
-
yui task role
|
|
446
|
+
yui task role view <task-id> <role>
|
|
447
|
+
yui task role takeover <task-id> <role>
|
|
448
|
+
yui task role release <task-id> <role>
|
|
450
449
|
```
|
|
451
450
|
|
|
451
|
+
`view` 始终只读。`takeover` 要求存在 active managed Run 且没有未决 Turn;它先以持久 CAS 把唯一 writer authority 转给人工 holder,再把相同 epoch 同步给 Agent Host,最后开放 PTY 输入网关。人工输入仍由 Host 转换为结构化 Provider Turn,而不是直接注入 Provider 终端。detach 会自动归还 authority;`release` 即使没有 active Run 也可执行,用于幂等修复中断或未完全同步的接管。Global Operator 与 global Role 继续使用原生交互式 CLI,不属于受管理 Task Provider 协议。
|
|
452
|
+
|
|
453
|
+
tmux 会在 pane 创建时固定其历史容量。配置该限制之前创建的 Role 会保留原容量;Yui 会在 Terminal attach 和 Web 中提示用户退出并重新进入一次,从而创建具有 100,000 行历史的新 pane。
|
|
454
|
+
|
|
452
455
|
每个 Role 可绑定多个 Agent,但任一时刻只有一个 active Agent,并为每个
|
|
453
456
|
Agent binding 独立保存 native session。Operator 进一步限制为同一种
|
|
454
457
|
adapter 最多绑定一个,例如可同时绑定一个 Codex 和一个 Claude;这些
|
|
@@ -460,7 +463,7 @@ binding 是预先保存、可随时切换的配置,而不是并行身份。Ope
|
|
|
460
463
|
|
|
461
464
|
使用 `yui config role unbind <global-role> <agent-id>` 或 `yui task role unbind <task-id> <role> <agent-id>` 可移除休眠 binding。active binding 或任何未 stopped 的 native session 都会被拒绝;stopped session 记录会和 binding 在同一事务中删除。
|
|
462
465
|
|
|
463
|
-
Claude 的 session ID
|
|
466
|
+
Claude 的 session ID 在启动前分配,并由持久 stream-json Provider 进程承载多个 Turn;Codex 使用持久 App Server thread。两者都复用同一套 Conversation、Activation、Turn 与 authority fence,不再向模型对话注入 session-bind prompt。
|
|
464
467
|
|
|
465
468
|
自动生命周期与投递判断只使用结构化 Hook payload、持久身份、tmux process
|
|
466
469
|
state、receipt 与 pane fence。Yui 不会解析 prompt glyph、进度文本、trust dialog
|