@zq-silk/yui 0.0.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/ARCHITECTURE.md +141 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/agent/adapterCatalog.js +10 -0
- package/dist/agent/agent.js +89 -0
- package/dist/agent/agentRegistry.js +10 -0
- package/dist/agent/argumentPolicy.js +80 -0
- package/dist/brief/taskBrief.js +37 -0
- package/dist/cli/commandCatalog.js +647 -0
- package/dist/cli/completion.js +111 -0
- package/dist/cli/completionWizard.js +143 -0
- package/dist/cli/dynamicCompletion.js +48 -0
- package/dist/cli/helpRenderer.js +32 -0
- package/dist/cli/interactionCandidates.js +139 -0
- package/dist/cli/interactionPolicy.js +389 -0
- package/dist/cli/interactiveSelection.js +185 -0
- package/dist/cli/invocationRouter.js +51 -0
- package/dist/cli/roleOptionCatalog.js +67 -0
- package/dist/cli/roleWizard.js +546 -0
- package/dist/cli/selectionPorts.js +1 -0
- package/dist/cli/updateCommand.js +22 -0
- package/dist/cli.js +402 -0
- package/dist/commands/agentCommands.js +196 -0
- package/dist/commands/globalRoleCommands.js +367 -0
- package/dist/commands/jobCommands.js +100 -0
- package/dist/commands/operatorCommands.js +38 -0
- package/dist/commands/repositoryCommands.js +86 -0
- package/dist/commands/roleConfiguration.js +201 -0
- package/dist/commands/taskCommands.js +1344 -0
- package/dist/commands/taskContextCommand.js +215 -0
- package/dist/commands/taskInputCommands.js +423 -0
- package/dist/commands/taskRoleRuntimeStatus.js +152 -0
- package/dist/completion/completionInstaller.js +168 -0
- package/dist/completion/completionPort.js +1 -0
- package/dist/completion/completionState.js +137 -0
- package/dist/completion/completionWizard.js +125 -0
- package/dist/completion/fileCompletionManager.js +51 -0
- package/dist/config/yuiConfig.js +17 -0
- package/dist/context/dispatchContext.js +74 -0
- package/dist/controller/clientRuntime.js +215 -0
- package/dist/controller/controller.js +158 -0
- package/dist/controller/controllerMain.js +37 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +322 -0
- package/dist/controller/runtime.js +31 -0
- package/dist/controller/sessionNotify.js +136 -0
- package/dist/core/controllerClient.js +127 -0
- package/dist/core/controllerServer.js +269 -0
- package/dist/core/protocol.js +169 -0
- package/dist/decision/decision.js +42 -0
- package/dist/doctor/doctor.js +229 -0
- package/dist/errors/cliError.js +38 -0
- package/dist/event/taskEvent.js +44 -0
- package/dist/executor/agentAdapter.js +338 -0
- package/dist/executor/agentExecutor.js +144 -0
- package/dist/executor/executorRegistry.js +101 -0
- package/dist/executor/fileRoleLaunchPlanner.js +156 -0
- package/dist/executor/launchPlan.js +16 -0
- package/dist/input/inputRequest.js +326 -0
- package/dist/message/message.js +69 -0
- package/dist/milestone/milestone.js +27 -0
- package/dist/operator/operatorContext.js +66 -0
- package/dist/output/rolePresentation.js +82 -0
- package/dist/output/table.js +77 -0
- package/dist/output/terminal.js +198 -0
- package/dist/repository/gitWorkspace.js +210 -0
- package/dist/repository/repository.js +55 -0
- package/dist/repository/taskWorkspacePreparer.js +256 -0
- package/dist/role/role.js +246 -0
- package/dist/role/systemRoles.js +20 -0
- package/dist/run/agentRun.js +102 -0
- package/dist/scheduler/activeRoleRunDelivery.js +94 -0
- package/dist/scheduler/archivedTaskRuntime.js +12 -0
- package/dist/scheduler/leaderFailure.js +18 -0
- package/dist/scheduler/leaderWakeupProcessor.js +143 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +85 -0
- package/dist/scheduler/operatorNotification.js +17 -0
- package/dist/scheduler/pendingWakeup.js +33 -0
- package/dist/scheduler/ports.js +1 -0
- package/dist/scheduler/roleRunLiveness.js +41 -0
- package/dist/scheduler/wakeupQueue.js +13 -0
- package/dist/setup/setupCommand.js +317 -0
- package/dist/storage/durableFile.js +38 -0
- package/dist/storage/storageSchema.js +259 -0
- package/dist/storage/taskStore.js +1032 -0
- package/dist/task/task.js +216 -0
- package/dist/tmux/commandExecutor.js +69 -0
- package/dist/tmux/terminalHandoff.js +17 -0
- package/dist/tmux/tmuxManager.js +408 -0
- package/dist/workItem/workItem.js +45 -0
- package/dist/worktree/roleWorkspace.js +62 -0
- package/i18n/README.zh-CN.md +205 -0
- package/package.json +47 -0
- package/skills/yui-leader/SKILL.md +72 -0
- package/skills/yui-operator/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +31 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
2
|
+
return {
|
|
3
|
+
schemaVersion: 1,
|
|
4
|
+
owner: normalizeOwner(owner),
|
|
5
|
+
activeAgentId: requireSafeIdentity(activeAgentId, "Active Agent id"),
|
|
6
|
+
sessions: {},
|
|
7
|
+
updatedAt: now.toISOString()
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function activeRoleAgentSession(set) {
|
|
11
|
+
if (set === null)
|
|
12
|
+
return null;
|
|
13
|
+
validateRoleSessionSet(set);
|
|
14
|
+
return set.sessions[set.activeAgentId] ?? null;
|
|
15
|
+
}
|
|
16
|
+
export function recordRoleAgentSession(set, input, now) {
|
|
17
|
+
validateRoleSessionSet(set);
|
|
18
|
+
const agentId = requireSafeIdentity(input.agentId, "Agent id");
|
|
19
|
+
const adapterId = requireText(input.adapterId, "Agent adapter id");
|
|
20
|
+
const nativeSessionId = requireText(input.nativeSessionId, "Native session id");
|
|
21
|
+
if (!isAgentSessionStatus(input.status)) {
|
|
22
|
+
throw new Error(`Role Agent session status is invalid: ${input.status}.`);
|
|
23
|
+
}
|
|
24
|
+
if (input.policy !== "fixed" && input.policy !== "leader-controlled") {
|
|
25
|
+
throw new Error(`Role Agent session policy is invalid: ${input.policy}.`);
|
|
26
|
+
}
|
|
27
|
+
const existing = set.sessions[agentId];
|
|
28
|
+
if (existing !== undefined && existing.adapterId !== adapterId) {
|
|
29
|
+
throw new Error(`Role Agent session adapter cannot change: ${agentId}.`);
|
|
30
|
+
}
|
|
31
|
+
const timestamp = now.toISOString();
|
|
32
|
+
const session = {
|
|
33
|
+
schemaVersion: 1,
|
|
34
|
+
agentId,
|
|
35
|
+
adapterId,
|
|
36
|
+
nativeSessionId,
|
|
37
|
+
policy: input.policy,
|
|
38
|
+
status: input.status,
|
|
39
|
+
createdAt: existing?.createdAt ?? timestamp,
|
|
40
|
+
updatedAt: timestamp
|
|
41
|
+
};
|
|
42
|
+
validateRoleAgentSession(session, agentId);
|
|
43
|
+
const updated = {
|
|
44
|
+
...set,
|
|
45
|
+
sessions: { ...set.sessions, [agentId]: session },
|
|
46
|
+
updatedAt: timestamp
|
|
47
|
+
};
|
|
48
|
+
validateRoleSessionSet(updated);
|
|
49
|
+
return updated;
|
|
50
|
+
}
|
|
51
|
+
export function createRoleAgentSession(input, now) {
|
|
52
|
+
const agentId = requireSafeIdentity(input.agentId, "Agent id");
|
|
53
|
+
const set = createRoleSessionSet({ scope: "global", roleName: "session-factory" }, agentId, now);
|
|
54
|
+
return recordRoleAgentSession(set, input, now).sessions[agentId];
|
|
55
|
+
}
|
|
56
|
+
export function updateRoleAgentSessionStatus(set, agentId, status, now) {
|
|
57
|
+
validateRoleSessionSet(set);
|
|
58
|
+
const normalizedAgentId = requireSafeIdentity(agentId, "Agent id");
|
|
59
|
+
if (!isAgentSessionStatus(status)) {
|
|
60
|
+
throw new Error(`Role Agent session status is invalid: ${status}.`);
|
|
61
|
+
}
|
|
62
|
+
const existing = set.sessions[normalizedAgentId];
|
|
63
|
+
if (existing === undefined) {
|
|
64
|
+
throw new Error(`No Role session is recorded for Agent: ${normalizedAgentId}.`);
|
|
65
|
+
}
|
|
66
|
+
const timestamp = now.toISOString();
|
|
67
|
+
const updated = {
|
|
68
|
+
...set,
|
|
69
|
+
sessions: {
|
|
70
|
+
...set.sessions,
|
|
71
|
+
[normalizedAgentId]: { ...existing, status, updatedAt: timestamp }
|
|
72
|
+
},
|
|
73
|
+
updatedAt: timestamp
|
|
74
|
+
};
|
|
75
|
+
validateRoleSessionSet(updated);
|
|
76
|
+
return updated;
|
|
77
|
+
}
|
|
78
|
+
export function roleAgentSessionResumeMode(set, agentId) {
|
|
79
|
+
if (set === null)
|
|
80
|
+
return "new";
|
|
81
|
+
validateRoleSessionSet(set);
|
|
82
|
+
const session = set.sessions[requireSafeIdentity(agentId, "Agent id")];
|
|
83
|
+
return session === undefined ? "new" : "resume";
|
|
84
|
+
}
|
|
85
|
+
export function validateRoleSessionSet(set) {
|
|
86
|
+
if (set.schemaVersion !== 1)
|
|
87
|
+
throw new Error("Role session set schema version is invalid.");
|
|
88
|
+
normalizeOwner(set.owner);
|
|
89
|
+
requireSafeIdentity(set.activeAgentId, "Active Agent id");
|
|
90
|
+
for (const [agentId, session] of Object.entries(set.sessions)) {
|
|
91
|
+
validateRoleAgentSession(session, agentId);
|
|
92
|
+
}
|
|
93
|
+
requireText(set.updatedAt, "Role session set update timestamp");
|
|
94
|
+
return set;
|
|
95
|
+
}
|
|
96
|
+
export function validateRoleAgentSession(session, expectedAgentId = session.agentId) {
|
|
97
|
+
if (session.schemaVersion !== 1) {
|
|
98
|
+
throw new Error(`Role Agent session schema version is invalid: ${expectedAgentId}.`);
|
|
99
|
+
}
|
|
100
|
+
const agentId = requireSafeIdentity(session.agentId, "Agent id");
|
|
101
|
+
if (agentId !== expectedAgentId) {
|
|
102
|
+
throw new Error(`Role Agent session identity is inconsistent: ${expectedAgentId}.`);
|
|
103
|
+
}
|
|
104
|
+
requireText(session.adapterId, "Agent adapter id");
|
|
105
|
+
requireText(session.nativeSessionId, "Native session id");
|
|
106
|
+
if (session.policy !== "fixed" && session.policy !== "leader-controlled") {
|
|
107
|
+
throw new Error(`Role Agent session policy is invalid: ${agentId}.`);
|
|
108
|
+
}
|
|
109
|
+
if (!isAgentSessionStatus(session.status)) {
|
|
110
|
+
throw new Error(`Role Agent session status is invalid: ${agentId}.`);
|
|
111
|
+
}
|
|
112
|
+
requireText(session.createdAt, "Role Agent session creation timestamp");
|
|
113
|
+
requireText(session.updatedAt, "Role Agent session update timestamp");
|
|
114
|
+
return session;
|
|
115
|
+
}
|
|
116
|
+
function normalizeOwner(owner) {
|
|
117
|
+
const roleName = requireSafeIdentity(owner.roleName, "Role name");
|
|
118
|
+
return owner.scope === "global"
|
|
119
|
+
? { scope: "global", roleName }
|
|
120
|
+
: {
|
|
121
|
+
scope: "task",
|
|
122
|
+
taskId: requireSafeIdentity(owner.taskId, "Task id"),
|
|
123
|
+
roleName
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function isAgentSessionStatus(value) {
|
|
127
|
+
return ["reserved", "ready", "running", "stopped", "broken"].includes(value);
|
|
128
|
+
}
|
|
129
|
+
function requireSafeIdentity(value, label) {
|
|
130
|
+
const normalized = requireText(value, label);
|
|
131
|
+
if (["__proto__", "prototype", "constructor", ".", ".."].includes(normalized)
|
|
132
|
+
|| /[\/\\\0]/.test(normalized)) {
|
|
133
|
+
throw new Error(`${label} is invalid.`);
|
|
134
|
+
}
|
|
135
|
+
return normalized;
|
|
136
|
+
}
|
|
137
|
+
function requireText(value, label) {
|
|
138
|
+
if (typeof value !== "string" || value.includes("\0"))
|
|
139
|
+
throw new Error(`${label} is invalid.`);
|
|
140
|
+
const normalized = value.trim();
|
|
141
|
+
if (normalized.length === 0)
|
|
142
|
+
throw new Error(`${label} is required.`);
|
|
143
|
+
return normalized;
|
|
144
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Scheduler-to-tmux adapter. It retains only in-process prepared launch data;
|
|
4
|
+
* durable session identity remains owned by FileTaskStore.
|
|
5
|
+
*/
|
|
6
|
+
export class ExecutorRegistry {
|
|
7
|
+
planner;
|
|
8
|
+
tmux;
|
|
9
|
+
readiness;
|
|
10
|
+
#prepared = new Map();
|
|
11
|
+
constructor(planner, tmux, readiness = agentComposerReadinessProbe) {
|
|
12
|
+
this.planner = planner;
|
|
13
|
+
this.tmux = tmux;
|
|
14
|
+
this.readiness = readiness;
|
|
15
|
+
}
|
|
16
|
+
async prepareRoleSession(input) {
|
|
17
|
+
if (input.mode === "resume" && !hasText(input.nativeSessionId)) {
|
|
18
|
+
throw new Error("Role session resume requires a native session id.");
|
|
19
|
+
}
|
|
20
|
+
const planned = this.planner.plan(input);
|
|
21
|
+
this.tmux.ensureRoleWindow(input.taskId, planned.role, planned.launch);
|
|
22
|
+
const delivery = {
|
|
23
|
+
deliveryId: preparedDeliveryId(input),
|
|
24
|
+
taskId: input.taskId,
|
|
25
|
+
roleName: input.roleName,
|
|
26
|
+
agentId: input.agentId,
|
|
27
|
+
adapterId: input.adapterId,
|
|
28
|
+
mode: input.mode
|
|
29
|
+
};
|
|
30
|
+
this.#prepared.set(delivery.deliveryId, planned);
|
|
31
|
+
return delivery;
|
|
32
|
+
}
|
|
33
|
+
async waitUntilReady(delivery) {
|
|
34
|
+
const planned = this.requirePrepared(delivery);
|
|
35
|
+
this.tmux.waitUntilReady(delivery.taskId, delivery.roleName, this.readiness(delivery.adapterId));
|
|
36
|
+
return { prepared: delivery, session: planned.session };
|
|
37
|
+
}
|
|
38
|
+
async sendOnce(input) {
|
|
39
|
+
this.requirePrepared(input.delivery.prepared);
|
|
40
|
+
return this.tmux.sendRoleInputOnce(input.delivery.prepared.taskId, input.delivery.prepared.roleName, input.receiptId, input.text, this.readiness(input.delivery.prepared.adapterId));
|
|
41
|
+
}
|
|
42
|
+
async notifyOperatorInputOnce(input) {
|
|
43
|
+
if (this.tmux.probeRoleStatus("operator", input.roleName) !== "running") {
|
|
44
|
+
return "unavailable";
|
|
45
|
+
}
|
|
46
|
+
return this.tmux.sendRoleInputOnceIfReady("operator", input.roleName, input.receiptId, input.text, this.readiness(input.adapterId));
|
|
47
|
+
}
|
|
48
|
+
async inspectRole(input) {
|
|
49
|
+
return this.tmux.probeRoleStatus(input.taskId, input.roleName) === "running"
|
|
50
|
+
? "present"
|
|
51
|
+
: "absent";
|
|
52
|
+
}
|
|
53
|
+
async findExistingReceipt(input) {
|
|
54
|
+
const planned = this.requirePrepared(input.delivery);
|
|
55
|
+
return this.tmux.hasDeliveryReceipt(input.delivery.taskId, input.delivery.roleName, input.receiptId) ? { prepared: input.delivery, session: planned.session } : null;
|
|
56
|
+
}
|
|
57
|
+
async stopTask(taskId) {
|
|
58
|
+
return this.tmux.stopTask(taskId);
|
|
59
|
+
}
|
|
60
|
+
requirePrepared(delivery) {
|
|
61
|
+
const planned = this.#prepared.get(delivery.deliveryId);
|
|
62
|
+
if (planned === undefined) {
|
|
63
|
+
throw new Error(`Role delivery is not prepared: ${delivery.deliveryId}.`);
|
|
64
|
+
}
|
|
65
|
+
return planned;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function agentComposerReadinessProbe(adapterId) {
|
|
69
|
+
switch (adapterId) {
|
|
70
|
+
case "codex":
|
|
71
|
+
return (pane) => {
|
|
72
|
+
const content = pane.content.replace(/\r/g, "");
|
|
73
|
+
return livePane(pane)
|
|
74
|
+
&& !/Press enter to continue|select.*update|update selector/i.test(content)
|
|
75
|
+
&& content.includes("OpenAI Codex")
|
|
76
|
+
&& content.includes("/model to change")
|
|
77
|
+
&& /(?:^|\n)\s*›\s*(?:$|\S)/u.test(content);
|
|
78
|
+
};
|
|
79
|
+
case "claude":
|
|
80
|
+
return (pane) => livePane(pane)
|
|
81
|
+
&& /(?:^|\n)\s*❯\s*(?:$|\S)/u.test(pane.content.replace(/\r/g, ""));
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`No tmux readiness probe is registered for Agent adapter: ${adapterId}.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function livePane(pane) {
|
|
87
|
+
return !pane.dead && pane.pid !== undefined && pane.currentCommand.trim().length > 0;
|
|
88
|
+
}
|
|
89
|
+
function preparedDeliveryId(input) {
|
|
90
|
+
return createHash("sha256").update(JSON.stringify([
|
|
91
|
+
input.taskId,
|
|
92
|
+
input.roleName,
|
|
93
|
+
input.agentId,
|
|
94
|
+
input.adapterId,
|
|
95
|
+
input.mode,
|
|
96
|
+
input.nativeSessionId ?? null
|
|
97
|
+
])).digest("hex");
|
|
98
|
+
}
|
|
99
|
+
function hasText(value) {
|
|
100
|
+
return value !== undefined && value.trim().length > 0;
|
|
101
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
5
|
+
import { activeRoleAgentBinding } from "../role/role.js";
|
|
6
|
+
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
7
|
+
/** Builds managed native Agent launches from the authoritative Task records. */
|
|
8
|
+
export class FileRoleLaunchPlanner {
|
|
9
|
+
home;
|
|
10
|
+
store;
|
|
11
|
+
#environment;
|
|
12
|
+
#createNativeSessionId;
|
|
13
|
+
#cliPath;
|
|
14
|
+
constructor(home, store, options = {}) {
|
|
15
|
+
this.home = home;
|
|
16
|
+
this.store = store;
|
|
17
|
+
this.#environment = options.environment ?? process.env;
|
|
18
|
+
this.#createNativeSessionId = options.createNativeSessionId ?? randomUUID;
|
|
19
|
+
this.#cliPath = options.cliPath
|
|
20
|
+
?? fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
21
|
+
}
|
|
22
|
+
plan(input) {
|
|
23
|
+
const task = this.store.getTask(input.taskId);
|
|
24
|
+
if (task === null)
|
|
25
|
+
throw new Error(`Task not found: ${input.taskId}.`);
|
|
26
|
+
if (task.status !== "active")
|
|
27
|
+
throw new Error(`Task is not active: ${input.taskId}.`);
|
|
28
|
+
const role = this.store.getRole(input.taskId, input.roleName);
|
|
29
|
+
if (role === null)
|
|
30
|
+
throw new Error(`Role not found: ${input.taskId}/${input.roleName}.`);
|
|
31
|
+
if (task.repositoryId !== undefined) {
|
|
32
|
+
const workspace = this.store.getRoleWorkspace(task.id, role.name);
|
|
33
|
+
if (task.cwd === undefined
|
|
34
|
+
|| workspace === null
|
|
35
|
+
|| workspace.repositoryId !== task.repositoryId
|
|
36
|
+
|| workspace.path !== role.workspace) {
|
|
37
|
+
throw new Error(`Role workspace is not ready: ${input.taskId}/${input.roleName}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return this.#compile(role, input, { scope: "task", taskId: task.id }, this.store.getRoleSession(task.id, role.name)?.nativeSessionId);
|
|
41
|
+
}
|
|
42
|
+
planGlobalRole(input) {
|
|
43
|
+
const role = this.store.getGlobalRole(input.roleName);
|
|
44
|
+
if (role === null)
|
|
45
|
+
throw new Error(`Global Role not found: ${input.roleName}.`);
|
|
46
|
+
return this.#compile(role, input, { scope: "global" }, this.store.getGlobalRoleSessionSet(role.name)?.sessions[role.activeAgentId]?.nativeSessionId);
|
|
47
|
+
}
|
|
48
|
+
#compile(role, input, owner, knownNativeSessionId) {
|
|
49
|
+
const binding = activeRoleAgentBinding(role);
|
|
50
|
+
if (binding.agentId !== input.agentId || binding.adapterId !== input.adapterId) {
|
|
51
|
+
throw new Error(`Role launch identity changed: ${role.name}.`);
|
|
52
|
+
}
|
|
53
|
+
const configured = this.store.getConfiguredAgent(input.agentId);
|
|
54
|
+
if (configured === null)
|
|
55
|
+
throw new Error(`Configured Agent not found: ${input.agentId}.`);
|
|
56
|
+
if (configured.adapterId !== binding.adapterId) {
|
|
57
|
+
throw new Error(`Configured Agent adapter changed: ${input.agentId}.`);
|
|
58
|
+
}
|
|
59
|
+
const agent = configuredAgentToDefinition(configured);
|
|
60
|
+
const adapter = resolveAgentAdapter(binding.adapterId);
|
|
61
|
+
const compileInput = {
|
|
62
|
+
agent,
|
|
63
|
+
config: binding.config,
|
|
64
|
+
workspace: role.workspace,
|
|
65
|
+
systemPrompt: role.systemPrompt
|
|
66
|
+
};
|
|
67
|
+
if (input.mode === "resume"
|
|
68
|
+
&& knownNativeSessionId !== undefined
|
|
69
|
+
&& input.nativeSessionId !== knownNativeSessionId) {
|
|
70
|
+
throw new Error(`Role resume changed the fixed native session id: ${role.name}.`);
|
|
71
|
+
}
|
|
72
|
+
// A previous attempt may have persisted a preallocated/discovered ID
|
|
73
|
+
// before its receipt was committed. Reuse that fixed session rather than
|
|
74
|
+
// allocating a second native session for the same durable AgentRun.
|
|
75
|
+
const resumeNativeSessionId = input.mode === "resume"
|
|
76
|
+
? requireText(input.nativeSessionId, "Native session id")
|
|
77
|
+
: knownNativeSessionId;
|
|
78
|
+
const launchMode = resumeNativeSessionId === undefined
|
|
79
|
+
? "new"
|
|
80
|
+
: "resume";
|
|
81
|
+
const compiled = launchMode === "resume"
|
|
82
|
+
? adapter.compileResume({
|
|
83
|
+
...compileInput,
|
|
84
|
+
nativeSessionId: resumeNativeSessionId
|
|
85
|
+
})
|
|
86
|
+
: adapter.compileNew(compileInput);
|
|
87
|
+
let args = [...compiled.argv];
|
|
88
|
+
let session;
|
|
89
|
+
if (binding.adapterId === "codex") {
|
|
90
|
+
args = addCodexSessionNotify(args, launchMode, this.#cliPath);
|
|
91
|
+
session = launchMode === "resume"
|
|
92
|
+
? readySession(input.agentId, binding.adapterId, resumeNativeSessionId)
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
95
|
+
else if (launchMode === "new") {
|
|
96
|
+
const nativeSessionId = requireText(this.#createNativeSessionId(), "Native session id");
|
|
97
|
+
args.push("--session-id", nativeSessionId);
|
|
98
|
+
session = readySession(input.agentId, binding.adapterId, nativeSessionId);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
session = readySession(input.agentId, binding.adapterId, resumeNativeSessionId);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
role: {
|
|
105
|
+
name: role.name,
|
|
106
|
+
workspace: role.workspace,
|
|
107
|
+
...(owner.scope === "task" ? { status: role.status } : {})
|
|
108
|
+
},
|
|
109
|
+
launch: {
|
|
110
|
+
command: configured.command,
|
|
111
|
+
args,
|
|
112
|
+
env: {
|
|
113
|
+
...resolveAgentEnvironment(agent, this.#environment),
|
|
114
|
+
YUI_HOME: resolve(this.home),
|
|
115
|
+
YUI_SESSION_SCOPE: owner.scope,
|
|
116
|
+
...(owner.scope === "task" ? { YUI_TASK_ID: owner.taskId } : {}),
|
|
117
|
+
YUI_ROLE: role.name,
|
|
118
|
+
YUI_AGENT_ID: configured.id,
|
|
119
|
+
YUI_ADAPTER_ID: configured.adapterId,
|
|
120
|
+
YUI_WORKSPACE: role.workspace
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
session
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Codex invokes this argv after each completed turn and appends one JSON
|
|
129
|
+
* payload. JSON arrays are valid TOML arrays for the managed config override.
|
|
130
|
+
*/
|
|
131
|
+
export function codexSessionNotifyConfig(cliPath) {
|
|
132
|
+
return `notify=${JSON.stringify([process.execPath, cliPath, "internal", "session-notify"])}`;
|
|
133
|
+
}
|
|
134
|
+
function addCodexSessionNotify(args, mode, cliPath) {
|
|
135
|
+
const managed = ["--config", codexSessionNotifyConfig(cliPath)];
|
|
136
|
+
if (mode === "new")
|
|
137
|
+
return [...args, ...managed];
|
|
138
|
+
if (args.length < 2 || args.at(-2) !== "resume") {
|
|
139
|
+
throw new Error("Codex resume launch shape is invalid.");
|
|
140
|
+
}
|
|
141
|
+
return [...args.slice(0, -2), ...managed, ...args.slice(-2)];
|
|
142
|
+
}
|
|
143
|
+
function readySession(agentId, adapterId, nativeSessionId) {
|
|
144
|
+
return {
|
|
145
|
+
agentId,
|
|
146
|
+
adapterId,
|
|
147
|
+
nativeSessionId: requireText(nativeSessionId, "Native session id"),
|
|
148
|
+
status: "ready"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function requireText(value, label) {
|
|
152
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
153
|
+
throw new Error(`${label} is required.`);
|
|
154
|
+
}
|
|
155
|
+
return value.trim();
|
|
156
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function withYuiRunEnvironment(launch, yuiHome, role, run, nativeSessionId, nativeSessionRoot) {
|
|
2
|
+
return {
|
|
3
|
+
...launch,
|
|
4
|
+
env: {
|
|
5
|
+
...launch.env,
|
|
6
|
+
YUI_HOME: yuiHome,
|
|
7
|
+
YUI_TASK_ID: run.taskId,
|
|
8
|
+
YUI_ROLE: role.name,
|
|
9
|
+
YUI_AGENT_ID: role.activeAgentId ?? role.agent ?? "",
|
|
10
|
+
YUI_RUN_ID: run.id,
|
|
11
|
+
YUI_WORKSPACE: role.workspace,
|
|
12
|
+
...(nativeSessionRoot === undefined ? {} : { YUI_NATIVE_SESSION_ROOT: nativeSessionRoot }),
|
|
13
|
+
...(nativeSessionId === undefined ? {} : { YUI_NATIVE_SESSION_ID: nativeSessionId })
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|