@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/ARCHITECTURE.md +40 -4
- package/README.md +19 -1
- package/dist/cli/updatePorts.js +4 -4
- package/dist/cli.js +1 -1
- package/dist/commands/taskCommands.js +1 -12
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskExecutionCommands.js +0 -5
- package/dist/commands/taskOverviewCommand.js +5 -1
- package/dist/commands/taskRoleRuntimeStatus.js +0 -13
- package/dist/context/sessionBootstrapManifest.js +26 -23
- package/dist/controller/controller.js +0 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +84 -78
- package/dist/executor/agentExecutor.js +14 -28
- package/dist/executor/fileRoleLaunchPlanner.js +2 -3
- package/dist/lifecycle/exactRunTerminalization.js +1 -3
- package/dist/output/rolePresentation.js +0 -1
- package/dist/repository/taskWorkspacePreparer.js +0 -1
- package/dist/role/role.js +12 -20
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/providerRuntimeIdentity.js +8 -17
- package/dist/scheduler/activeRoleRunDelivery.js +2 -10
- package/dist/scheduler/activeTaskProgress.js +1 -4
- package/dist/scheduler/leaderWakeupProcessor.js +0 -5
- package/dist/scheduler/roleRunStall.js +3 -3
- package/dist/storage/migration/productionRegistry.js +143 -0
- package/dist/storage/taskStore.js +2 -2
- package/dist/web/webSnapshot.js +3 -0
- package/i18n/README.zh-CN.md +11 -3
- package/package.json +1 -1
- package/skills/yui-runtime/SKILL.md +5 -4
package/ARCHITECTURE.md
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
# Yui Architecture
|
|
2
2
|
|
|
3
|
-
Yui is a local control plane for durable work across
|
|
4
|
-
runtimes. The user talks to one Operator. The Operator
|
|
5
|
-
the right Project and Task; that Task's Leader owns
|
|
6
|
-
choice, review, integration, and completion.
|
|
3
|
+
Yui is a local control plane for intelligent Agents doing durable work across
|
|
4
|
+
Projects and native runtimes. The user talks to one Operator. The Operator
|
|
5
|
+
routes each request to the right Project and Task; that Task's Leader owns
|
|
6
|
+
decomposition, execution choice, review, integration, and completion.
|
|
7
|
+
|
|
8
|
+
## Design principles
|
|
9
|
+
|
|
10
|
+
- **Agents own judgment.** Yui exposes current durable context and atomic
|
|
11
|
+
capabilities; the Operator, Leader, and Workers choose plans, execution
|
|
12
|
+
topology, sequencing, retry, and recovery from that context.
|
|
13
|
+
- **Core provides primitives, not a prescribed workflow.** Reads, messages,
|
|
14
|
+
bounded record transitions, workspace ownership, Session lifecycle, and
|
|
15
|
+
acceptance are composable operations. Project Skills and Knowledge provide
|
|
16
|
+
project-specific policy without adding core branches.
|
|
17
|
+
- **Durable intent outranks runtime continuity.** Tasks, WorkItems, Messages,
|
|
18
|
+
Decisions, results, Project Knowledge, and managed workspaces are authority.
|
|
19
|
+
Provider Sessions, transcripts, processes, and observations are execution
|
|
20
|
+
aids that may be resumed or replaced.
|
|
21
|
+
- **Trust explicit Agent actions.** Once identity, authority, and scope are
|
|
22
|
+
established, a valid Agent command is a semantic declaration. Core should not
|
|
23
|
+
reconstruct the same judgment through another status protocol.
|
|
24
|
+
- **Fail visibly and let the Agent adapt.** Preserve pending intent and return
|
|
25
|
+
actionable state. Add automated retry, recovery, leases, or fallback only for
|
|
26
|
+
a normal product path, a hard safety or data-integrity boundary, or a proven
|
|
27
|
+
failure whose cost justifies the machinery.
|
|
28
|
+
- **One question has one authority.** Projections and indexes may summarize
|
|
29
|
+
state, but scheduling and lifecycle decisions must not depend on independently
|
|
30
|
+
writable copies of the same fact.
|
|
7
31
|
|
|
8
32
|
## One outcome, Leader-chosen execution topology
|
|
9
33
|
|
|
@@ -254,6 +278,18 @@ Yui's current attachment, not exclusive ownership of the Provider thread. One
|
|
|
254
278
|
Turn identifies one provider-native execution. Yui's authority epoch fences
|
|
255
279
|
only Yui's own submissions and retries.
|
|
256
280
|
|
|
281
|
+
`AgentRun` is the single durable scheduling authority for a Role. Provider
|
|
282
|
+
runtime persistence has no independently writable current-Run field; a Turn's
|
|
283
|
+
Run id is correlation evidence for receipts and terminal observations only.
|
|
284
|
+
`TaskRole` likewise stores configuration and identity, not a writable runtime
|
|
285
|
+
status. CLI and Web status views derive activity from the active AgentRun and
|
|
286
|
+
add Session/Driver facts only as lifecycle and diagnostic detail.
|
|
287
|
+
`AgentHost` is the serialized consumer: while a native Turn is active, the next
|
|
288
|
+
AgentRun and mailbox batch remain durable and unsubmitted. When that Turn ends,
|
|
289
|
+
the Host makes the Conversation ready and the retained delivery continues.
|
|
290
|
+
This remains true when the Agent declared the old Run's semantic outcome before
|
|
291
|
+
the Provider emitted its terminal event.
|
|
292
|
+
|
|
257
293
|
Codex Task threads remain ordinary native sessions and can be opened and used
|
|
258
294
|
directly in Desktop. If a direct user Turn is active, Yui keeps its pending
|
|
259
295
|
Run/message until that Turn settles. Global interactive entry remains a native
|
package/README.md
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
# Yui
|
|
4
4
|
|
|
5
|
-
Yui is a local control plane for
|
|
5
|
+
Yui is a local control plane for intelligent Codex and Claude Agents. It keeps
|
|
6
|
+
user intent, Project knowledge, Tasks, handoffs, and results durable and
|
|
7
|
+
inspectable, while exposing small atomic capabilities for context, messaging,
|
|
8
|
+
delegation, workspaces, Sessions, review, and integration. Agents compose those
|
|
9
|
+
capabilities and decide how to plan, sequence, delegate, retry, and recover.
|
|
10
|
+
|
|
11
|
+
Yui deliberately does not turn Agent judgment into a deterministic workflow
|
|
12
|
+
engine. Its core owns durable identity, user authority, workspace isolation,
|
|
13
|
+
and atomic state changes. Provider Sessions and runtime observations support
|
|
14
|
+
execution and continuity, but they are not competing sources of Task truth.
|
|
6
15
|
|
|
7
16
|
The current implementation restores the useful Role/Agent/session and CLI framework without restoring the later data-maintenance, lease, schedule, and recovery-ledger systems.
|
|
8
17
|
|
|
@@ -900,6 +909,15 @@ supported human-control boundary. A Codex Role uses an ordinary shared thread
|
|
|
900
909
|
and may be operated directly in Desktop; an active Desktop Turn creates bounded
|
|
901
910
|
backpressure for Yui rather than a failed Run.
|
|
902
911
|
|
|
912
|
+
AgentRun is the only durable Role scheduling state. Conversation state does not
|
|
913
|
+
carry a second current-Run pointer; each Provider Turn records a Run id only to
|
|
914
|
+
correlate its receipt and terminal event. If an Agent finishes a Yui Run before
|
|
915
|
+
the native Turn terminal arrives, the next mailbox batch may already become a
|
|
916
|
+
new AgentRun, but Agent Host keeps its input pending and submits it only after
|
|
917
|
+
the old Turn settles. TaskRole itself stores identity and desired launch
|
|
918
|
+
configuration, not runtime status; Role status shown by CLI/Web is derived from
|
|
919
|
+
the active AgentRun plus Session/Driver lifecycle facts.
|
|
920
|
+
|
|
903
921
|
Global Operator and global Role sessions remain native interactive CLIs:
|
|
904
922
|
|
|
905
923
|
```sh
|
package/dist/cli/updatePorts.js
CHANGED
|
@@ -226,10 +226,10 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
226
226
|
+ `but the staged/verified artifact was ${staged.version}. Refusing to trust a `
|
|
227
227
|
+ "different build than the one that passed preflight.");
|
|
228
228
|
}
|
|
229
|
-
// Existing managed Sessions may have been created by
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
229
|
+
// Existing managed Sessions may have been created by an earlier release.
|
|
230
|
+
// Retarget those authenticated, Manifest-referenced wrappers to the
|
|
231
|
+
// activated control plane before the replacement Controller starts so
|
|
232
|
+
// the update cannot strand a live Session.
|
|
233
233
|
const sessionCliRefresh = run(activeBinary, ["--json", "internal", "session-cli-refresh"], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
234
234
|
assertSpawnOk(sessionCliRefresh, "refresh managed Session CLI wrappers");
|
|
235
235
|
// Retain the exact path used by both doctor and version verification. The
|
package/dist/cli.js
CHANGED
|
@@ -348,7 +348,7 @@ export async function main() {
|
|
|
348
348
|
|| (process.env.YUI_SESSION_SCOPE === "global" && process.env.YUI_ROLE !== "operator")) {
|
|
349
349
|
throw usageError("Managed Session CLI refresh may be run only by the user or global Operator.");
|
|
350
350
|
}
|
|
351
|
-
const result = refreshManagedSessionCliWrappers(home);
|
|
351
|
+
const result = refreshManagedSessionCliWrappers(home, currentInvocationControlPlane(home));
|
|
352
352
|
emit(`Refreshed ${result.refreshed} legacy Session CLI wrapper(s); `
|
|
353
353
|
+ `${result.current} already current, ${result.skipped} skipped.`, false, result);
|
|
354
354
|
return;
|
|
@@ -19,7 +19,7 @@ import { renderRoleDetails } from "../output/rolePresentation.js";
|
|
|
19
19
|
import { createTaskMessage, taskMessageAuthorLabel } from "../message/message.js";
|
|
20
20
|
import { cancelInputRequest } from "../input/inputRequest.js";
|
|
21
21
|
import { retireExactActiveAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
|
|
22
|
-
import { activeRoleAgentBinding, copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole
|
|
22
|
+
import { activeRoleAgentBinding, copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole } from "../role/role.js";
|
|
23
23
|
import { agentRunDeliveryReceiptId, createAgentRun, failAgentRun, withAgentRunContextSnapshot, yieldAgentRun } from "../run/agentRun.js";
|
|
24
24
|
import { matchYieldReceipt } from "../run/yieldReceipt.js";
|
|
25
25
|
import { createRejectedYieldAttempt, rejectedYieldAttemptEventPayload, rejectedYieldAttemptFromTaskEvent, RUN_YIELD_REJECTED_EVENT } from "../run/rejectedYieldAttempt.js";
|
|
@@ -987,11 +987,6 @@ function retireTaskCommand(args, store, options) {
|
|
|
987
987
|
tx.saveInputRequest(task.id, cancelInputRequest(request, `Task retired: ${summary}`, now));
|
|
988
988
|
}
|
|
989
989
|
}
|
|
990
|
-
for (const role of tx.listRoles(task.id)) {
|
|
991
|
-
if (role.status !== "idle") {
|
|
992
|
-
tx.saveRole(task.id, updateRoleStatus(role, "idle", now));
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
990
|
for (const mailbox of tx.listWorkMailboxes()) {
|
|
996
991
|
if ((mailbox.target.kind === "task"
|
|
997
992
|
|| mailbox.target.kind === "role")
|
|
@@ -2374,7 +2369,6 @@ function dispatchWork(args, store, options) {
|
|
|
2374
2369
|
const role = requireRole(tx, task.id, unboundRun.roleName);
|
|
2375
2370
|
tx.saveAgentRun(runWithLineage);
|
|
2376
2371
|
tx.saveActiveAgentRun(runWithLineage);
|
|
2377
|
-
tx.saveRole(task.id, updateRoleStatus(role, "running", now));
|
|
2378
2372
|
enqueueWork(tx, roleMailbox(task.id, role.name), "run-dispatched", now, [
|
|
2379
2373
|
runRef(task.id, runWithLineage.id),
|
|
2380
2374
|
workItemRef(task.id, item.id)
|
|
@@ -4499,7 +4493,6 @@ function retryRun(args, store, options) {
|
|
|
4499
4493
|
});
|
|
4500
4494
|
tx.saveAgentRun(created);
|
|
4501
4495
|
tx.saveActiveAgentRun(created);
|
|
4502
|
-
tx.saveRole(task.id, updateRoleStatus(role, "running", now));
|
|
4503
4496
|
if (previous.workItemId !== undefined && retriedItemWithGroup !== null) {
|
|
4504
4497
|
const item = retriedItemWithGroup;
|
|
4505
4498
|
const workspace = tx.getWorkItemWorkspace(task.id, item.id);
|
|
@@ -6072,7 +6065,6 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
|
|
|
6072
6065
|
const laneReviewer = requireRole(tx, taskId, unboundRun.roleName);
|
|
6073
6066
|
tx.saveAgentRun(created);
|
|
6074
6067
|
tx.saveActiveAgentRun(created);
|
|
6075
|
-
tx.saveRole(taskId, updateRoleStatus(laneReviewer, "running", now));
|
|
6076
6068
|
enqueueWork(tx, roleMailbox(taskId, laneReviewer.name), "review-requested", now, [
|
|
6077
6069
|
runRef(taskId, created.id),
|
|
6078
6070
|
...(item === undefined ? [] : [workItemRef(taskId, item.id)])
|
|
@@ -7175,9 +7167,6 @@ function settleTaskExecutionForCompletion(store, taskId, roles, summary, now) {
|
|
|
7175
7167
|
}
|
|
7176
7168
|
store.saveTaskRoleSessionSet(sessions);
|
|
7177
7169
|
}
|
|
7178
|
-
if (role.status !== "idle") {
|
|
7179
|
-
store.saveRole(taskId, updateRoleStatus(role, "idle", now));
|
|
7180
|
-
}
|
|
7181
7170
|
if (!cleanupRequired)
|
|
7182
7171
|
continue;
|
|
7183
7172
|
const target = runtimeLifecycleTarget({
|
|
@@ -249,7 +249,7 @@ export function runTaskContextCommand(args, store, currentTaskReviewCandidate =
|
|
|
249
249
|
const effectiveSource = activeRun === undefined ? "Session" : "Run";
|
|
250
250
|
const creation = [...events].reverse().find((event) => (event.type === "role.added" && event.payload.role === role.name));
|
|
251
251
|
return [
|
|
252
|
-
` ${role.name} [${
|
|
252
|
+
` ${role.name} [${activeRun === undefined ? "idle" : "running"}]: ${role.activeAgentId}/${binding.adapterId}`,
|
|
253
253
|
` Desired: r${role.launchRevision}; Profile intent: ${role.defaultAccess}; Model: ${binding.config.model ?? "default"}; effort: ${binding.config.effort ?? "default"}; permission: ${binding.config.permission.strategy}`,
|
|
254
254
|
` Effective: ${effective === undefined
|
|
255
255
|
? "not started"
|
|
@@ -4,7 +4,6 @@ import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
|
4
4
|
import { usageError } from "../errors/cliError.js";
|
|
5
5
|
import { requestDurableJobCancel } from "../job/durableJob.js";
|
|
6
6
|
import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
|
|
7
|
-
import { updateRoleStatus } from "../role/role.js";
|
|
8
7
|
import { failAgentRun } from "../run/agentRun.js";
|
|
9
8
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
10
9
|
import { startTaskExecution, stopTaskExecution } from "../task/task.js";
|
|
@@ -83,10 +82,6 @@ export function stopTaskExecutionCommand(request, store, options = {}) {
|
|
|
83
82
|
if (sessions !== null && sessions.inFlight !== null) {
|
|
84
83
|
tx.saveTaskRoleSessionSet(terminalizeCurrentRun(sessions, now));
|
|
85
84
|
}
|
|
86
|
-
const role = tx.getRole(task.id, roleName);
|
|
87
|
-
if (role !== null && role.status !== "idle") {
|
|
88
|
-
tx.saveRole(task.id, updateRoleStatus(role, "idle", now));
|
|
89
|
-
}
|
|
90
85
|
}
|
|
91
86
|
// A stop is allowed to discard stale pointer projections even when their
|
|
92
87
|
// historical Runs are already terminal.
|
|
@@ -78,7 +78,11 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
78
78
|
const legacyNext = deriveNextAction(task, brief, workItems, openInputRequests, blockers, pendingWakeup);
|
|
79
79
|
const leader = {
|
|
80
80
|
role: "leader",
|
|
81
|
-
roleStatus: leaderRole
|
|
81
|
+
roleStatus: leaderRole === null
|
|
82
|
+
? "missing"
|
|
83
|
+
: agentRuns.some((run) => run.roleName === "leader" && run.status === "active")
|
|
84
|
+
? "running"
|
|
85
|
+
: roleSessions.find((session) => session.roleName === "leader")?.status ?? "idle",
|
|
82
86
|
summary: brief?.leaderSummary ?? null,
|
|
83
87
|
currentFocus: brief?.currentFocus ?? null,
|
|
84
88
|
updatedAt: brief?.updatedAt ?? null,
|
|
@@ -85,7 +85,6 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
85
85
|
? "-"
|
|
86
86
|
: status.launchDrift ? "pending next launch" : "none"}`,
|
|
87
87
|
` Run/session ${status.runSessionDrift ? "snapshot mismatch" : "snapshot consistent"}`,
|
|
88
|
-
` Role state ${status.role.status}`,
|
|
89
88
|
` Active work ${activeWork}`,
|
|
90
89
|
` Active run ${activeRun}`,
|
|
91
90
|
...(lastRun === undefined ? [] : [` Last run ${lastRun}`]),
|
|
@@ -254,9 +253,6 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
254
253
|
healthReason: "the native Session is unbound; verified runtime cleanup is pending"
|
|
255
254
|
};
|
|
256
255
|
}
|
|
257
|
-
if (role.status === "failed" || role.status === "exited") {
|
|
258
|
-
return { health: "failed", healthReason: `persisted Role state is ${role.status}` };
|
|
259
|
-
}
|
|
260
256
|
if (nativeSession?.status === "broken") {
|
|
261
257
|
// Issue 09: a broken Session only fails a live Run. When the last Run
|
|
262
258
|
// already yielded, the Session death is a lifecycle event, not a Run
|
|
@@ -287,12 +283,6 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
287
283
|
};
|
|
288
284
|
}
|
|
289
285
|
if (activeRun !== null) {
|
|
290
|
-
if (role.status !== "running") {
|
|
291
|
-
return {
|
|
292
|
-
health: "needs-attention",
|
|
293
|
-
healthReason: `the active Run conflicts with persisted Role state ${role.status}`
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
286
|
if (activeRun.deliveredAt !== undefined && tmux.state !== "running") {
|
|
297
287
|
return { health: "needs-attention", healthReason: "the delivered active Run has no live tmux pane" };
|
|
298
288
|
}
|
|
@@ -339,9 +329,6 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
339
329
|
}
|
|
340
330
|
}
|
|
341
331
|
}
|
|
342
|
-
if (activeRun === null && role.status === "running") {
|
|
343
|
-
return { health: "needs-attention", healthReason: "the Role is running without an active Run" };
|
|
344
|
-
}
|
|
345
332
|
if (nativeSession?.status === "running" && tmux.state !== "running") {
|
|
346
333
|
return { health: "needs-attention", healthReason: "the native session is running without a live tmux pane" };
|
|
347
334
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { chmodSync, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { exactControlPlaneDigest, serializeExactDescriptor } from "../runtime/exactControlPlane.js";
|
|
4
|
+
import { exactControlPlaneCommandPrefix, exactControlPlaneDigest, serializeExactDescriptor } from "../runtime/exactControlPlane.js";
|
|
5
5
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
6
6
|
import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
7
7
|
export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const ordinarySessionCli = ["#!/bin/sh", "exec yui \"$@\"", ""].join("\n");
|
|
9
|
+
function renderSessionCli(controlPlane) {
|
|
10
|
+
return [
|
|
11
|
+
"#!/bin/sh",
|
|
12
|
+
`exec ${exactControlPlaneCommandPrefix(controlPlane)} \"$@\"`,
|
|
13
|
+
""
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
|
13
16
|
/** Read back one immutable Session Manifest and verify its content digest. */
|
|
14
17
|
export function readSessionBootstrapManifest(path) {
|
|
15
18
|
const source = resolve(path);
|
|
@@ -84,10 +87,11 @@ export function materializeSessionBootstrap(input) {
|
|
|
84
87
|
const controlDigest = exactControlPlaneDigest(input.controlPlane);
|
|
85
88
|
const descriptorPath = resolve(join(home, "runtime", "control-plane", `${controlDigest}.json`));
|
|
86
89
|
writeImmutableText(descriptorPath, `${serializeExactDescriptor(input.controlPlane)}\n`);
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
90
|
+
// Provider command runners may rebuild PATH independently of the managed
|
|
91
|
+
// process environment. Keep the Session entry point deterministic; the
|
|
92
|
+
// existing continuity preflight accepts compatible package replacement at
|
|
93
|
+
// this path without treating package version as Session identity.
|
|
94
|
+
const sessionCliContent = renderSessionCli(input.controlPlane);
|
|
91
95
|
const sessionCliDigest = digest(sessionCliContent);
|
|
92
96
|
const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
|
|
93
97
|
writeImmutableText(sessionCliPath, sessionCliContent);
|
|
@@ -126,11 +130,11 @@ export function materializeSessionBootstrap(input) {
|
|
|
126
130
|
roleProfileRef: { digest: profileDigest, path: roleProfilePath },
|
|
127
131
|
contextProtocol: input.owner.scope === "global"
|
|
128
132
|
? {
|
|
129
|
-
loadCommand: "
|
|
133
|
+
loadCommand: "\"$YUI_SESSION_CLI\" session context \"$YUI_ROLE\" --json"
|
|
130
134
|
}
|
|
131
135
|
: {
|
|
132
|
-
loadCommand: "
|
|
133
|
-
expandCommand: "
|
|
136
|
+
loadCommand: "\"$YUI_SESSION_CLI\" task run context \"$YUI_TASK_ID/<run-id>\" --json",
|
|
137
|
+
expandCommand: "\"$YUI_SESSION_CLI\" task run context expand \"$YUI_TASK_ID/<run-id>\" <ref-id> --store <store> --mode full --json"
|
|
134
138
|
}
|
|
135
139
|
};
|
|
136
140
|
const manifest = Object.freeze({ ...body, digest: digest(body) });
|
|
@@ -145,14 +149,13 @@ export function materializeSessionBootstrap(input) {
|
|
|
145
149
|
});
|
|
146
150
|
}
|
|
147
151
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* Manifest and its frozen descriptor stay immutable and continue to
|
|
152
|
-
* authenticate the Session; repeated refreshes are no-ops.
|
|
152
|
+
* Retargets known managed wrappers to the current resolved control plane after
|
|
153
|
+
* a compatible update. Only a valid Session Manifest may nominate a wrapper,
|
|
154
|
+
* and only Yui's ordinary or exact two-line wrapper shapes are changed.
|
|
153
155
|
*/
|
|
154
|
-
export function refreshManagedSessionCliWrappers(homeInput) {
|
|
156
|
+
export function refreshManagedSessionCliWrappers(homeInput, controlPlane) {
|
|
155
157
|
const home = resolve(homeInput);
|
|
158
|
+
const currentSessionCli = renderSessionCli(controlPlane);
|
|
156
159
|
const manifestDirectory = resolve(join(home, "runtime", "session-manifests"));
|
|
157
160
|
const sessionCliDirectory = resolve(join(home, "runtime", "session-cli"));
|
|
158
161
|
if (!existsSync(manifestDirectory)) {
|
|
@@ -189,21 +192,21 @@ export function refreshManagedSessionCliWrappers(homeInput) {
|
|
|
189
192
|
continue;
|
|
190
193
|
}
|
|
191
194
|
const content = readFileSync(wrapperPath, "utf8");
|
|
192
|
-
if (content ===
|
|
195
|
+
if (content === currentSessionCli) {
|
|
193
196
|
current += 1;
|
|
194
197
|
continue;
|
|
195
198
|
}
|
|
196
|
-
if (!
|
|
199
|
+
if (content !== ordinarySessionCli && !isExactSessionCli(content)) {
|
|
197
200
|
skipped += 1;
|
|
198
201
|
continue;
|
|
199
202
|
}
|
|
200
|
-
writeTextFileAtomically(wrapperPath,
|
|
203
|
+
writeTextFileAtomically(wrapperPath, currentSessionCli);
|
|
201
204
|
chmodSync(wrapperPath, 0o700);
|
|
202
205
|
refreshed += 1;
|
|
203
206
|
}
|
|
204
207
|
return Object.freeze({ refreshed, current, skipped });
|
|
205
208
|
}
|
|
206
|
-
function
|
|
209
|
+
function isExactSessionCli(content) {
|
|
207
210
|
return /^#!\/bin\/sh\nexec [^\n]+ '--yui-control' '[a-f0-9]{64}' "\$@"\n$/u.test(content);
|
|
208
211
|
}
|
|
209
212
|
function writeImmutableText(path, content) {
|
|
@@ -96,8 +96,6 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
|
|
|
96
96
|
}
|
|
97
97
|
const newlyIdleBusyTaskIds = new Set(initialWakeupResults.flatMap((result) => (result.reason === "busy"
|
|
98
98
|
&& store.getActiveAgentRun(result.taskId, "leader") === null
|
|
99
|
-
&& (typeof store.hasInFlightTurn !== "function"
|
|
100
|
-
|| !store.hasInFlightTurn(result.taskId, "leader"))
|
|
101
99
|
? [result.taskId]
|
|
102
100
|
: [])));
|
|
103
101
|
// Phase-one Leader Runs did not exist at the pass's liveness boundary.
|