@zq-silk/yui 0.2.0 → 0.4.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 +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { dataError, roleNotFound, taskNotFound, usageError } from "../errors/cliError.js";
|
|
2
2
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
3
|
-
import {
|
|
3
|
+
import { activeLiveRoleAgentSession } from "../executor/agentExecutor.js";
|
|
4
4
|
import { answerInputRequest, cancelInputRequest, createInputRequest } from "../input/inputRequest.js";
|
|
5
5
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { formatTimestamp } from "../output/timePresentation.js";
|
|
7
|
+
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
8
|
+
import { clearMatchingLeaderStallAttention, isRoleRunStalled, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
|
|
9
|
+
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
10
|
+
import { formatAgentRunReceiptId, resolveTaskRecordReference } from "../task/taskRecordReference.js";
|
|
9
11
|
const LEADER_ROLE = "leader";
|
|
10
12
|
export function runTaskInputCommand(args, store, options) {
|
|
11
13
|
const [command, ...rest] = args;
|
|
12
14
|
switch (command) {
|
|
13
15
|
case "request": return createRequest(rest, store, options);
|
|
14
16
|
case "list": return listRequests(rest, store);
|
|
15
|
-
case "show": return showRequest(rest, store);
|
|
17
|
+
case "show": return showRequest(rest, store, options);
|
|
16
18
|
case "answer": return answerRequest(rest, store, options);
|
|
17
19
|
case "cancel": return cancelRequest(rest, store, options);
|
|
18
20
|
default:
|
|
@@ -36,7 +38,7 @@ function createRequest(args, store, options) {
|
|
|
36
38
|
exactPositionals(parsed.positionals, 1, usage);
|
|
37
39
|
const question = requiredOption(parsed.options, "--question");
|
|
38
40
|
const choices = (parsed.multiOptions.get("--choice") ?? []).map(parseInputChoice);
|
|
39
|
-
const
|
|
41
|
+
const blockedValues = parsed.multiOptions.get("--blocks") ?? [];
|
|
40
42
|
const recommendedChoiceKey = optionalNonEmptyOption(parsed.options, "--recommend");
|
|
41
43
|
const timeoutSeconds = optionalNonEmptyOption(parsed.options, "--timeout-seconds");
|
|
42
44
|
if ((recommendedChoiceKey === undefined) !== (timeoutSeconds === undefined)) {
|
|
@@ -54,16 +56,42 @@ function createRequest(args, store, options) {
|
|
|
54
56
|
const task = requireTask(tx, parsed.positionals[0]);
|
|
55
57
|
if (task.status !== "active")
|
|
56
58
|
throw usageError(inactiveTaskMessage(task, "requesting input"));
|
|
59
|
+
const blockedRefs = blockedValues.map((value) => parseInputBlockedRef(value, task.id));
|
|
57
60
|
validateBlockedInputOwnership(tx, task.id, blockedRefs);
|
|
58
61
|
const origin = requireLeaderInputOrigin(tx, task.id, options.environment);
|
|
59
62
|
const created = createInputRequest(tx.nextInputRequestId(task.id), task.id, origin.requester, { question, choices, blockedRefs, policy }, now);
|
|
63
|
+
const wasStalled = isRoleRunStalled(tx.listEvents(task.id), origin.run.id);
|
|
60
64
|
tx.saveInputRequest(task.id, created);
|
|
61
|
-
tx
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
enqueueWork(tx, { kind: "operator" }, "input-requested", now, [
|
|
66
|
+
{ type: "input", taskId: task.id, id: created.id },
|
|
67
|
+
{ type: "run", taskId: task.id, id: origin.run.id }
|
|
68
|
+
]);
|
|
69
|
+
const terminal = terminalizeExactTaskRun(tx, {
|
|
70
|
+
taskId: task.id,
|
|
71
|
+
roleName: LEADER_ROLE,
|
|
72
|
+
agentId: origin.run.effective.agentId,
|
|
73
|
+
runId: origin.run.id,
|
|
74
|
+
receiptId: formatAgentRunReceiptId(task.id, origin.run.id),
|
|
75
|
+
...(origin.requester.nativeSessionId === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: { nativeSessionId: origin.requester.nativeSessionId }),
|
|
78
|
+
outcome: {
|
|
79
|
+
status: "yielded",
|
|
80
|
+
summary: `Waiting for input ${created.id}: ${created.question}`
|
|
81
|
+
}
|
|
82
|
+
}, now);
|
|
83
|
+
if (terminal.disposition !== "applied") {
|
|
84
|
+
throw usageError(`Task Leader Run changed while requesting input: ${origin.run.id}/${terminal.reason}.`);
|
|
85
|
+
}
|
|
86
|
+
if (wasStalled) {
|
|
87
|
+
recordTaskEvent(tx, task.id, RUN_RECOVERED_EVENT, {
|
|
88
|
+
runId: origin.run.id,
|
|
89
|
+
roleName: LEADER_ROLE,
|
|
90
|
+
progressAt: now.toISOString(),
|
|
91
|
+
kind: "input-request"
|
|
92
|
+
}, now);
|
|
66
93
|
}
|
|
94
|
+
clearMatchingLeaderStallAttention(tx, task.id, origin.run.id);
|
|
67
95
|
recordTaskEvent(tx, task.id, "input.requested", {
|
|
68
96
|
requestId: created.id,
|
|
69
97
|
requesterRunId: created.requester.runId,
|
|
@@ -71,7 +99,7 @@ function createRequest(args, store, options) {
|
|
|
71
99
|
}, now);
|
|
72
100
|
return created;
|
|
73
101
|
});
|
|
74
|
-
options
|
|
102
|
+
notifyMailbox(options, { kind: "operator" }, request.taskId);
|
|
75
103
|
return output(`Created input request ${request.id} for ${request.taskId}\n`, { request });
|
|
76
104
|
}
|
|
77
105
|
function listRequests(args, store) {
|
|
@@ -88,9 +116,10 @@ function listRequests(args, store) {
|
|
|
88
116
|
const requests = parsed.options.has("--all")
|
|
89
117
|
? all
|
|
90
118
|
: all.filter((request) => request.status === "open");
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
119
|
+
let rendered = "No input requests found.\n";
|
|
120
|
+
if (requests.length > 0) {
|
|
121
|
+
const timeZone = store.getConfig().timeZone;
|
|
122
|
+
rendered = `${renderTable(taskId === undefined ? "Input inbox" : `Input requests: ${taskId}`, [
|
|
94
123
|
{ header: "Input", minWidth: 6, maxWidth: 18 },
|
|
95
124
|
{ header: "Task", minWidth: 6, maxWidth: 18 },
|
|
96
125
|
{ header: "Status", minWidth: 6, maxWidth: 10 },
|
|
@@ -98,29 +127,29 @@ function listRequests(args, store) {
|
|
|
98
127
|
{ header: "Question", minWidth: 8, maxWidth: 72 },
|
|
99
128
|
{ header: "Created", minWidth: 10, maxWidth: 28 }
|
|
100
129
|
], requests.map((request) => [
|
|
101
|
-
request.id,
|
|
130
|
+
taskId === undefined ? `${request.taskId}/${request.id}` : request.id,
|
|
102
131
|
request.taskId,
|
|
103
132
|
request.status,
|
|
104
133
|
request.policy.kind,
|
|
105
134
|
request.question,
|
|
106
|
-
request.createdAt
|
|
135
|
+
formatTimestamp(request.createdAt, timeZone)
|
|
107
136
|
]), defaultTableWidth())}\n`;
|
|
137
|
+
}
|
|
108
138
|
return output(rendered, { requests });
|
|
109
139
|
}
|
|
110
|
-
function showRequest(args, store) {
|
|
111
|
-
const usage = "Task input show usage: yui task input show <input>
|
|
140
|
+
function showRequest(args, store, options) {
|
|
141
|
+
const usage = "Task input show usage: yui task input show (<task>/<input> | <input> --task <task>).";
|
|
112
142
|
const parsed = parseTail(args, new Set(["--task"]), usage);
|
|
113
143
|
exactPositionals(parsed.positionals, 1, usage);
|
|
114
|
-
const
|
|
115
|
-
const request = taskId
|
|
116
|
-
|
|
117
|
-
:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return output(renderInputRequest(request), { request });
|
|
144
|
+
const reference = inputRequestReference(store, parsed.positionals[0], optionalNonEmptyOption(parsed.options, "--task"), options.environment);
|
|
145
|
+
const request = store.getInputRequest(reference.taskId, reference.localId);
|
|
146
|
+
if (request === null) {
|
|
147
|
+
throw dataError(`Input request not found: ${reference.taskId}/${reference.localId}.`);
|
|
148
|
+
}
|
|
149
|
+
return output(renderInputRequest(request, store.getConfig().timeZone), { request });
|
|
121
150
|
}
|
|
122
151
|
function answerRequest(args, store, options) {
|
|
123
|
-
const usage = "Task input answer usage: yui task input answer <input>
|
|
152
|
+
const usage = "Task input answer usage: yui task input answer (<task>/<input> | <input> --task <task>) (--choice <key> | --text <text>).";
|
|
124
153
|
const parsed = parseTail(args, new Set(["--task", "--choice", "--text"]), usage);
|
|
125
154
|
exactPositionals(parsed.positionals, 1, usage);
|
|
126
155
|
const choice = optionalNonEmptyOption(parsed.options, "--choice");
|
|
@@ -128,12 +157,11 @@ function answerRequest(args, store, options) {
|
|
|
128
157
|
if ((choice === undefined) === (text === undefined)) {
|
|
129
158
|
throw usageError("Exactly one of --choice or --text is required.", usage);
|
|
130
159
|
}
|
|
131
|
-
const
|
|
132
|
-
const located =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
throw dataError(`Input request not found: ${parsed.positionals[0]}.`);
|
|
160
|
+
const reference = inputRequestReference(store, parsed.positionals[0], optionalNonEmptyOption(parsed.options, "--task"), options.environment);
|
|
161
|
+
const located = store.getInputRequest(reference.taskId, reference.localId);
|
|
162
|
+
if (located === null) {
|
|
163
|
+
throw dataError(`Input request not found: ${reference.taskId}/${reference.localId}.`);
|
|
164
|
+
}
|
|
137
165
|
const answer = choice === undefined ? { text: text } : { choiceKey: choice };
|
|
138
166
|
const now = clock(options);
|
|
139
167
|
const request = store.transaction((tx) => {
|
|
@@ -149,10 +177,10 @@ function answerRequest(args, store, options) {
|
|
|
149
177
|
requestId: answered.id,
|
|
150
178
|
answeredBy: answered.resolution.answeredBy
|
|
151
179
|
}, now);
|
|
152
|
-
|
|
180
|
+
enqueueWork(tx, { kind: "role", taskId: task.id, roleName: LEADER_ROLE }, `input-answered:${answered.id}`, now, [{ type: "input", taskId: task.id, id: answered.id }]);
|
|
153
181
|
return answered;
|
|
154
182
|
});
|
|
155
|
-
options.
|
|
183
|
+
notifyMailbox(options, { kind: "role", taskId: request.taskId, roleName: LEADER_ROLE }, request.taskId);
|
|
156
184
|
return output(`Answered input request ${request.id} for ${request.taskId}\n`, { request });
|
|
157
185
|
}
|
|
158
186
|
function cancelRequest(args, store, options) {
|
|
@@ -168,13 +196,21 @@ function cancelRequest(args, store, options) {
|
|
|
168
196
|
const current = tx.getInputRequest(task.id, parsed.positionals[1]);
|
|
169
197
|
if (current === null)
|
|
170
198
|
throw dataError(`Input request not found: ${parsed.positionals[1]}.`);
|
|
171
|
-
assertInputCancelOrigin(current, options.environment);
|
|
199
|
+
const cancelledBy = assertInputCancelOrigin(tx, current, options.environment);
|
|
172
200
|
const cancelled = cancelInputRequest(current, reason, now);
|
|
201
|
+
if (cancelled.status !== "cancelled") {
|
|
202
|
+
throw new Error(`Input request ${cancelled.id} did not enter the cancelled state.`);
|
|
203
|
+
}
|
|
173
204
|
tx.saveInputRequest(task.id, cancelled);
|
|
174
|
-
recordTaskEvent(tx, task.id, "input.cancelled", {
|
|
205
|
+
recordTaskEvent(tx, task.id, "input.cancelled", {
|
|
206
|
+
requestId: cancelled.id,
|
|
207
|
+
cancelledBy,
|
|
208
|
+
reason: cancelled.cancellation.reason
|
|
209
|
+
}, now);
|
|
210
|
+
enqueueWork(tx, { kind: "role", taskId: task.id, roleName: LEADER_ROLE }, `input-cancelled:${cancelled.id}`, now, [{ type: "input", taskId: task.id, id: cancelled.id }]);
|
|
175
211
|
return cancelled;
|
|
176
212
|
});
|
|
177
|
-
options.
|
|
213
|
+
notifyMailbox(options, { kind: "role", taskId: request.taskId, roleName: LEADER_ROLE }, request.taskId);
|
|
178
214
|
return output(`Cancelled input request ${request.id} for ${request.taskId}\n`, { request });
|
|
179
215
|
}
|
|
180
216
|
function requireLeaderInputOrigin(store, taskId, environment) {
|
|
@@ -184,24 +220,24 @@ function requireLeaderInputOrigin(store, taskId, environment) {
|
|
|
184
220
|
if (env.YUI_SESSION_SCOPE !== "task"
|
|
185
221
|
|| env.YUI_TASK_ID !== taskId
|
|
186
222
|
|| env.YUI_ROLE !== LEADER_ROLE
|
|
187
|
-
|| env.YUI_AGENT_ID !== role.activeAgentId
|
|
188
223
|
|| run === null
|
|
189
|
-
|| env.
|
|
224
|
+
|| env.YUI_AGENT_ID !== run.effective.agentId
|
|
190
225
|
|| run.status !== "active"
|
|
191
|
-
|| run.
|
|
226
|
+
|| run.pushedAt === undefined
|
|
192
227
|
|| run.workItemId !== undefined) {
|
|
193
228
|
throw usageError("Task input request requires the active Leader Run environment.");
|
|
194
229
|
}
|
|
195
230
|
const sessions = store.getTaskRoleSessionSet(taskId, LEADER_ROLE);
|
|
196
231
|
const nativeSessionId = trimmed(env.YUI_NATIVE_SESSION_ID);
|
|
197
232
|
if (nativeSessionId !== undefined
|
|
198
|
-
&& sessions?.sessions[
|
|
233
|
+
&& sessions?.sessions[run.effective.agentId]?.nativeSessionId !== nativeSessionId) {
|
|
199
234
|
throw usageError("Task input request native session does not match the active Leader session.");
|
|
200
235
|
}
|
|
201
236
|
return {
|
|
202
237
|
requester: {
|
|
238
|
+
taskId,
|
|
203
239
|
roleName: "leader",
|
|
204
|
-
agentId:
|
|
240
|
+
agentId: run.effective.agentId,
|
|
205
241
|
runId: run.id,
|
|
206
242
|
...(nativeSessionId === undefined ? {} : { nativeSessionId })
|
|
207
243
|
},
|
|
@@ -210,17 +246,62 @@ function requireLeaderInputOrigin(store, taskId, environment) {
|
|
|
210
246
|
sessions
|
|
211
247
|
};
|
|
212
248
|
}
|
|
213
|
-
function assertInputCancelOrigin(request, environment) {
|
|
249
|
+
function assertInputCancelOrigin(store, request, environment) {
|
|
214
250
|
const env = environment ?? {};
|
|
251
|
+
if (isCurrentGlobalOperator(store, env)) {
|
|
252
|
+
return "operator";
|
|
253
|
+
}
|
|
215
254
|
if (env.YUI_SESSION_SCOPE !== "task"
|
|
216
255
|
|| env.YUI_TASK_ID !== request.taskId
|
|
217
256
|
|| env.YUI_ROLE !== request.requester.roleName
|
|
218
257
|
|| env.YUI_AGENT_ID !== request.requester.agentId
|
|
219
|
-
|| env.YUI_RUN_ID !== request.requester.runId
|
|
220
258
|
|| (request.requester.nativeSessionId !== undefined
|
|
221
259
|
&& env.YUI_NATIVE_SESSION_ID !== request.requester.nativeSessionId)) {
|
|
222
260
|
throw usageError("Only the originating Leader may cancel this input request.");
|
|
223
261
|
}
|
|
262
|
+
return "leader";
|
|
263
|
+
}
|
|
264
|
+
function isCurrentGlobalOperator(store, environment) {
|
|
265
|
+
if (environment.YUI_SESSION_SCOPE !== "global"
|
|
266
|
+
|| environment.YUI_ROLE !== "operator"
|
|
267
|
+
|| environment.YUI_TASK_ID !== undefined)
|
|
268
|
+
return false;
|
|
269
|
+
const role = store.getGlobalRole("operator");
|
|
270
|
+
if (role === null)
|
|
271
|
+
return false;
|
|
272
|
+
const sessions = store.getGlobalRoleSessionSet(role.name);
|
|
273
|
+
const session = activeLiveRoleAgentSession(sessions);
|
|
274
|
+
if (sessions === null || session === null || sessions.activeAgentId !== role.activeAgentId) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const agentId = exactIdentity(environment.YUI_AGENT_ID);
|
|
278
|
+
const adapterId = exactIdentity(environment.YUI_ADAPTER_ID);
|
|
279
|
+
const launchId = exactIdentity(environment.YUI_LAUNCH_ID);
|
|
280
|
+
const nativeSessionId = exactIdentity(environment.YUI_NATIVE_SESSION_ID);
|
|
281
|
+
const binding = role.agentBindings[role.activeAgentId];
|
|
282
|
+
// A fresh Codex launch discovers its native Session asynchronously. Its
|
|
283
|
+
// launch envelope therefore cannot carry YUI_NATIVE_SESSION_ID, but the
|
|
284
|
+
// durable Session still binds that provider identity to the exact launch
|
|
285
|
+
// generation. Accept that one transport shape only when the current
|
|
286
|
+
// launch, Agent, and adapter all match; Claude and stale/unknown launches
|
|
287
|
+
// remain fail-closed on the native identity fence.
|
|
288
|
+
const nativeSessionMatches = binding !== undefined && (nativeSessionId === session.nativeSessionId
|
|
289
|
+
|| (nativeSessionId === undefined
|
|
290
|
+
&& binding.adapterId === "codex"
|
|
291
|
+
&& session.adapterId === "codex"
|
|
292
|
+
&& session.launchId !== undefined
|
|
293
|
+
&& session.launchId === launchId));
|
|
294
|
+
return agentId !== undefined
|
|
295
|
+
&& adapterId !== undefined
|
|
296
|
+
&& launchId !== undefined
|
|
297
|
+
&& binding !== undefined
|
|
298
|
+
&& binding.agentId === session.agentId
|
|
299
|
+
&& binding.adapterId === session.adapterId
|
|
300
|
+
&& session.agentId === agentId
|
|
301
|
+
&& session.adapterId === adapterId
|
|
302
|
+
&& session.launchId !== undefined
|
|
303
|
+
&& session.launchId === launchId
|
|
304
|
+
&& nativeSessionMatches;
|
|
224
305
|
}
|
|
225
306
|
function inputAnswerer(environment) {
|
|
226
307
|
const env = environment ?? {};
|
|
@@ -237,28 +318,49 @@ function parseInputChoice(value) {
|
|
|
237
318
|
}
|
|
238
319
|
return { key: value.slice(0, separator).trim(), label: value.slice(separator + 1).trim() };
|
|
239
320
|
}
|
|
240
|
-
function parseInputBlockedRef(value) {
|
|
321
|
+
function parseInputBlockedRef(value, taskId) {
|
|
241
322
|
const separator = value.indexOf(":");
|
|
242
323
|
const type = value.slice(0, separator);
|
|
243
324
|
const id = value.slice(separator + 1).trim();
|
|
244
325
|
if ((type !== "work-item" && type !== "run") || separator <= 0 || id.length === 0) {
|
|
245
326
|
throw usageError("--blocks must use work-item:<id> or run:<id>.");
|
|
246
327
|
}
|
|
247
|
-
return { type, id };
|
|
328
|
+
return { type, taskId, id };
|
|
248
329
|
}
|
|
249
330
|
function validateBlockedInputOwnership(store, taskId, references) {
|
|
250
331
|
for (const reference of references) {
|
|
251
332
|
const record = reference.type === "work-item"
|
|
252
|
-
? store.
|
|
253
|
-
: store.
|
|
333
|
+
? store.getWorkItem(taskId, reference.id)
|
|
334
|
+
: store.getAgentRun(taskId, reference.id);
|
|
254
335
|
if (record === null)
|
|
255
336
|
throw dataError(`Blocked ${reference.type} not found: ${reference.id}.`);
|
|
256
|
-
if (record.taskId !== taskId) {
|
|
257
|
-
throw usageError(`Blocked ${reference.type} belongs to another Task: ${reference.id}.`);
|
|
258
|
-
}
|
|
259
337
|
}
|
|
260
338
|
}
|
|
261
|
-
function
|
|
339
|
+
function inputRequestReference(store, value, taskHint, environment) {
|
|
340
|
+
const explicitTaskId = taskHint === undefined
|
|
341
|
+
? undefined
|
|
342
|
+
: requireTask(store, taskHint).id;
|
|
343
|
+
let reference;
|
|
344
|
+
try {
|
|
345
|
+
reference = resolveTaskRecordReference(value, {
|
|
346
|
+
kind: "inputRequest",
|
|
347
|
+
label: "Input request reference",
|
|
348
|
+
...(explicitTaskId !== undefined
|
|
349
|
+
? { contextTaskId: explicitTaskId }
|
|
350
|
+
: environment?.YUI_TASK_ID === undefined
|
|
351
|
+
? {}
|
|
352
|
+
: { contextTaskId: environment.YUI_TASK_ID })
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
throw usageError(error instanceof Error ? error.message : String(error));
|
|
357
|
+
}
|
|
358
|
+
if (explicitTaskId !== undefined && reference.taskId !== explicitTaskId) {
|
|
359
|
+
throw usageError(`Input request belongs to another Task: ${reference.taskId}/${reference.localId}.`);
|
|
360
|
+
}
|
|
361
|
+
return reference;
|
|
362
|
+
}
|
|
363
|
+
function renderInputRequest(request, timeZone) {
|
|
262
364
|
return [
|
|
263
365
|
`Input: ${request.id}`,
|
|
264
366
|
`Task: ${request.taskId}`,
|
|
@@ -275,22 +377,22 @@ function renderInputRequest(request) {
|
|
|
275
377
|
? ["Policy: user response required"]
|
|
276
378
|
: [
|
|
277
379
|
`Policy: use recommended choice ${request.policy.recommendedChoiceKey} after timeout`,
|
|
278
|
-
`Timeout: ${request.policy.timeoutAt}`
|
|
380
|
+
`Timeout: ${formatTimestamp(request.policy.timeoutAt, timeZone)}`
|
|
279
381
|
]),
|
|
280
382
|
...(request.status === "answered"
|
|
281
383
|
? [
|
|
282
384
|
`Answered by: ${request.resolution.answeredBy}`,
|
|
283
385
|
`Answer: ${request.resolution.answer.text}`,
|
|
284
|
-
`Answered: ${request.resolution.answeredAt}`
|
|
386
|
+
`Answered: ${formatTimestamp(request.resolution.answeredAt, timeZone)}`
|
|
285
387
|
]
|
|
286
388
|
: request.status === "cancelled"
|
|
287
389
|
? [
|
|
288
390
|
`Cancellation: ${request.cancellation.reason}`,
|
|
289
|
-
`Cancelled: ${request.cancellation.cancelledAt}`
|
|
391
|
+
`Cancelled: ${formatTimestamp(request.cancellation.cancelledAt, timeZone)}`
|
|
290
392
|
]
|
|
291
393
|
: []),
|
|
292
|
-
`Created: ${request.createdAt}`,
|
|
293
|
-
`Updated: ${request.updatedAt}`
|
|
394
|
+
`Created: ${formatTimestamp(request.createdAt, timeZone)}`,
|
|
395
|
+
`Updated: ${formatTimestamp(request.updatedAt, timeZone)}`
|
|
294
396
|
].join("\n").concat("\n");
|
|
295
397
|
}
|
|
296
398
|
function requireTask(store, taskId) {
|
|
@@ -313,6 +415,8 @@ function inactiveTaskMessage(task, action) {
|
|
|
313
415
|
if (task.status === "completed") {
|
|
314
416
|
return `Task ${task.id} is completed; reopen it before ${action}.`;
|
|
315
417
|
}
|
|
418
|
+
if (task.status === "retired")
|
|
419
|
+
return `Task ${task.id} is retired; it cannot resume ${action}.`;
|
|
316
420
|
return `Task is archived: ${task.id}.`;
|
|
317
421
|
}
|
|
318
422
|
function requiredOption(options, name) {
|
|
@@ -332,7 +436,7 @@ function clock(options) {
|
|
|
332
436
|
return options.now?.() ?? new Date();
|
|
333
437
|
}
|
|
334
438
|
function recordTaskEvent(store, taskId, type, payload, now) {
|
|
335
|
-
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), type, payload, now));
|
|
439
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, type, payload, now));
|
|
336
440
|
}
|
|
337
441
|
function requiredText(value, label) {
|
|
338
442
|
const normalized = value?.trim();
|
|
@@ -344,6 +448,12 @@ function trimmed(value) {
|
|
|
344
448
|
const normalized = value?.trim();
|
|
345
449
|
return normalized === undefined || normalized.length === 0 ? undefined : normalized;
|
|
346
450
|
}
|
|
451
|
+
function exactIdentity(value) {
|
|
452
|
+
if (value === undefined || value.includes("\0"))
|
|
453
|
+
return undefined;
|
|
454
|
+
const normalized = value.trim();
|
|
455
|
+
return normalized.length === 0 || normalized !== value ? undefined : normalized;
|
|
456
|
+
}
|
|
347
457
|
function timeoutAfter(now, value) {
|
|
348
458
|
if (!/^[1-9][0-9]*$/.test(value)) {
|
|
349
459
|
throw usageError("--timeout-seconds must be a positive integer.");
|
|
@@ -421,3 +531,11 @@ function parseMultiValueTail(args, valueOptions, repeatOptions, usage) {
|
|
|
421
531
|
}
|
|
422
532
|
return { positionals, options, multiOptions };
|
|
423
533
|
}
|
|
534
|
+
function notifyMailbox(options, target, compatibilityTaskId) {
|
|
535
|
+
if (options.runtime?.notifyMailboxChanged !== undefined) {
|
|
536
|
+
options.runtime.notifyMailboxChanged(target);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
options.runtime?.notifyStateChanged(compatibilityTaskId);
|
|
540
|
+
}
|
|
541
|
+
}
|