@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
package/ARCHITECTURE.md
CHANGED
|
@@ -201,34 +201,33 @@ context.
|
|
|
201
201
|
|
|
202
202
|
## Runtime ownership
|
|
203
203
|
|
|
204
|
-
tmux owns Agent
|
|
205
|
-
mailbox delivery, wakeups, Role liveness,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
is
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
the
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
bindings.
|
|
204
|
+
tmux owns the persistent Agent Host lifetime and observable output. The
|
|
205
|
+
Controller owns mailbox delivery, wakeups, Role liveness, recovery decisions,
|
|
206
|
+
and a durable single-writer authority epoch. The Agent Host owns one structured
|
|
207
|
+
Provider child process and mirrors its output to the pane. Managed prompts are
|
|
208
|
+
never terminal bytes: both Controller and human takeover input become fenced
|
|
209
|
+
Provider-native Turn requests.
|
|
210
|
+
|
|
211
|
+
Run, Conversation, Activation, and Turn identities are independent. A
|
|
212
|
+
Conversation can span Runs and Provider processes; one Activation identifies
|
|
213
|
+
one live process; one Turn identifies one pre-recorded input attempt. A live
|
|
214
|
+
Activation is retained when a later Run reuses the Conversation. Process exit
|
|
215
|
+
ends that Activation, and a resumed process receives a new Activation and a
|
|
216
|
+
higher authority epoch.
|
|
217
|
+
|
|
218
|
+
Task observation uses `task role view`. Explicit takeover requires a live
|
|
219
|
+
managed Run, atomically transfers authority to a human holder, synchronizes the
|
|
220
|
+
same fence to the Host, and only then exposes the PTY input gateway. Detach
|
|
221
|
+
releases authority; `task role release` is an idempotent repair path even after
|
|
222
|
+
the Run has ended. Global interactive entry remains a native session-lifecycle
|
|
223
|
+
operation outside this managed Provider contract.
|
|
224
|
+
|
|
225
|
+
Codex uses a persistent App Server JSON-RPC transport. Claude uses a persistent
|
|
226
|
+
stream-json transport with exact user-message replay acknowledgement. In both
|
|
227
|
+
cases, Yui records Turn intent before writing, accepts only exact Provider
|
|
228
|
+
evidence, and maps an uncertain write to `delivery-unknown` without automatic
|
|
229
|
+
resubmission. Conversation replacement requires exact missing evidence and no
|
|
230
|
+
unsettled input, Turn, Activation, or writer authority.
|
|
232
231
|
|
|
233
232
|
Role desired revisions and Run/Session effective snapshots keep configuration
|
|
234
233
|
history explicit. Resume compares the complete effective snapshot and
|
package/README.md
CHANGED
|
@@ -659,55 +659,46 @@ yui task complete <task-id> --summary-file delivery.txt --refresh-remote
|
|
|
659
659
|
yui task reopen <task-id>
|
|
660
660
|
```
|
|
661
661
|
|
|
662
|
-
Completed Tasks reject messages, dispatch,
|
|
662
|
+
Completed Tasks reject messages, dispatch, Provider authority changes, retry, and late yields until explicitly reopened, while retaining Task main for inspection or integration. Every isolated WorkItem worktree must be explicitly cleaned as integrated or abandoned before archive; that cleanup also removes its managed branch. Archive requires `--integrated` or `--abandon` to state the Task main outcome and is allowed only after Task main is clean. It removes managed worktrees but retains Task and WorkItem records. The Task main branch is retained as a recovery artifact instead of being silently deleted.
|
|
663
663
|
Task lifecycle completion/selection only suggests valid source states: Draft for activate, active for complete, and completed for reopen.
|
|
664
664
|
|
|
665
665
|
## Sessions and tmux
|
|
666
666
|
|
|
667
|
-
|
|
668
|
-
and
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
tmux fixes a pane's history capacity when that pane is created. Roles created
|
|
695
|
-
before this limit was configured keep their earlier capacity; Yui warns on
|
|
696
|
-
Terminal attach and in Web so the user can exit and re-enter that Role once to
|
|
697
|
-
create a 100,000-line pane while retaining the native Agent conversation.
|
|
698
|
-
|
|
699
|
-
Global interactive entry remains writable when no writer exists and
|
|
700
|
-
automatically downgrades to read-only when another writer is present; global
|
|
701
|
-
Web keeps one writer per tmux session. Task Web is always read-only. Task CLI
|
|
702
|
-
entry is read-only unless `--read-write` is requested, preventing observation
|
|
703
|
-
from changing Agent execution.
|
|
667
|
+
Managed Task Agents use the [hybrid Provider runtime](docs/provider-runtime.md).
|
|
668
|
+
The Controller and Agent Host send Provider-native structured requests; tmux
|
|
669
|
+
and PTY keep the Host alive, show output, and provide an explicit human input
|
|
670
|
+
gateway. Managed prompts are never delivered as terminal bytes.
|
|
671
|
+
|
|
672
|
+
Codex uses a persistent App Server JSON-RPC process. Claude Code uses a
|
|
673
|
+
persistent stream-json process with exact user-message replay acknowledgement.
|
|
674
|
+
Both follow the same Conversation, Activation, Turn, and authority-epoch
|
|
675
|
+
contract. A timeout or uncertain write becomes `delivery-unknown` and is never
|
|
676
|
+
automatically retried.
|
|
677
|
+
|
|
678
|
+
Task Role observation and takeover are explicit:
|
|
679
|
+
|
|
680
|
+
```sh
|
|
681
|
+
yui task role view <task-id> <role>
|
|
682
|
+
yui task role takeover <task-id> <role>
|
|
683
|
+
yui task role release <task-id> <role>
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
`view` is read-only. `takeover` transfers the durable writer authority to the
|
|
687
|
+
human before enabling the Host PTY gateway and requires a live managed Run;
|
|
688
|
+
detach returns it to the Controller. `release` remains available without a live
|
|
689
|
+
Run and idempotently repairs a stranded or partially synchronized takeover.
|
|
690
|
+
Authority cannot transfer while a Provider Turn or input delivery is unsettled.
|
|
691
|
+
|
|
692
|
+
Global Operator and global Role sessions remain native interactive CLIs:
|
|
704
693
|
|
|
705
694
|
```sh
|
|
706
695
|
yui session enter <global-role>
|
|
707
|
-
yui task enter <task-id> [role] [--read-only | --read-write]
|
|
708
|
-
yui task role enter <task-id> <role> [--read-only | --read-write]
|
|
709
696
|
```
|
|
710
697
|
|
|
698
|
+
tmux fixes a pane's history capacity when that pane is created. Existing panes
|
|
699
|
+
retain their configured capacity; managed runtime output remains observable in
|
|
700
|
+
the Agent Host pane without becoming lifecycle or acknowledgement evidence.
|
|
701
|
+
|
|
711
702
|
Each Role, including a Task-bound Worker instance, can bind multiple configured Agents, has one active Agent, and keeps
|
|
712
703
|
a separate native session per Agent binding. Operator narrows this to at most
|
|
713
704
|
one Agent per adapter—for example, one Codex and one Claude—so its bindings are
|
|
@@ -729,12 +720,10 @@ snapshot instead of applying desired drift as a hot change.
|
|
|
729
720
|
|
|
730
721
|
Use `yui config role unbind <global-role> <agent-id>` or `yui task role unbind <task-id> <role> <agent-id>` to retire a dormant binding. The active binding and any non-stopped native session are rejected; a stopped session record is removed atomically with the binding.
|
|
731
722
|
|
|
732
|
-
Claude session IDs are preallocated at launch.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
Agent Driver Hook ingress for both CLIs. Global interactive Codex sessions may
|
|
737
|
-
still use its structured `notify` callback for conversation presentation.
|
|
723
|
+
Claude session IDs are preallocated at launch. Codex discovers its native
|
|
724
|
+
thread identity from App Server responses. Managed Task Runs use structured
|
|
725
|
+
Provider observations for both CLIs. Global interactive Codex sessions may
|
|
726
|
+
still use its `notify` callback for conversation presentation.
|
|
738
727
|
|
|
739
728
|
Automated lifecycle and delivery decisions use structured Hook payloads,
|
|
740
729
|
persisted identities, usage snapshots, tmux process state, receipts, and pane
|
|
@@ -513,7 +513,8 @@ const taskChildren = [
|
|
|
513
513
|
name: "role",
|
|
514
514
|
summary: "Manage Roles within a Task.",
|
|
515
515
|
sections: [{ id: "manage", title: "Commands", entries: [
|
|
516
|
-
"add", "list", "status", "show", "update", "remove", "bind", "unbind", "reset",
|
|
516
|
+
"add", "list", "status", "show", "update", "remove", "bind", "unbind", "reset",
|
|
517
|
+
"view", "takeover", "release"
|
|
517
518
|
] }],
|
|
518
519
|
children: [
|
|
519
520
|
{
|
|
@@ -548,10 +549,19 @@ const taskChildren = [
|
|
|
548
549
|
options: ["--reason"]
|
|
549
550
|
},
|
|
550
551
|
{
|
|
551
|
-
name: "
|
|
552
|
-
summary: "Attach to
|
|
553
|
-
usage: "yui task role
|
|
554
|
-
|
|
552
|
+
name: "view",
|
|
553
|
+
summary: "Attach read-only to a managed Provider presentation surface.",
|
|
554
|
+
usage: "yui task role view <task> <role>"
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "takeover",
|
|
558
|
+
summary: "Acquire Provider writer authority and enter the PTY input gateway.",
|
|
559
|
+
usage: "yui task role takeover <task> <role>"
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "release",
|
|
563
|
+
summary: "Return stranded human Provider authority to the Controller.",
|
|
564
|
+
usage: "yui task role release <task> <role>"
|
|
555
565
|
}
|
|
556
566
|
]
|
|
557
567
|
},
|
|
@@ -675,7 +685,7 @@ const taskChildren = [
|
|
|
675
685
|
{
|
|
676
686
|
name: "run",
|
|
677
687
|
summary: "Inspect and control Task Role Agent Runs.",
|
|
678
|
-
sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "settle", "recover", "yield", "checkpoint"] }],
|
|
688
|
+
sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "settle", "recover", "yield", "context", "checkpoint"] }],
|
|
679
689
|
children: [
|
|
680
690
|
{ name: "list", summary: "List Runs for a work item.", usage: "yui task run list <task>/<work>" },
|
|
681
691
|
{
|
|
@@ -706,6 +716,28 @@ const taskChildren = [
|
|
|
706
716
|
options: ["--summary", "--summary-file"],
|
|
707
717
|
fileOptions: ["--summary-file"]
|
|
708
718
|
},
|
|
719
|
+
{
|
|
720
|
+
name: "context",
|
|
721
|
+
summary: "Load the exact authorized Run context.",
|
|
722
|
+
usage: "yui task run context <task>/<run> [--json]",
|
|
723
|
+
executable: true,
|
|
724
|
+
hidden: true,
|
|
725
|
+
sections: [{ id: "load", title: "Commands", entries: ["expand", "delta"] }],
|
|
726
|
+
children: [
|
|
727
|
+
{
|
|
728
|
+
name: "expand",
|
|
729
|
+
summary: "Expand one authorized Run context reference.",
|
|
730
|
+
usage: "yui task run context expand <task>/<run> <ref-id> [--mode full]",
|
|
731
|
+
options: ["--mode"]
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
name: "delta",
|
|
735
|
+
summary: "Load authorized Run context changes after a cursor.",
|
|
736
|
+
usage: "yui task run context delta <task>/<run> --after <cursor>",
|
|
737
|
+
options: ["--after"]
|
|
738
|
+
}
|
|
739
|
+
]
|
|
740
|
+
},
|
|
709
741
|
{
|
|
710
742
|
name: "checkpoint",
|
|
711
743
|
summary: "Record durable progress for a long-running Agent Run.",
|
|
@@ -940,12 +972,6 @@ const taskChildren = [
|
|
|
940
972
|
{ name: "show", summary: "Show one ChangeSet.", usage: "yui task change-set show <task>/<change-set>" }
|
|
941
973
|
]
|
|
942
974
|
},
|
|
943
|
-
{
|
|
944
|
-
name: "enter",
|
|
945
|
-
summary: "Attach to an existing Task Role, defaulting to Leader and read-only.",
|
|
946
|
-
usage: "yui task enter <task> [role] [--read-only | --read-write]",
|
|
947
|
-
options: ["--read-only", "--read-write"]
|
|
948
|
-
}
|
|
949
975
|
];
|
|
950
976
|
export const ROOT_COMMAND = buildNode({
|
|
951
977
|
name: "yui",
|
|
@@ -1319,7 +1345,7 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1319
1345
|
summary: "Manage Tasks, WorkItems, Agent Runs, and integration.",
|
|
1320
1346
|
sections: [
|
|
1321
1347
|
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "complete", "reopen", "retire", "list", "show", "context", "next-action", "archive", "rebuild", "history", "replace", "reconcile"] },
|
|
1322
|
-
{ id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "publication", "work", "run", "review", "integration", "role", "
|
|
1348
|
+
{ id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "publication", "work", "run", "review", "integration", "role", "overlap", "change-set"] },
|
|
1323
1349
|
{ id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event", "continuation", "wake"] }
|
|
1324
1350
|
],
|
|
1325
1351
|
children: taskChildren
|
|
@@ -1359,8 +1385,17 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1359
1385
|
name: "internal",
|
|
1360
1386
|
summary: "Internal Yui callbacks.",
|
|
1361
1387
|
hidden: true,
|
|
1362
|
-
sections: [{
|
|
1388
|
+
sections: [{
|
|
1389
|
+
id: "callbacks",
|
|
1390
|
+
title: "Callbacks",
|
|
1391
|
+
entries: ["session-notify", "runtime-hook", "agent-host"]
|
|
1392
|
+
}],
|
|
1363
1393
|
children: [
|
|
1394
|
+
{
|
|
1395
|
+
name: "agent-host",
|
|
1396
|
+
summary: "Run the persistent structured Provider host.",
|
|
1397
|
+
usage: "yui internal agent-host <launch-id> <ticket>"
|
|
1398
|
+
},
|
|
1364
1399
|
{
|
|
1365
1400
|
name: "session-notify",
|
|
1366
1401
|
summary: "Record a structured native session notification.",
|
|
@@ -361,8 +361,8 @@ export const INTERACTION_POLICIES = Object.freeze([
|
|
|
361
361
|
trailingOptions: { "--reason": "value" },
|
|
362
362
|
confirmation: { action: "Reset Task Role Session", targetArgumentIndex: 4 }
|
|
363
363
|
},
|
|
364
|
-
{
|
|
365
|
-
commandPath: ["task", "role",
|
|
364
|
+
...["view", "takeover", "release"].map((command) => ({
|
|
365
|
+
commandPath: ["task", "role", command],
|
|
366
366
|
selectors: [
|
|
367
367
|
{ argumentIndex: 3, entity: "task", provider: "tasks", actionTarget: true },
|
|
368
368
|
{
|
|
@@ -372,9 +372,8 @@ export const INTERACTION_POLICIES = Object.freeze([
|
|
|
372
372
|
dependsOn: 3,
|
|
373
373
|
actionTarget: true
|
|
374
374
|
}
|
|
375
|
-
]
|
|
376
|
-
|
|
377
|
-
},
|
|
375
|
+
]
|
|
376
|
+
})),
|
|
378
377
|
{
|
|
379
378
|
commandPath: ["task", "work", "create"],
|
|
380
379
|
selectors: [
|
|
@@ -534,11 +533,6 @@ export const INTERACTION_POLICIES = Object.freeze([
|
|
|
534
533
|
? { trailingOptions: { "--reason": "value" } }
|
|
535
534
|
: {})
|
|
536
535
|
})),
|
|
537
|
-
{
|
|
538
|
-
commandPath: ["task", "enter"],
|
|
539
|
-
selectors: [{ argumentIndex: 2, entity: "task", provider: "tasks", actionTarget: true }],
|
|
540
|
-
trailingOptions: { "--read-only": "flag", "--read-write": "flag" }
|
|
541
|
-
},
|
|
542
536
|
{
|
|
543
537
|
commandPath: ["jobs", "retry"],
|
|
544
538
|
selectors: [{ argumentIndex: 2, entity: "job", provider: "jobs", actionTarget: true }]
|
|
@@ -32,7 +32,8 @@ function resolveExecutionPath(args) {
|
|
|
32
32
|
const child = findChild(node, args[index] ?? "");
|
|
33
33
|
const internalExecutable = child !== undefined && ((node === ROOT_COMMAND && child.name === "internal")
|
|
34
34
|
|| (node.path.join(" ") === "yui config completion" && child.name === "candidates")
|
|
35
|
-
|| (node.path.join(" ") === "yui task run"
|
|
35
|
+
|| (node.path.join(" ") === "yui task run"
|
|
36
|
+
&& (child.name === "checkpoint" || child.name === "context"))
|
|
36
37
|
|| (node.path.join(" ") === "yui controller"
|
|
37
38
|
&& (child.name === "identity" || child.name === "live-identity")));
|
|
38
39
|
if (child === undefined || (child.hidden && !internalExecutable)) {
|
package/dist/cli.js
CHANGED
|
@@ -36,7 +36,7 @@ import { runResourcesCommand } from "./commands/resourcesCommands.js";
|
|
|
36
36
|
import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
|
|
37
37
|
import { runProjectCommand } from "./commands/projectCommands.js";
|
|
38
38
|
import { runProfileCommand } from "./commands/profileCommands.js";
|
|
39
|
-
import { dispatchPreparedReviewRound, failPendingReviewRound,
|
|
39
|
+
import { dispatchPreparedReviewRound, failPendingReviewRound, RESUMED_PENDING_FINAL_REVIEW, TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW, TaskFinalReviewDispatchDriftError, preserveReviewRoundWorkspace, parseTaskCompletionRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, validateTaskArchiveRequest } from "./commands/taskCommands.js";
|
|
40
40
|
import { taskActor } from "./commands/taskActor.js";
|
|
41
41
|
import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
|
|
42
42
|
import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
|
|
@@ -61,9 +61,9 @@ import { runSessionNotifyCommand } from "./controller/sessionNotify.js";
|
|
|
61
61
|
import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
|
|
62
62
|
import { runRuntimeObservationHookCommand } from "./controller/runtimeObservationHook.js";
|
|
63
63
|
import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
|
|
64
|
-
import { agentNotFound, CliError, usageError } from "./errors/cliError.js";
|
|
64
|
+
import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
|
|
65
65
|
import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
|
|
66
|
-
import { runAgentHost } from "./runtime/agentHost.js";
|
|
66
|
+
import { AGENT_HOST_CONTROL_PROTOCOL, runAgentHost, sendAgentHostAuthorityControl } from "./runtime/agentHost.js";
|
|
67
67
|
import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
|
|
68
68
|
import { FileTaskWorkspacePreparer, ReviewRoundWorkspaceEvidenceError } from "./repository/taskWorkspacePreparer.js";
|
|
69
69
|
import { inspectStorageSchema } from "./storage/storageSchema.js";
|
|
@@ -1148,30 +1148,82 @@ export async function main() {
|
|
|
1148
1148
|
: { command: result.data, ...reviewData });
|
|
1149
1149
|
return;
|
|
1150
1150
|
}
|
|
1151
|
-
if (
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1151
|
+
if (jsonOutput) {
|
|
1152
|
+
throw usageError("Task Role view/takeover requires an interactive terminal.");
|
|
1153
|
+
}
|
|
1154
|
+
if (result.kind === "view") {
|
|
1155
|
+
if (result.output !== undefined)
|
|
1156
|
+
emit(result.output);
|
|
1157
|
+
tmux.attachRole(result.taskId, result.roleName, "read-only");
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const syncAuthority = async (authorityResult) => {
|
|
1161
|
+
let control;
|
|
1162
|
+
try {
|
|
1163
|
+
control = await sendAgentHostAuthorityControl({
|
|
1164
|
+
home,
|
|
1165
|
+
scope: "task",
|
|
1166
|
+
taskId: authorityResult.taskId,
|
|
1167
|
+
roleName: authorityResult.roleName,
|
|
1168
|
+
control: {
|
|
1169
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
1170
|
+
type: "set-authority",
|
|
1171
|
+
nativeSessionId: authorityResult.nativeSessionId,
|
|
1172
|
+
authority: authorityResult.authority
|
|
1162
1173
|
}
|
|
1163
|
-
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
catch (error) {
|
|
1177
|
+
throw runtimeError(`Agent Host authority synchronization failed at epoch ${authorityResult.authority.epoch}: `
|
|
1178
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1179
|
+
+ `Durable authority is ${authorityResult.authority.owner}-owned; retry `
|
|
1180
|
+
+ `'yui task role release ${authorityResult.taskId} ${authorityResult.roleName}' `
|
|
1181
|
+
+ "to reconcile the Host.");
|
|
1182
|
+
}
|
|
1183
|
+
if (control.outcome !== "accepted"
|
|
1184
|
+
|| control.snapshot.nativeSessionId !== authorityResult.nativeSessionId
|
|
1185
|
+
|| control.snapshot.authorityEpoch !== authorityResult.authority.epoch
|
|
1186
|
+
|| control.snapshot.authorityOwner !== authorityResult.authority.owner
|
|
1187
|
+
|| control.snapshot.authorityHolderId !== authorityResult.authority.holderId) {
|
|
1188
|
+
throw runtimeError(`Agent Host did not accept Provider authority epoch ${authorityResult.authority.epoch}: `
|
|
1189
|
+
+ (control.snapshot.detail ?? control.outcome)
|
|
1190
|
+
+ `. Durable authority is ${authorityResult.authority.owner}-owned; `
|
|
1191
|
+
+ "retry 'yui task role release "
|
|
1192
|
+
+ `${authorityResult.taskId} ${authorityResult.roleName}' to reconcile the Host.`);
|
|
1193
|
+
}
|
|
1194
|
+
return control;
|
|
1195
|
+
};
|
|
1196
|
+
await syncAuthority(result);
|
|
1197
|
+
emit(result.output);
|
|
1198
|
+
if (result.action === "release") {
|
|
1199
|
+
runtime.notifyMailboxChanged({
|
|
1200
|
+
kind: "role",
|
|
1201
|
+
taskId: result.taskId,
|
|
1202
|
+
roleName: result.roleName
|
|
1164
1203
|
});
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
process.stdout.write("Provider input is now routed through the Agent Host PTY gateway. "
|
|
1207
|
+
+ "Use tmux detach (Ctrl-b d) to return authority to the Controller.\n");
|
|
1208
|
+
try {
|
|
1209
|
+
tmux.attachRole(result.taskId, result.roleName, "read-write");
|
|
1165
1210
|
}
|
|
1166
1211
|
finally {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1212
|
+
const currentTask = store.getTask(result.taskId);
|
|
1213
|
+
// Completing or retiring the Task from inside the takeover Turn owns
|
|
1214
|
+
// Provider shutdown and clears the live binding. Do not turn that
|
|
1215
|
+
// successful terminal transition into a failing best-effort release.
|
|
1216
|
+
if (currentTask?.status === "active") {
|
|
1217
|
+
const released = runTaskCommand(["role", "release", result.taskId, result.roleName], store, { runtime, environment: process.env, yuiHome: home });
|
|
1218
|
+
if (released.kind !== "authority" || released.action !== "release") {
|
|
1219
|
+
throw runtimeError("Provider authority release returned an invalid result.");
|
|
1220
|
+
}
|
|
1221
|
+
await syncAuthority(released);
|
|
1222
|
+
emit(released.output);
|
|
1171
1223
|
runtime.notifyMailboxChanged({
|
|
1172
1224
|
kind: "role",
|
|
1173
|
-
taskId:
|
|
1174
|
-
roleName:
|
|
1225
|
+
taskId: released.taskId,
|
|
1226
|
+
roleName: released.roleName
|
|
1175
1227
|
});
|
|
1176
1228
|
}
|
|
1177
1229
|
}
|