@zq-silk/yui 0.13.2 → 0.13.4
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 +31 -25
- package/README.md +24 -20
- package/dist/cli/commandCatalog.js +3 -3
- package/dist/commands/taskCommands.js +32 -9
- package/dist/controller/controller.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +12 -1
- package/dist/controller/runtimeLaunchCoordinator.js +4 -0
- package/dist/executor/agentAdapter.js +62 -8
- package/dist/executor/executorRegistry.js +4 -0
- package/dist/executor/fileRoleLaunchPlanner.js +71 -27
- package/dist/runtime/agentHost.js +226 -14
- package/dist/runtime/builtinAgentDrivers.js +9 -0
- package/dist/runtime/codexAppServerRuntime.js +63 -12
- package/dist/runtime/launchBroker.js +51 -1
- package/dist/runtime/runtimeBinding.js +10 -1
- package/dist/runtime/structuredProviderHost.js +214 -54
- package/dist/runtime/tmuxAdapters.js +13 -2
- package/dist/scheduler/activeRoleRunDelivery.js +24 -3
- package/dist/scheduler/leaderWakeupProcessor.js +20 -0
- package/i18n/README.zh-CN.md +7 -7
- package/package.json +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -49,7 +49,8 @@ Session.
|
|
|
49
49
|
- `WorkerProfile` is a versioned, provider-neutral behavior template containing
|
|
50
50
|
instructions, Skills, a read/write behavior intent, and optional model/effort hints.
|
|
51
51
|
- `TaskRole` is a mutable Worker instance inside one Task. Applying a Profile
|
|
52
|
-
copies its portable behavior
|
|
52
|
+
copies its portable behavior and the optional model/effort selections into
|
|
53
|
+
the active Agent binding. Its versioned desired launch configuration is
|
|
53
54
|
next-launch-only. The Role may bind multiple Agents; every binding retains
|
|
54
55
|
independent runtime configuration.
|
|
55
56
|
- `AgentRun` records one managed dispatch and an immutable effective snapshot:
|
|
@@ -240,33 +241,38 @@ context.
|
|
|
240
241
|
|
|
241
242
|
## Runtime ownership
|
|
242
243
|
|
|
243
|
-
|
|
244
|
+
Provider conversations remain user conversations. Yui adds the matching Role
|
|
245
|
+
Skill and Session Manifest pointer, then uses provider-native requests for
|
|
246
|
+
durable Task delivery; it does not own or mirror the full transcript. The
|
|
244
247
|
Controller owns mailbox delivery, wakeups, Role liveness, recovery decisions,
|
|
245
|
-
and
|
|
246
|
-
|
|
247
|
-
never terminal bytes: both Controller and human takeover input become fenced
|
|
248
|
-
Provider-native Turn requests.
|
|
248
|
+
and exact receipts. tmux keeps Yui's client attachment observable where the
|
|
249
|
+
provider path needs one.
|
|
249
250
|
|
|
250
251
|
Run, Conversation, Activation, and Turn identities are independent. A
|
|
251
|
-
Conversation can span Runs and
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
252
|
+
Conversation can span Runs and client attachments; one Activation identifies
|
|
253
|
+
Yui's current attachment, not exclusive ownership of the Provider thread. One
|
|
254
|
+
Turn identifies one provider-native execution. Yui's authority epoch fences
|
|
255
|
+
only Yui's own submissions and retries.
|
|
256
|
+
|
|
257
|
+
Codex Task threads can be opened and used directly in Desktop as ordinary
|
|
258
|
+
sessions; no Yui takeover is required. If a direct user Turn is active, Yui
|
|
259
|
+
keeps its pending Run/message and retries after that Turn settles. The explicit
|
|
260
|
+
tmux takeover gateway remains for providers whose managed conversation is
|
|
261
|
+
attached to an independent process. Global interactive entry remains a native
|
|
262
|
+
session-lifecycle operation outside the Task delivery contract.
|
|
263
|
+
|
|
264
|
+
Codex uses `app-server proxy` to create or resume a normal thread on the shared
|
|
265
|
+
App Server daemon. Role model, effort, permission, workspace, and shell settings are passed at
|
|
266
|
+
`thread/start`/`thread/resume`, while the ordinary Task message points to the
|
|
267
|
+
Session Manifest and matching Role Skill. Yui does not write either to global
|
|
268
|
+
Codex config and may only ensure that the daemon is running; it never restarts
|
|
269
|
+
or stops it for a thread error. A Codex native config profile is rejected for
|
|
270
|
+
Managed Codex because the protocol cannot isolate it to one thread; a Yui Agent
|
|
271
|
+
Profile supplies the per-Role behavior, Skills, model, and effort instead.
|
|
272
|
+
Claude uses a persistent stream-json
|
|
273
|
+
transport with exact user-message replay acknowledgement. In both cases, Yui
|
|
274
|
+
records Turn intent before writing, accepts only exact Provider evidence, and
|
|
275
|
+
maps an uncertain write to `delivery-unknown` without automatic resubmission.
|
|
270
276
|
|
|
271
277
|
Role desired revisions and Run/Session effective snapshots keep configuration
|
|
272
278
|
history explicit. Resume compares the complete effective snapshot and
|
package/README.md
CHANGED
|
@@ -252,7 +252,7 @@ Yui provides four reusable Worker Profile definitions through
|
|
|
252
252
|
worker explorer implementer reviewer
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
Profiles are versioned, provider-neutral Worker behavior templates. They hold portable prompt instructions, Skills, access expectations, and optional model and effort
|
|
255
|
+
Profiles are versioned, provider-neutral Worker behavior templates. They hold portable prompt instructions, Skills, access expectations, and optional model and effort selections, but do not bind an Agent or own a Session or workspace. A Task Role is the Task-bound Worker instance: applying a Yui Agent Profile copies the behavior plus model/effort into that Role's active Agent binding; explicit Role options may override the copied values. This is separate from a Codex native config profile selected with `--profile`.
|
|
256
256
|
|
|
257
257
|
## Quick start
|
|
258
258
|
|
|
@@ -871,15 +871,20 @@ Task lifecycle completion/selection only suggests valid source states: Draft for
|
|
|
871
871
|
## Sessions and tmux
|
|
872
872
|
|
|
873
873
|
Managed Task Agents use the [hybrid Provider runtime](docs/provider-runtime.md).
|
|
874
|
-
|
|
875
|
-
and
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
Codex uses
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
874
|
+
Provider conversations remain ordinary user conversations. Yui adds the Role
|
|
875
|
+
Skill and Session Manifest pointer, then sends Task work through provider-native
|
|
876
|
+
structured requests. Managed prompts are never delivered as terminal bytes.
|
|
877
|
+
|
|
878
|
+
Codex uses `app-server proxy` to create or resume a normal thread on the shared
|
|
879
|
+
App Server daemon. The thread is visible and directly usable in Desktop; Yui
|
|
880
|
+
does not require takeover or install global Hook/config state. It may
|
|
881
|
+
idempotently start an absent daemon, but never restarts or stops it for a thread
|
|
882
|
+
error. When a user Turn is active, Yui keeps pending Task work and retries
|
|
883
|
+
later. If Yui's proxy disconnects, the Host reattaches without ending the
|
|
884
|
+
logical Activation and reconciles the exact owned Turn from native history.
|
|
885
|
+
Claude Code keeps its independent stream-json process with exact
|
|
886
|
+
user-message replay acknowledgement. A timeout or uncertain write becomes
|
|
887
|
+
`delivery-unknown` and is never automatically retried.
|
|
883
888
|
|
|
884
889
|
Task Role observation and takeover are explicit:
|
|
885
890
|
|
|
@@ -889,11 +894,10 @@ yui task role takeover <task-id> <role>
|
|
|
889
894
|
yui task role release <task-id> <role>
|
|
890
895
|
```
|
|
891
896
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
Authority cannot transfer while a Provider Turn or input delivery is unsettled.
|
|
897
|
+
For Codex, open the Role's ordinary thread in Desktop for direct interaction;
|
|
898
|
+
these commands are not required. `view`/`takeover`/`release` remain the explicit
|
|
899
|
+
PTY gateway for providers whose managed conversation uses an independent
|
|
900
|
+
process.
|
|
897
901
|
|
|
898
902
|
Global Operator and global Role sessions remain native interactive CLIs:
|
|
899
903
|
|
|
@@ -941,8 +945,8 @@ thread identity from App Server responses. Managed Task Runs use structured
|
|
|
941
945
|
Provider observations for both CLIs. Global interactive Codex sessions may
|
|
942
946
|
still use its `notify` callback for conversation presentation.
|
|
943
947
|
|
|
944
|
-
Automated lifecycle and delivery decisions use structured
|
|
945
|
-
persisted identities, usage snapshots, tmux process state, receipts, and pane
|
|
948
|
+
Automated lifecycle and delivery decisions use structured Provider events or
|
|
949
|
+
supported Hook payloads, persisted identities, usage snapshots, tmux process state, receipts, and pane
|
|
946
950
|
fences. Yui never
|
|
947
951
|
parses prompt glyphs, progress text, trust dialogs, or other Agent terminal
|
|
948
952
|
output to infer readiness or success. `captureRole()` remains an explicit
|
|
@@ -956,9 +960,9 @@ not. A live tmux pane proves only that the host exists. Runtime activity and
|
|
|
956
960
|
durable workflow progress use independent clocks, so token/tool/resource
|
|
957
961
|
movement cannot conceal a workflow that is not advancing.
|
|
958
962
|
|
|
959
|
-
Stable Role context
|
|
963
|
+
Stable Role context never creates a separate bootstrap Turn. Task execution Runs use the generic Leader or Worker Skill, while review Runs use the generic Reviewer Skill based on durable Run purpose rather than a configured Role name. The provider either carries the Skill through a safe additive native context channel or points to it from the ordinary Task delivery. These Yui-owned Role Skills define portable orchestration only. Project Skills remain ordinary versioned files in the Project and are discovered, selected, and loaded by the Agent through its native project mechanism; Yui does not scan, parse, copy, or inject them.
|
|
960
964
|
|
|
961
|
-
|
|
965
|
+
Managed Codex keeps the user's native developer instructions unchanged. The ordinary Task message includes a compact absolute Session Manifest pointer, and the manifest identifies the matching Yui-owned Role Skill for Codex to read on demand. Model, effort, permission, workspace, and shell settings selected for the Role are supplied as thread-scoped `thread/start` or `thread/resume` configuration through the shared App Server daemon. A Codex native config profile cannot be isolated to one shared-daemon thread, so Managed Codex rejects that setting and directs callers to a Yui Agent Profile; ordinary non-Yui and interactive Codex sessions remain unaffected. Unrelated Codex threads keep their existing user, profile, project, and system configuration and the underlying config file is never mutated. App Server notifications are the managed thread's lifecycle authority; Yui installs no managed Codex Hook and does not claim `notify`. Interactive Codex Sessions may still use Yui's structured `notify` callback, and Doctor reports any effective configuration conflict. `skills.config` is not misused because it only enables or disables already-discovered Skills. Claude receives the same Yui-owned Role Skill content from a private `0600` managed context file rather than a large or sensitive argv value; retries and resumes reuse the purpose-specific Role path. Non-Operator global Roles stay neutral and receive no Task orchestration Skill. Operator therefore opens at an empty native composer, so the user's text remains its first user message. Leader wakeups and Worker or Reviewer Run assignments remain real mailbox-delivered work messages.
|
|
962
966
|
|
|
963
967
|
## Controller and failure handling
|
|
964
968
|
|
|
@@ -994,7 +998,7 @@ Read-only commands and `upgrade --dry-run` do not start a Controller. `update`
|
|
|
994
998
|
also replaces an already-running Controller only after the new binary passes its
|
|
995
999
|
health checks.
|
|
996
1000
|
|
|
997
|
-
Its recovery reconciliation runs every 120 seconds by default. Normal durable state changes enqueue a Task, Role, or Operator key and return immediately; keys received in the same fixed 100 ms window trigger one non-overlapping targeted pass. Operator presentation has an independent lane, so a blocked Task workspace operation cannot delay a user question. Periodic Git/worktree work is limited to Tasks with durable Task-mailbox work, while active Role liveness uses one tmux inventory. Agent Driver Hooks
|
|
1001
|
+
Its recovery reconciliation runs every 120 seconds by default. Normal durable state changes enqueue a Task, Role, or Operator key and return immediately; keys received in the same fixed 100 ms window trigger one non-overlapping targeted pass. Operator presentation has an independent lane, so a blocked Task workspace operation cannot delay a user question. Periodic Git/worktree work is limited to Tasks with durable Task-mailbox work, while active Role liveness uses one tmux inventory. Structured Agent Driver observations, whether received from native provider events or supported Hooks, are exact-fenced before they reach the durable runtime inbox. A terminal Turn observation gives a legal yield/input/completion two seconds to win before a forgotten Run fails its workflow contract. Durable mailboxes freeze the current batch while new signals merge into the next batch. Task-orchestration failures retain the exact Controller-owned processing batch for two bounded fast retries and later periodic recovery; a successful retry completes that batch before newer pending work is claimed. Recommended InputRequest and pending Turn deadlines share one nearest-deadline selector and therefore do not wait for the recovery interval. Explicit `task reconcile` still requests an immediate recovery pass. The retained loop is:
|
|
998
1002
|
|
|
999
1003
|
1. dispatch pending Leader wakes whose Task workspaces are already ready;
|
|
1000
1004
|
2. prepare active Project Task main worktrees with durable orchestration work;
|
|
@@ -582,17 +582,17 @@ const taskChildren = [
|
|
|
582
582
|
},
|
|
583
583
|
{
|
|
584
584
|
name: "view",
|
|
585
|
-
summary: "Attach read-only to
|
|
585
|
+
summary: "Attach read-only to an independent Provider presentation surface.",
|
|
586
586
|
usage: "yui task role view <task> <role>"
|
|
587
587
|
},
|
|
588
588
|
{
|
|
589
589
|
name: "takeover",
|
|
590
|
-
summary: "
|
|
590
|
+
summary: "Enter the PTY input gateway for an independent Provider process.",
|
|
591
591
|
usage: "yui task role takeover <task> <role>"
|
|
592
592
|
},
|
|
593
593
|
{
|
|
594
594
|
name: "release",
|
|
595
|
-
summary: "
|
|
595
|
+
summary: "Release an independent Provider PTY input gateway.",
|
|
596
596
|
usage: "yui task role release <task> <role>"
|
|
597
597
|
}
|
|
598
598
|
]
|
|
@@ -20,7 +20,7 @@ import { renderRoleDetails } from "../output/rolePresentation.js";
|
|
|
20
20
|
import { createTaskMessage, taskMessageAuthorLabel } from "../message/message.js";
|
|
21
21
|
import { cancelInputRequest } from "../input/inputRequest.js";
|
|
22
22
|
import { recoverExactAgentRun, retireExactActiveAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
|
|
23
|
-
import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole, updateRoleStatus } from "../role/role.js";
|
|
23
|
+
import { activeRoleAgentBinding, copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole, updateRoleStatus } from "../role/role.js";
|
|
24
24
|
import { agentRunDeliveryReceiptId, createAgentRun, withAgentRunContextSnapshot } from "../run/agentRun.js";
|
|
25
25
|
import { projectRunRecovery, readRunRecoveryFacts } from "../run/recoveryProjection.js";
|
|
26
26
|
import { matchYieldReceipt } from "../run/yieldReceipt.js";
|
|
@@ -1441,7 +1441,7 @@ function addTaskRole(args, store, options) {
|
|
|
1441
1441
|
let created = createTaskRole(tx, task, roleName, agentId, now);
|
|
1442
1442
|
const profileId = parsed.one("--profile");
|
|
1443
1443
|
if (profileId !== undefined) {
|
|
1444
|
-
created =
|
|
1444
|
+
created = applyWorkerAgentProfile(created, requireAgentProfile(tx, profileId), now);
|
|
1445
1445
|
}
|
|
1446
1446
|
const profile = roleProfilePatch(parsed);
|
|
1447
1447
|
if (Object.keys(profile).length > 0)
|
|
@@ -1558,20 +1558,20 @@ function updateTaskRole(args, store, options) {
|
|
|
1558
1558
|
roleName: role.name
|
|
1559
1559
|
}, "desired launch configuration update");
|
|
1560
1560
|
}
|
|
1561
|
-
|
|
1561
|
+
const profileId = parsed.one("--profile");
|
|
1562
|
+
const withProfile = profileId === undefined
|
|
1563
|
+
? role
|
|
1564
|
+
: applyWorkerAgentProfile(role, requireAgentProfile(tx, profileId), now);
|
|
1565
|
+
let bindings = withProfile.agentBindings;
|
|
1562
1566
|
if (changesAgentConfig) {
|
|
1563
|
-
const agentId = parsed.one("--agent")?.trim() ||
|
|
1567
|
+
const agentId = parsed.one("--agent")?.trim() || withProfile.activeAgentId;
|
|
1564
1568
|
const agent = requireAgent(tx, agentId);
|
|
1565
1569
|
const binding = bindings[agentId]
|
|
1566
1570
|
?? createRoleAgentBinding({ id: agent.id, adapterId: agent.adapterId });
|
|
1567
1571
|
bindings = { ...bindings, [agentId]: patchRoleAgentBinding(binding, parsed) };
|
|
1568
1572
|
}
|
|
1569
|
-
const profileId = parsed.one("--profile");
|
|
1570
|
-
const withProfile = profileId === undefined
|
|
1571
|
-
? role
|
|
1572
|
-
: updateRole(role, workerProfileRolePatch(requireAgentProfile(tx, profileId)), now);
|
|
1573
1573
|
const next = updateRole(withProfile, {
|
|
1574
|
-
...(bindings ===
|
|
1574
|
+
...(bindings === withProfile.agentBindings ? {} : { agentBindings: bindings }),
|
|
1575
1575
|
...roleProfilePatch(parsed)
|
|
1576
1576
|
}, now);
|
|
1577
1577
|
if (changesLaunchContext) {
|
|
@@ -6463,6 +6463,29 @@ function workerProfileRolePatch(profile) {
|
|
|
6463
6463
|
: undefined
|
|
6464
6464
|
};
|
|
6465
6465
|
}
|
|
6466
|
+
function applyWorkerAgentProfile(role, profile, now) {
|
|
6467
|
+
const binding = activeRoleAgentBinding(role);
|
|
6468
|
+
const config = structuredClone(binding.config);
|
|
6469
|
+
if (profile.model === undefined)
|
|
6470
|
+
delete config.model;
|
|
6471
|
+
else
|
|
6472
|
+
config.model = profile.model;
|
|
6473
|
+
if (profile.effort === undefined)
|
|
6474
|
+
delete config.effort;
|
|
6475
|
+
else
|
|
6476
|
+
config.effort = profile.effort;
|
|
6477
|
+
const profiledBinding = createRoleAgentBinding({
|
|
6478
|
+
id: binding.agentId,
|
|
6479
|
+
adapterId: binding.adapterId
|
|
6480
|
+
}, config);
|
|
6481
|
+
return updateRole(role, {
|
|
6482
|
+
...workerProfileRolePatch(profile),
|
|
6483
|
+
agentBindings: {
|
|
6484
|
+
...role.agentBindings,
|
|
6485
|
+
[binding.agentId]: profiledBinding
|
|
6486
|
+
}
|
|
6487
|
+
}, now);
|
|
6488
|
+
}
|
|
6466
6489
|
function appendMessage(store, taskId, body, kind, author, now, context = {}) {
|
|
6467
6490
|
const message = createTaskMessage(store.nextMessageId(taskId), taskId, body, kind, author, now, context);
|
|
6468
6491
|
store.saveMessage(taskId, message);
|
|
@@ -1515,7 +1515,8 @@ export class FileTaskController {
|
|
|
1515
1515
|
else if (wakeup.reason === "writer-attached") {
|
|
1516
1516
|
writerBlocked.add(key);
|
|
1517
1517
|
}
|
|
1518
|
-
else if (wakeup.reason === "not-ready"
|
|
1518
|
+
else if (wakeup.reason === "not-ready"
|
|
1519
|
+
|| (wakeup.reason === "busy" && wakeup.runId !== undefined)) {
|
|
1519
1520
|
retry.set(key, { identity: wakeup.runId ?? key });
|
|
1520
1521
|
}
|
|
1521
1522
|
else if (wakeup.status === "dispatched")
|
|
@@ -3111,6 +3111,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
3111
3111
|
current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.turnId, now);
|
|
3112
3112
|
store.saveGlobalRoleSessionSet(current);
|
|
3113
3113
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
3114
|
+
if (input.roleName === SYSTEM_OPERATOR_ROLE
|
|
3115
|
+
&& input.adapterId === "codex"
|
|
3116
|
+
&& completedStatus === "ready") {
|
|
3117
|
+
const operatorMailbox = store.getWorkMailbox({ kind: "operator" });
|
|
3118
|
+
// A completed foreground Codex Turn leaves its native TUI attached to
|
|
3119
|
+
// the thread. Stop only that idle Role runtime so Desktop can become
|
|
3120
|
+
// the writer; the durable nativeSessionId remains available to resume.
|
|
3121
|
+
if (operatorMailbox === null || !mailboxHasWork(operatorMailbox)) {
|
|
3122
|
+
enqueueWork(store, runtimeLifecycleTarget(owner), RUNTIME_CLEANUP_REQUIRED_REASON, now);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3114
3125
|
return current.sessions[input.agentId];
|
|
3115
3126
|
});
|
|
3116
3127
|
}
|
|
@@ -3588,7 +3599,7 @@ function nativeTransitionExisting(store, owner, existing, nativeSessionId, launc
|
|
|
3588
3599
|
throw new Error(conflictMessage);
|
|
3589
3600
|
}
|
|
3590
3601
|
function globalCompletionNativeSessionId(existing, input) {
|
|
3591
|
-
// Codex
|
|
3602
|
+
// Codex structured event sources can expose different native IDs for the
|
|
3592
3603
|
// same launch. The launch generation remains the authoritative fence.
|
|
3593
3604
|
return input.adapterId === "codex"
|
|
3594
3605
|
&& input.launchId !== undefined
|
|
@@ -446,6 +446,10 @@ function requireMatchingRuntimeBinding(raw, request, launchId) {
|
|
|
446
446
|
&& binding.initialTurnDeliveryUnknownRunId !== request.runId) {
|
|
447
447
|
throw new RuntimeBindingContractError(`Session host returned a delivery-unknown initial structured Turn for another Run: ${request.owner.roleName}.`);
|
|
448
448
|
}
|
|
449
|
+
if (binding.initialTurnBusyRunId !== undefined
|
|
450
|
+
&& binding.initialTurnBusyRunId !== request.runId) {
|
|
451
|
+
throw new RuntimeBindingContractError(`Session host returned a busy initial structured Turn for another Run: ${request.owner.roleName}.`);
|
|
452
|
+
}
|
|
449
453
|
if (binding.initialTurnRejectedRunId !== undefined
|
|
450
454
|
&& binding.initialTurnRejectedRunId !== request.runId) {
|
|
451
455
|
throw new RuntimeBindingContractError(`Session host returned a rejected initial structured Turn for another Run: ${request.owner.roleName}.`);
|
|
@@ -10,7 +10,7 @@ const SANDBOXES = ["read-only", "workspace-write", "danger-full-access"];
|
|
|
10
10
|
const APPROVALS = ["untrusted", "on-request", "never"];
|
|
11
11
|
const PROBE_TIMEOUT_MS = 2_000;
|
|
12
12
|
const PROBE_MAX_BYTES = 1024 * 1024;
|
|
13
|
-
const CODEX_TESTED_THROUGH_VERSION = "0.
|
|
13
|
+
const CODEX_TESTED_THROUGH_VERSION = "0.150.1";
|
|
14
14
|
class BaseAdapter {
|
|
15
15
|
launchContextArgs(_input) {
|
|
16
16
|
return [];
|
|
@@ -51,7 +51,7 @@ class BaseAdapter {
|
|
|
51
51
|
class CodexAdapter extends BaseAdapter {
|
|
52
52
|
id = "codex";
|
|
53
53
|
label = "Codex";
|
|
54
|
-
supportedVersion = "0.
|
|
54
|
+
supportedVersion = "0.150.1";
|
|
55
55
|
capabilities = {
|
|
56
56
|
recover: true,
|
|
57
57
|
interrupt: true,
|
|
@@ -149,21 +149,31 @@ class CodexAdapter extends BaseAdapter {
|
|
|
149
149
|
return { ...launch, argv: [...launch.argv, "resume", nativeId(input.nativeSessionId)] };
|
|
150
150
|
}
|
|
151
151
|
compileManagedControl(input, _mode, _nativeSessionId) {
|
|
152
|
+
const config = this.canonicalizeConfig(input.config);
|
|
153
|
+
if (config.profile !== undefined) {
|
|
154
|
+
throw new Error("Managed Codex does not accept a Codex config profile because it cannot be scoped to one "
|
|
155
|
+
+ "shared-daemon thread. Use a Yui Agent Profile for skills, model, and effort.");
|
|
156
|
+
}
|
|
152
157
|
const launch = this.compileNew(input);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
// A managed Codex thread is an ordinary user thread. Its Yui guidance is
|
|
159
|
+
// part of the durable Task message, not a developer_instructions override
|
|
160
|
+
// that would shadow the user's native project/developer configuration.
|
|
161
|
+
const argv = withoutCodexConfigOverride(launch.argv, "developer_instructions");
|
|
162
|
+
if (config.model !== undefined) {
|
|
163
|
+
const modelFlag = argv.findIndex((value, index) => (value === "--model" && argv[index + 1] === config.model));
|
|
156
164
|
if (modelFlag < 0)
|
|
157
165
|
throw new Error("Managed Codex launch lost its selected model.");
|
|
158
166
|
argv.splice(modelFlag, 2);
|
|
159
167
|
// App Server thread/start reads the model from resolved configuration;
|
|
160
168
|
// the interactive --model shortcut is not inherited by new threads.
|
|
161
|
-
argv.push("--config", `model=${tomlString(
|
|
169
|
+
argv.push("--config", `model=${tomlString(config.model)}`);
|
|
162
170
|
}
|
|
163
171
|
return {
|
|
164
172
|
...launch,
|
|
165
|
-
argv: [...argv, "app-server", "
|
|
166
|
-
transport: "codex-app-server-
|
|
173
|
+
argv: [...argv, "app-server", "proxy"],
|
|
174
|
+
transport: "codex-app-server-proxy",
|
|
175
|
+
codexThread: codexThreadOptions(input, config),
|
|
176
|
+
codexDaemonStartArgs: [...input.agent.baseArgs, "app-server", "daemon", "start"]
|
|
167
177
|
};
|
|
168
178
|
}
|
|
169
179
|
}
|
|
@@ -314,6 +324,50 @@ function tomlString(value) {
|
|
|
314
324
|
throw new Error("Agent launch context cannot contain NUL bytes.");
|
|
315
325
|
return JSON.stringify(value);
|
|
316
326
|
}
|
|
327
|
+
function codexThreadOptions(input, config) {
|
|
328
|
+
const roots = [...new Set([
|
|
329
|
+
resolve(input.workspace),
|
|
330
|
+
...(config.additionalDirectories ?? []).map((path) => resolve(path))
|
|
331
|
+
])];
|
|
332
|
+
const threadConfig = {
|
|
333
|
+
// Yui has already scoped and authorized this exact managed workspace.
|
|
334
|
+
// Keep the trust override on this thread instead of mutating global config.
|
|
335
|
+
projects: { [resolve(input.workspace)]: { trust_level: "trusted" } },
|
|
336
|
+
...(config.effort === undefined ? {} : { model_reasoning_effort: config.effort }),
|
|
337
|
+
...(config.search === true ? { web_search: "live" } : {})
|
|
338
|
+
};
|
|
339
|
+
const permission = config.permission.strategy === "bypass"
|
|
340
|
+
? { approvalPolicy: "never", sandbox: "danger-full-access" }
|
|
341
|
+
: config.permission.strategy === "configured"
|
|
342
|
+
? {
|
|
343
|
+
...(config.permission.approval === undefined
|
|
344
|
+
? {}
|
|
345
|
+
: { approvalPolicy: config.permission.approval }),
|
|
346
|
+
...(config.permission.sandbox === undefined
|
|
347
|
+
? {}
|
|
348
|
+
: { sandbox: config.permission.sandbox })
|
|
349
|
+
}
|
|
350
|
+
: {};
|
|
351
|
+
return {
|
|
352
|
+
...(config.model === undefined ? {} : { model: config.model }),
|
|
353
|
+
...permission,
|
|
354
|
+
runtimeWorkspaceRoots: roots,
|
|
355
|
+
...(Object.keys(threadConfig).length === 0 ? {} : { config: threadConfig })
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function withoutCodexConfigOverride(argv, key) {
|
|
359
|
+
const filtered = [];
|
|
360
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
361
|
+
const argument = argv[index];
|
|
362
|
+
if ((argument === "--config" || argument === "-c")
|
|
363
|
+
&& argv[index + 1]?.startsWith(`${key}=`) === true) {
|
|
364
|
+
index += 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
filtered.push(argument);
|
|
368
|
+
}
|
|
369
|
+
return filtered;
|
|
370
|
+
}
|
|
317
371
|
function sessionTitle(value) {
|
|
318
372
|
if (typeof value !== "string" || value.includes("\0")) {
|
|
319
373
|
throw new Error("Agent session title is invalid.");
|
|
@@ -126,6 +126,10 @@ export class ExecutorRegistry {
|
|
|
126
126
|
&& binding?.initialTurnDeliveryUnknownRunId === input.runId
|
|
127
127
|
? { turnDeliveryUnknownDuringLaunch: true }
|
|
128
128
|
: {}),
|
|
129
|
+
...(input.runId !== undefined
|
|
130
|
+
&& binding?.initialTurnBusyRunId === input.runId
|
|
131
|
+
? { turnBusyDuringLaunch: true }
|
|
132
|
+
: {}),
|
|
129
133
|
...(input.runId !== undefined
|
|
130
134
|
&& binding?.initialTurnRejectedRunId === input.runId
|
|
131
135
|
? { turnRejectedDuringLaunch: true }
|