@zq-silk/yui 0.15.9 → 0.15.12
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 +8 -4
- package/ARCHITECTURE.zh-CN.md +5 -2
- package/README.md +13 -5
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/cli/commandCatalog.js +64 -16
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +381 -107
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +339 -4
- package/dist/commands/projectCommands.js +50 -22
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +586 -96
- package/dist/commands/taskIntegrationCommands.js +19 -39
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskOverviewCommand.js +4 -3
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +11 -2
- package/dist/commands/taskPublicationVerifyCommand.js +23 -39
- package/dist/commands/taskRemoteDeliveryCommand.js +22 -11
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +3 -0
- package/dist/context/taskCatalog.js +187 -0
- package/dist/context/taskContext.js +55 -6
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +14 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +519 -25
- package/dist/controller/globalInputDelivery.js +132 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +117 -7
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +329 -386
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/lifecycle/exactRunTerminalization.js +4 -1
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +367 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +430 -107
- package/dist/repository/projectMaintenanceLock.js +75 -18
- package/dist/repository/taskWorkspaceCoordinator.js +182 -101
- package/dist/repository/taskWorkspacePreparer.js +205 -72
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentHost.js +179 -82
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +6 -2
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/activeRoleRunDelivery.js +12 -0
- package/dist/scheduler/leaderWakeupProcessor.js +5 -0
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +38 -6
- package/dist/scheduler/taskObservabilityProjection.js +6 -44
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +26 -8
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +167 -4
- package/dist/storage/sqliteStore.js +57 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +2 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +129 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/web/assets/client/app.js +147 -17
- package/dist/web/assets/client/components.js +56 -13
- package/dist/web/assets/client/i18n.js +78 -4
- package/dist/web/assets/client/taskSurface.js +108 -1
- package/dist/web/assets/client/view.js +39 -8
- package/dist/web/assets/shell.js +29 -0
- package/dist/web/assets/styles/layout.js +8 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +131 -4
- package/dist/web/webSnapshot.js +16 -6
- package/dist/web/webTaskSurface.js +222 -5
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +111 -35
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +30 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
- package/docs/managed-turn-and-session-runtime.md +47 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/release-workflow.md +39 -0
- package/docs/release-workflow.zh-CN.md +29 -0
- package/docs/sqlite-control-plane-design.md +223 -1
- package/docs/task-delivery.md +133 -13
- package/docs/task-delivery.zh-CN.md +99 -10
- package/docs/task-discovery.md +102 -0
- package/docs/task-discovery.zh-CN.md +86 -0
- package/docs/testing/verification-levels.md +40 -0
- package/docs/testing/verification-levels.zh-CN.md +23 -0
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +154 -51
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-operator/SKILL.md +19 -3
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +42 -0
- package/skills/yui-runtime/references/publication.md +42 -0
- package/skills/yui-runtime/references/recovery.md +24 -0
|
@@ -1,14 +1,53 @@
|
|
|
1
|
+
import { recordTaskInterruptResult } from "../message/taskInterrupt.js";
|
|
2
|
+
import { recordGlobalInterruptResult, recordGlobalSteerResult } from "../message/globalInterrupt.js";
|
|
3
|
+
import { runGlobalRoleCommand } from "../commands/globalRoleCommands.js";
|
|
1
4
|
import { readTaskContext, readTaskContextDelta, inspectTaskContext, withContextObservations } from "../context/taskContext.js";
|
|
2
5
|
import { BUILTIN_CAPABILITIES } from "../kernel/builtinCapabilities.js";
|
|
3
6
|
import { capabilitySchemaError } from "../kernel/capabilitySchema.js";
|
|
4
|
-
import { updateTaskMetadataCommand, sendTaskMessageCommand } from "../commands/taskCommands.js";
|
|
7
|
+
import { updateTaskMetadataCommand, sendTaskMessageCommand, runTaskCommand } from "../commands/taskCommands.js";
|
|
5
8
|
import { webLocalMutation, WebRequestRejected } from "./webMutation.js";
|
|
6
9
|
import { runTaskInputCommand } from "../commands/taskInputCommands.js";
|
|
10
|
+
import { sendAgentHostSteerControl, sendAgentHostCancelControl, AGENT_HOST_CONTROL_PROTOCOL, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "../runtime/agentHost.js";
|
|
11
|
+
const DEFAULT_WEB_HOST_CONTROL = {
|
|
12
|
+
steer: sendAgentHostSteerControl,
|
|
13
|
+
cancel: sendAgentHostCancelControl
|
|
14
|
+
};
|
|
15
|
+
/** The store-only CLI argv for the shared application-layer primitive
|
|
16
|
+
* (decision-3 §7). The Web surface never re-implements the queue/steer/interrupt
|
|
17
|
+
* decisions; it drives the exact same command the CLI drives. */
|
|
18
|
+
function controlArgv(taskId, input) {
|
|
19
|
+
if (input.action === "queue") {
|
|
20
|
+
return ["message", "queue", taskId, input.body, "--request-id", input.requestId,
|
|
21
|
+
...(input.to === undefined ? [] : ["--to", input.to]),
|
|
22
|
+
...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
|
|
23
|
+
...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
|
|
24
|
+
}
|
|
25
|
+
if (input.action === "steer") {
|
|
26
|
+
return ["message", "steer", taskId, input.body, "--request-id", input.requestId,
|
|
27
|
+
"--expected-target", input.expectedTarget, "--to", input.to,
|
|
28
|
+
...(input.workItem === undefined ? [] : ["--work-item", input.workItem]),
|
|
29
|
+
...(input.reviewRound === undefined ? [] : ["--review-round", input.reviewRound])];
|
|
30
|
+
}
|
|
31
|
+
return ["role", "interrupt", taskId, input.role, "--expected-target", input.expectedTarget,
|
|
32
|
+
...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage]),
|
|
33
|
+
"--request-id", input.requestId];
|
|
34
|
+
}
|
|
35
|
+
/** A queue is delivered to the Leader mailbox only when it is unaddressed or
|
|
36
|
+
* addressed to the Leader; an addressed Worker/Reviewer queue goes to the Task
|
|
37
|
+
* mailbox. A steer/interrupt is a live control op, so it only reconciles the
|
|
38
|
+
* Task. This mirrors the mailbox the core command itself enqueues. */
|
|
39
|
+
function controlNotifiesLeader(input) {
|
|
40
|
+
return input.action === "queue" && (input.to === undefined || input.to === "leader");
|
|
41
|
+
}
|
|
42
|
+
function controlTarget(taskId, input) {
|
|
43
|
+
const roleName = input.action === "interrupt" ? input.role : input.to ?? "leader";
|
|
44
|
+
return { scope: "task", taskId, roleName };
|
|
45
|
+
}
|
|
7
46
|
/** Installed only by the local-user Web composition root. HTTP authenticates
|
|
8
47
|
* its token before using this port; input never supplies a caller or Role.
|
|
9
48
|
* Managed capability RPC keeps its own Session authentication unchanged.
|
|
10
49
|
*/
|
|
11
|
-
export function createWebTaskSurface(store, options = {}, observations = []) {
|
|
50
|
+
export function createWebTaskSurface(store, options = {}, observations = [], hostControl = DEFAULT_WEB_HOST_CONTROL) {
|
|
12
51
|
const environment = {};
|
|
13
52
|
const commandOptions = { ...options, environment, runtime: undefined };
|
|
14
53
|
// Notifications are after the outer transaction. Their failure must not be
|
|
@@ -22,10 +61,83 @@ export function createWebTaskSurface(store, options = {}, observations = []) {
|
|
|
22
61
|
options.runtime?.notifyStateChanged(taskId);
|
|
23
62
|
};
|
|
24
63
|
return {
|
|
64
|
+
globalState: (roleName) => {
|
|
65
|
+
const role = store.getGlobalRole(roleName);
|
|
66
|
+
if (role === null)
|
|
67
|
+
throw new WebRequestRejected("Global Role not found.");
|
|
68
|
+
const sessions = store.getGlobalRoleSessionSet(roleName);
|
|
69
|
+
return {
|
|
70
|
+
roleName,
|
|
71
|
+
nativeSessionId: sessions?.sessions[sessions.activeAgentId]?.nativeSessionId,
|
|
72
|
+
turn: sessions?.providerBinding?.run ?? null,
|
|
73
|
+
authority: sessions?.providerBinding?.authority ?? null,
|
|
74
|
+
interrupts: sessions?.interrupts ?? {},
|
|
75
|
+
messages: store.listGlobalRoleMessages(roleName).map(message => ({
|
|
76
|
+
id: message.id, body: message.body, inputControl: message.inputControl,
|
|
77
|
+
control: message.control, delivery: message.delivery, notDelivered: message.notDelivered
|
|
78
|
+
}))
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
globalControl: async (roleName, input) => {
|
|
82
|
+
const argv = input.action === "interrupt"
|
|
83
|
+
? ["interrupt", roleName, "--request-id", input.requestId, "--expected-target", input.expectedTarget,
|
|
84
|
+
...(input.thenMessage === undefined ? [] : ["--then-message", input.thenMessage])]
|
|
85
|
+
: ["message", input.action, roleName, input.body, "--request-id", input.requestId,
|
|
86
|
+
...(input.action === "steer" ? ["--expected-target", input.expectedTarget] : [])];
|
|
87
|
+
const result = webLocalMutation(store, tx => runGlobalRoleCommand(argv, tx, {
|
|
88
|
+
env: {}, yuiHome: options.yuiHome, jsonOutput: true
|
|
89
|
+
}));
|
|
90
|
+
if (typeof result === "string") {
|
|
91
|
+
if (input.action === "queue")
|
|
92
|
+
void options.runtime?.notifyMailboxChanged?.({
|
|
93
|
+
kind: "global-role-runtime", roleName
|
|
94
|
+
});
|
|
95
|
+
return { action: input.action, ...JSON.parse(result) };
|
|
96
|
+
}
|
|
97
|
+
if (result.kind !== "input-steer" && result.kind !== "input-interrupt") {
|
|
98
|
+
throw new WebRequestRejected("Global input cannot perform a Session lifecycle operation.");
|
|
99
|
+
}
|
|
100
|
+
if (options.yuiHome === undefined)
|
|
101
|
+
throw new Error("Global control requires a configured Yui Home.");
|
|
102
|
+
if (result.kind === "input-steer") {
|
|
103
|
+
let control;
|
|
104
|
+
try {
|
|
105
|
+
control = await hostControl.steer({
|
|
106
|
+
home: options.yuiHome, scope: "global", roleName,
|
|
107
|
+
control: { protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "steer-turn",
|
|
108
|
+
nativeSessionId: result.target.nativeSessionId, nativeTurnId: result.target.nativeTurnId,
|
|
109
|
+
authority: result.target.authority,
|
|
110
|
+
run: { attemptId: result.receiptId, boundedText: result.text } }
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
recordGlobalSteerResult(store, roleName, result.messageId, { state: "steer-unknown", outcome: "pending" });
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
const steer = foldSteerLiveReceipt(control);
|
|
118
|
+
recordGlobalSteerResult(store, roleName, result.messageId, steer);
|
|
119
|
+
return { action: "steer", roleName, messageId: result.messageId, steer };
|
|
120
|
+
}
|
|
121
|
+
let control;
|
|
122
|
+
try {
|
|
123
|
+
control = await hostControl.cancel({
|
|
124
|
+
home: options.yuiHome, scope: "global", roleName,
|
|
125
|
+
control: { protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "cancel", nativeOnly: true,
|
|
126
|
+
nativeSessionId: result.target.nativeSessionId,
|
|
127
|
+
authority: result.target.authority, attemptId: result.target.attemptId }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
recordGlobalInterruptResult(store, roleName, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
const interrupt = foldInterruptLiveReceipt(control);
|
|
135
|
+
recordGlobalInterruptResult(store, roleName, result.receiptId, interrupt);
|
|
136
|
+
return { action: "interrupt", roleName, receiptId: result.receiptId, interrupt };
|
|
137
|
+
},
|
|
25
138
|
message: (taskId, body, intent, requestId) => {
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// the submission key (§2.3) and the structured feedback is returned verbatim.
|
|
139
|
+
// Preserve the submission's intent and frozen receipt separately from
|
|
140
|
+
// queue/steer controls; an omitted intent still means discuss.
|
|
29
141
|
const { message, task, queuedForLeader, feedback } = webLocalMutation(store, (tx) => sendTaskMessageCommand(tx, taskId, body, undefined, commandOptions, undefined, intent, requestId));
|
|
30
142
|
notify(taskId, queuedForLeader);
|
|
31
143
|
return { record: message, revision: message.createdAt,
|
|
@@ -34,6 +146,111 @@ export function createWebTaskSurface(store, options = {}, observations = []) {
|
|
|
34
146
|
...(feedback === undefined ? {} : { submission: feedback }),
|
|
35
147
|
target: { scope: "task", taskId, roleName: "leader" } };
|
|
36
148
|
},
|
|
149
|
+
/**
|
|
150
|
+
* The decision-3 three-action input-control path for the local-user Web
|
|
151
|
+
* surface. Its store-only phase is the identical shared application-layer
|
|
152
|
+
* primitive the CLI uses (`runTaskCommand`), run inside `webLocalMutation`
|
|
153
|
+
* so a rejected input is provably not-submitted. A ready steer/interrupt
|
|
154
|
+
* returns a live intent; the single Agent Host edge then runs OUTSIDE the
|
|
155
|
+
* transaction exactly as cli.ts performs it — never a fallback, retarget, or
|
|
156
|
+
* fourth action. A committed input whose live edge fails is delivery-unknown,
|
|
157
|
+
* not not-submitted: it throws a plain error so the receipt is "unknown" and
|
|
158
|
+
* the durable Message is retained (decision-3 §1/§3/§5, message-5 gap F).
|
|
159
|
+
*/
|
|
160
|
+
control: async (taskId, input) => {
|
|
161
|
+
const execution = webLocalMutation(store, (tx) => runTaskCommand(controlArgv(taskId, input), tx, commandOptions));
|
|
162
|
+
if (execution.kind === "output") {
|
|
163
|
+
// A queue receipt, or a steer/interrupt that was saved-but-not-delivered
|
|
164
|
+
// or an idempotent replay: fully durable, no live edge, exact disposition.
|
|
165
|
+
notify(taskId, controlNotifiesLeader(input));
|
|
166
|
+
const data = execution.data;
|
|
167
|
+
const settlement = data.delivery ?? data.steer ?? data.interrupt;
|
|
168
|
+
return {
|
|
169
|
+
action: input.action, disposition: settlement?.state ?? "saved",
|
|
170
|
+
target: controlTarget(taskId, input),
|
|
171
|
+
...(data.message === undefined ? {} : { record: data.message, revision: data.message.createdAt }),
|
|
172
|
+
...(data.delivery === undefined ? {} : { delivery: data.delivery }),
|
|
173
|
+
...(data.steer === undefined ? {} : { steer: data.steer }),
|
|
174
|
+
...(data.interrupt === undefined ? {} : { interrupt: data.interrupt })
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// A resolved live control op. Core has persisted the Message (steer) and
|
|
178
|
+
// recorded the one `pending` control attempt; both are already committed.
|
|
179
|
+
const home = options.yuiHome;
|
|
180
|
+
if (home === undefined) {
|
|
181
|
+
throw new Error("Live Agent Host control requires a configured Yui home.");
|
|
182
|
+
}
|
|
183
|
+
if (execution.kind === "input-steer") {
|
|
184
|
+
let control;
|
|
185
|
+
try {
|
|
186
|
+
control = await hostControl.steer({
|
|
187
|
+
home, scope: "task", taskId: execution.taskId, roleName: execution.roleName,
|
|
188
|
+
control: {
|
|
189
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "steer-turn",
|
|
190
|
+
nativeSessionId: execution.target.nativeSessionId,
|
|
191
|
+
nativeTurnId: execution.target.nativeTurnId ?? execution.target.attemptId,
|
|
192
|
+
authority: execution.target.authority,
|
|
193
|
+
run: { attemptId: execution.receiptId, boundedText: execution.text }
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
throw new Error(`Steer message ${execution.messageId} is saved but the native steer did not `
|
|
199
|
+
+ `complete: ${error instanceof Error ? error.message : String(error)}. The Message is `
|
|
200
|
+
+ "retained and its outcome is recorded from the Host; whether the Provider accepted it may "
|
|
201
|
+
+ "be delivery-unknown. Re-read the Session before acting; do not reissue the same input "
|
|
202
|
+
+ "under a new requestId or a different action.");
|
|
203
|
+
}
|
|
204
|
+
notify(taskId);
|
|
205
|
+
// decision-3 §7 live acceptance: fold the actual Host outcome rather than
|
|
206
|
+
// presume success. `steered` is the only proven delivery; pending is
|
|
207
|
+
// delivery-unknown; rejected/unavailable did not deliver. No fallback.
|
|
208
|
+
const steer = foldSteerLiveReceipt(control);
|
|
209
|
+
return { action: "steer", disposition: steer.state,
|
|
210
|
+
taskId, roleName: execution.roleName, messageId: execution.messageId,
|
|
211
|
+
target: { scope: "task", taskId, roleName: execution.roleName },
|
|
212
|
+
steer };
|
|
213
|
+
}
|
|
214
|
+
let control;
|
|
215
|
+
if (execution.kind !== "input-interrupt") {
|
|
216
|
+
// The three-action argv only ever yields output/input-steer/input-interrupt;
|
|
217
|
+
// any other intent means the shared command was mis-dispatched, not a
|
|
218
|
+
// control outcome to fold. Fail closed rather than guess.
|
|
219
|
+
throw new Error(`Unexpected control execution kind: ${execution.kind}.`);
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
control = await hostControl.cancel({
|
|
223
|
+
home, scope: "task", taskId: execution.taskId, roleName: execution.roleName,
|
|
224
|
+
control: {
|
|
225
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "cancel",
|
|
226
|
+
nativeOnly: true,
|
|
227
|
+
nativeSessionId: execution.target.nativeSessionId,
|
|
228
|
+
// Native cancel names the exact original execution attempt it stops,
|
|
229
|
+
// never the durable receiptId of this interrupt operation.
|
|
230
|
+
attemptId: execution.target.attemptId,
|
|
231
|
+
authority: execution.target.authority
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
recordTaskInterruptResult(store, execution.taskId, execution.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
|
|
237
|
+
throw new Error(`Interrupt ${execution.receiptId} of ${execution.taskId}/${execution.roleName} did not complete: `
|
|
238
|
+
+ `${error instanceof Error ? error.message : String(error)}. No process was killed; `
|
|
239
|
+
+ "re-read the Session before retrying.");
|
|
240
|
+
}
|
|
241
|
+
notify(taskId);
|
|
242
|
+
// The proof is `control.cancellation`, not the bare `cancel-requested`
|
|
243
|
+
// outcome: only a proven stop-request is `interrupted`. A then-handoff, if
|
|
244
|
+
// any, was already claimed durably by Core and is delivered once by the
|
|
245
|
+
// ordinary continuation path — never re-driven from this receipt.
|
|
246
|
+
const interrupt = foldInterruptLiveReceipt(control);
|
|
247
|
+
recordTaskInterruptResult(store, execution.taskId, execution.receiptId, interrupt);
|
|
248
|
+
return { action: "interrupt", disposition: interrupt.state,
|
|
249
|
+
taskId, roleName: execution.roleName,
|
|
250
|
+
target: { scope: "task", taskId, roleName: execution.roleName },
|
|
251
|
+
...(execution.thenMessageId === undefined ? {} : { thenMessageId: execution.thenMessageId }),
|
|
252
|
+
interrupt };
|
|
253
|
+
},
|
|
37
254
|
read: async (taskId) => withContextObservations(readTaskContext(store, taskId, environment), observations),
|
|
38
255
|
delta: (taskId, input) => readTaskContextDelta(store, taskId, input, environment),
|
|
39
256
|
inspect: (taskId, input) => inspectTaskContext(store, taskId, input, environment),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
2
|
+
export class CleanupInspectionError extends Error {
|
|
3
|
+
checks;
|
|
4
|
+
constructor(checks) {
|
|
5
|
+
super(checks.map(renderCleanupCheck).join("\n"));
|
|
6
|
+
this.checks = checks;
|
|
7
|
+
this.name = "CleanupInspectionError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function renderCleanupCheck(check) {
|
|
11
|
+
return `[${check.reason}] ${check.resource}: ${check.detail}`
|
|
12
|
+
+ ` Expected=${JSON.stringify(check.expected)}; observed=${JSON.stringify(check.observed)}.`
|
|
13
|
+
+ (check.actions.length === 0 ? "" : ` Inspect/resolve: ${check.actions.join("; ")}.`);
|
|
14
|
+
}
|
|
15
|
+
/** Do not forward arbitrary Git stderr, native arguments, or foreign paths. */
|
|
16
|
+
export function cleanupCheckFromError(error, resource, sources, actions) {
|
|
17
|
+
if (error instanceof CleanupInspectionError) {
|
|
18
|
+
return error.checks.map(check => ({ ...check, resource, sources, actions }));
|
|
19
|
+
}
|
|
20
|
+
let cause = error;
|
|
21
|
+
let errorCode;
|
|
22
|
+
for (let depth = 0; depth < 4 && cause instanceof Error; depth += 1) {
|
|
23
|
+
const code = cause.code;
|
|
24
|
+
if ((typeof code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(code))
|
|
25
|
+
|| (typeof code === "number" && Number.isSafeInteger(code))) {
|
|
26
|
+
errorCode = code;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
cause = cause.cause;
|
|
30
|
+
}
|
|
31
|
+
return [{ resource, reason: "inspection-unavailable", status: "unknown",
|
|
32
|
+
detail: "The resource could not be inspected; no safe cleanup conclusion is available.",
|
|
33
|
+
expected: "readable owned resource", observed: errorCode === undefined ? "unavailable" : { errorCode }, sources, actions }];
|
|
34
|
+
}
|
|
35
|
+
export function cleanupFailure(reason, detail, expected, observed, status = "blocked") {
|
|
36
|
+
throw new CleanupInspectionError([{ resource: "git-workspace", reason, status,
|
|
37
|
+
detail, expected, observed, sources: [], actions: [] }]);
|
|
38
|
+
}
|
|
39
|
+
/** Describe exact differences without disclosing paths outside this Task.
|
|
40
|
+
* Legacy locations are displayed only for this Task's exact branch identity;
|
|
41
|
+
* displaying a historical location is not accepting it as migration proof.
|
|
42
|
+
*/
|
|
43
|
+
export function workspacePathValue(workspace, path) {
|
|
44
|
+
const inside = (root) => {
|
|
45
|
+
const value = relative(root, path);
|
|
46
|
+
return value !== ".." && !value.startsWith("../") && !isAbsolute(value) ? value || "." : undefined;
|
|
47
|
+
};
|
|
48
|
+
const marker = `/workspaces/tasks/${workspace.owner.taskId}/`;
|
|
49
|
+
const index = workspace.root.lastIndexOf(marker);
|
|
50
|
+
if (index >= 0) {
|
|
51
|
+
const home = workspace.root.slice(0, index);
|
|
52
|
+
const taskPath = inside(join(home, "workspaces", "tasks", workspace.owner.taskId));
|
|
53
|
+
if (taskPath !== undefined)
|
|
54
|
+
return `<task>/${taskPath}`;
|
|
55
|
+
const legacyPath = inside(join(home, "workspaces", "worktree"));
|
|
56
|
+
const parts = legacyPath?.split("/");
|
|
57
|
+
const taskSegment = workspace.entries.find(e => e.access === "write")?.branch.split("/")[1];
|
|
58
|
+
if (parts?.length === 3 && parts[1] === taskSegment)
|
|
59
|
+
return `<legacy-worktree>/${legacyPath}`;
|
|
60
|
+
}
|
|
61
|
+
const local = inside(workspace.root);
|
|
62
|
+
return local === undefined ? "[outside authorized Task paths]" : `<owner>/${local}`;
|
|
63
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { createWorkItemChangeSet } from "../integration/changeSet.js";
|
|
3
5
|
import { createChangeSetManifest } from "../integration/changeSetManifest.js";
|
|
4
6
|
import { deriveManifestTags } from "../integration/manifestTags.js";
|
|
@@ -7,6 +9,7 @@ import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.j
|
|
|
7
9
|
import { governingWorkItemCandidate } from "../workItem/workItem.js";
|
|
8
10
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
9
11
|
import { captureManagedGitChanges } from "./gitChangeSetCapture.js";
|
|
12
|
+
import { CleanupInspectionError, cleanupCheckFromError, workspacePathValue } from "./cleanupInspection.js";
|
|
10
13
|
const CAPTURABLE_WORK_ITEM_STATUSES = new Set([
|
|
11
14
|
"open",
|
|
12
15
|
"accepted",
|
|
@@ -44,6 +47,12 @@ export class WorkItemChangeSetManager {
|
|
|
44
47
|
return captured;
|
|
45
48
|
}
|
|
46
49
|
async assertIntegrated(taskId, workItemId, candidateId) {
|
|
50
|
+
const inspection = await this.inspectIntegrated(taskId, workItemId, candidateId);
|
|
51
|
+
if (inspection.checks.length > 0)
|
|
52
|
+
throw new CleanupInspectionError(inspection.checks);
|
|
53
|
+
return inspection.proof;
|
|
54
|
+
}
|
|
55
|
+
async inspectIntegrated(taskId, workItemId, candidateId) {
|
|
47
56
|
const item = this.store.getWorkItem(taskId, workItemId);
|
|
48
57
|
if (item === null)
|
|
49
58
|
throw new Error(`Work item not found: ${taskId}/${workItemId}.`);
|
|
@@ -53,48 +62,114 @@ export class WorkItemChangeSetManager {
|
|
|
53
62
|
throw new Error(`Candidate not found: ${taskId}/${workItemId}/${candidateId}.`);
|
|
54
63
|
}
|
|
55
64
|
const workspace = this.store.getWorkItemWorkspace(item.taskId, item.id);
|
|
56
|
-
if (workspace === null
|
|
57
|
-
|
|
58
|
-
|| workspace.owner.workItemId !== item.id)
|
|
59
|
-
return null;
|
|
65
|
+
if (workspace === null)
|
|
66
|
+
return { proof: null, checks: [] };
|
|
60
67
|
const git = new NodeGitWorkspace();
|
|
61
68
|
const projects = [];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
const checks = [];
|
|
70
|
+
const sources = [`work-item:${taskId}/${workItemId}`,
|
|
71
|
+
`candidate:${taskId}/${workItemId}/${candidate?.id ?? "missing"}`, managedWorkspaceKey(workspace.owner)];
|
|
72
|
+
const actions = [`yui task work show ${taskId}/${workItemId}`, `yui task integration list ${taskId}`];
|
|
73
|
+
const add = (resource, reason, detail, expected, observed) => {
|
|
74
|
+
checks.push({ resource, reason, detail, expected, observed, status: "blocked", sources, actions });
|
|
75
|
+
};
|
|
76
|
+
const resource = `work-item:${taskId}/${workItemId}`;
|
|
77
|
+
if (workspace.owner.type !== "work-item" || workspace.owner.taskId !== taskId || workspace.owner.workItemId !== item.id) {
|
|
78
|
+
add(resource, "workspace-identity-mismatch", "Managed workspace is not owned by this WorkItem.", { type: "work-item", taskId, workItemId }, "different owner");
|
|
79
|
+
return { proof: null, checks };
|
|
80
|
+
}
|
|
81
|
+
if (candidate?.workspace === undefined) {
|
|
82
|
+
add(resource, "candidate-workspace-missing", "The governing Candidate has no frozen workspace.", "frozen WorkItem workspace", null);
|
|
83
|
+
}
|
|
84
|
+
else if (!isDeepStrictEqual(candidate.workspace, workspace)) {
|
|
85
|
+
const frozen = candidate.workspace;
|
|
86
|
+
if (!isDeepStrictEqual(frozen.owner, workspace.owner)) {
|
|
87
|
+
add(resource, "workspace-identity-mismatch", "Frozen and current workspace owners differ.", frozen.owner, workspace.owner);
|
|
88
|
+
}
|
|
89
|
+
// Paths remain frozen evidence. A path-shaped difference alone is NOT a
|
|
90
|
+
// receipt proving that migration moved this exact Candidate safely.
|
|
91
|
+
if (frozen.root !== workspace.root) {
|
|
92
|
+
add(resource, "workspace-path-mismatch", "Workspace roots differ; relocation is not proven.", workspacePathValue(workspace, frozen.root), workspacePathValue(workspace, workspace.root));
|
|
65
93
|
}
|
|
66
|
-
|
|
94
|
+
if (!isDeepStrictEqual(frozen.entries.map(e => e.projectId), workspace.entries.map(e => e.projectId))) {
|
|
95
|
+
add(resource, "workspace-identity-mismatch", "Frozen and current Project scope/order differ.", frozen.entries.map(e => e.projectId), workspace.entries.map(e => e.projectId));
|
|
96
|
+
}
|
|
97
|
+
for (const entry of workspace.entries) {
|
|
98
|
+
const previous = frozen.entries.find(e => e.projectId === entry.projectId);
|
|
99
|
+
if (previous === undefined)
|
|
100
|
+
continue;
|
|
101
|
+
const entryResource = `${resource}/${entry.projectId}`;
|
|
102
|
+
if (previous.path !== entry.path) {
|
|
103
|
+
add(entryResource, "workspace-path-mismatch", "Frozen and current Project paths differ; no relocation receipt is available. Historical evidence is unchanged.", workspacePathValue(workspace, previous.path), workspacePathValue(workspace, entry.path));
|
|
104
|
+
}
|
|
105
|
+
for (const key of ["directory", "access", "branch", "baseRef", "baseCommit"]) {
|
|
106
|
+
if (previous[key] !== entry[key])
|
|
107
|
+
add(entryResource, "workspace-identity-mismatch", `Frozen and current ${key} differ.`, { [key]: previous[key] }, { [key]: entry[key] });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const key of ["schemaVersion", "createdAt", "updatedAt"]) {
|
|
111
|
+
if (frozen[key] !== workspace[key])
|
|
112
|
+
add(resource, "workspace-metadata-mismatch", `Frozen and current ${key} differ.`, { [key]: frozen[key] }, { [key]: workspace[key] });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const entry of writableEntries(workspace)) {
|
|
116
|
+
const projectResource = `${resource}/${entry.projectId}`;
|
|
67
117
|
const resultCommit = candidate?.gitSnapshot?.projects.find(({ projectId }) => projectId === entry.projectId)?.commit;
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
118
|
+
if (resultCommit === undefined)
|
|
119
|
+
add(projectResource, "frozen-commit-missing", "The governing Candidate has no frozen Project commit.", "frozen commit", null);
|
|
120
|
+
if (resultCommit !== undefined) {
|
|
121
|
+
// An explicit historical selection is proved against its immutable
|
|
122
|
+
// integrated commit, not mislabeled as the workspace's current HEAD.
|
|
123
|
+
const integrated = this.store.listIntegrationAttempts(item.taskId).some(integration => integration.status === "committed"
|
|
124
|
+
&& integration.projectId === entry.projectId
|
|
125
|
+
&& integration.source.kind === "work-item"
|
|
126
|
+
&& integration.source.workItemId === item.id
|
|
127
|
+
&& integration.source.startCommit === entry.baseCommit
|
|
128
|
+
&& integration.source.resultCommit === resultCommit);
|
|
129
|
+
if (!integrated) {
|
|
130
|
+
add(projectResource, "result-not-integrated", "WorkItem result is not integrated.", { baseCommit: entry.baseCommit, resultCommit }, "no committed Integration with these exact commits");
|
|
131
|
+
}
|
|
132
|
+
projects.push({ projectId: entry.projectId, baseCommit: entry.baseCommit, headCommit: resultCommit });
|
|
73
133
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
134
|
+
if (entry.path !== join(workspace.root, entry.directory)) {
|
|
135
|
+
add(projectResource, "workspace-path-mismatch", "Current path differs from the exact owner-root/Project location.", workspacePathValue(workspace, join(workspace.root, entry.directory)), workspacePathValue(workspace, entry.path));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const path = await lstat(entry.path).catch(error => {
|
|
140
|
+
if (error.code === "ENOENT")
|
|
141
|
+
return null;
|
|
142
|
+
throw error;
|
|
143
|
+
});
|
|
144
|
+
if (path !== null && (path.isSymbolicLink() || await realpath(entry.path) !== entry.path)) {
|
|
145
|
+
add(projectResource, "workspace-identity-mismatch", "WorkItem path resolves through a symbolic link.", "real owned directory", "symbolic link");
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Absence is a filesystem fact, not proof of integration or Git cleanup.
|
|
149
|
+
// Check any retained branch against the same frozen Candidate; the
|
|
150
|
+
// cleanup primitive separately removes its exact Git registration.
|
|
151
|
+
const repository = this.store.getTaskWorkspace(taskId)?.entries.find(e => e.projectId === entry.projectId);
|
|
152
|
+
const workspaceHeadCommit = path !== null ? (await git.inspect(entry.path, "HEAD")).baseCommit
|
|
153
|
+
: repository !== undefined && await git.refExists(repository.path, entry.branch)
|
|
154
|
+
? (await git.inspect(repository.path, entry.branch)).baseCommit
|
|
155
|
+
: resultCommit;
|
|
156
|
+
if (candidateId === undefined && resultCommit !== undefined && resultCommit !== workspaceHeadCommit) {
|
|
157
|
+
add(projectResource, "head-mismatch", "Current HEAD no longer matches the frozen result.", resultCommit, workspaceHeadCommit ?? null);
|
|
158
|
+
}
|
|
159
|
+
if (path !== null && !await git.inspectClean(entry.path)) {
|
|
160
|
+
add(projectResource, "dirty-worktree", "WorkItem Project workspace is not clean.", "clean", "dirty");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
checks.push(...cleanupCheckFromError(error, projectResource, sources, actions));
|
|
85
165
|
}
|
|
86
|
-
projects.push({
|
|
87
|
-
projectId: entry.projectId,
|
|
88
|
-
baseCommit: entry.baseCommit,
|
|
89
|
-
headCommit
|
|
90
|
-
});
|
|
91
166
|
}
|
|
92
|
-
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
167
|
+
return { checks, proof: checks.length > 0 ? null : {
|
|
168
|
+
workItemId: item.id,
|
|
169
|
+
...(item.assignee === undefined ? {} : { assignee: item.assignee }),
|
|
170
|
+
workspace,
|
|
171
|
+
projects
|
|
172
|
+
} };
|
|
98
173
|
}
|
|
99
174
|
/**
|
|
100
175
|
* Fail-closed proof that retiring the aggregate will not hide unrecorded Git
|
|
@@ -109,6 +184,7 @@ export class WorkItemChangeSetManager {
|
|
|
109
184
|
}
|
|
110
185
|
const unresolved = this.store.listIntegrationAttempts(task.id).find((attempt) => (attempt.status === "running"
|
|
111
186
|
|| attempt.status === "blocked"
|
|
187
|
+
|| attempt.status === "conflicted"
|
|
112
188
|
|| attempt.status === "validating"));
|
|
113
189
|
if (unresolved !== undefined) {
|
|
114
190
|
throw new Error(`Task has an unresolved Integration Attempt: ${task.id}/${unresolved.id}.`);
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# Agent result consumption
|
|
4
4
|
|
|
5
|
+
For candidate discovery before reading original results, use the
|
|
6
|
+
[bounded Task catalog](task-discovery.md). A catalog summary never replaces a
|
|
7
|
+
requirement or the original result.
|
|
8
|
+
|
|
5
9
|
Every explicitly dispatched AgentRun produces one durable original result.
|
|
6
10
|
Ordinary notification and native conversation do not implicitly create Runs.
|
|
7
11
|
The next Agent in the ownership chain reads the exact result and decides what
|
|
@@ -67,6 +67,13 @@ than guessed. Incremental observers report health and coverage; sampling does
|
|
|
67
67
|
not block lifecycle events. Metrics never trigger model selection, wake, retry,
|
|
68
68
|
resource release or acceptance.
|
|
69
69
|
|
|
70
|
+
[Task usage and time](observability/README.md#task-usage-and-time) reuse this
|
|
71
|
+
reducer across historical Sessions. Task-fenced observations are not a billing
|
|
72
|
+
completeness guarantee. Native child counters are excluded from parent Session
|
|
73
|
+
totals without an explicit non-overlap contract; they are never extra requests.
|
|
74
|
+
Raw cumulative Session counters and safely attributable Task increments remain
|
|
75
|
+
distinct, and only exact request/Run bindings support WorkItem allocation.
|
|
76
|
+
|
|
70
77
|
## Native children
|
|
71
78
|
|
|
72
79
|
Native subagents are collaboration inside a parent conversation, not Yui Roles,
|
|
@@ -56,6 +56,11 @@ Task 完成。
|
|
|
56
56
|
和覆盖度;采样不阻塞生命周期事件。度量绝不触发模型选择、唤醒、重试、资源释放
|
|
57
57
|
或接受。
|
|
58
58
|
|
|
59
|
+
[Task 用量与耗时](observability/README.zh-CN.md#task-用量与耗时)跨历史 Session
|
|
60
|
+
复用此 reducer。Task 围栏内的观察不是完整账单保证。缺少明确互斥合同时,原生
|
|
61
|
+
子计数不加入父 Session 总量,也不作为额外请求。原始 Session 累计值与安全归属
|
|
62
|
+
的 Task 增量分开;仅精确请求/Run 绑定支持 WorkItem 分配。
|
|
63
|
+
|
|
59
64
|
## 原生子代
|
|
60
65
|
|
|
61
66
|
原生 subagent 是父对话内部的协作,不是 Yui Role、Lane 或独立的受管工作区 owner。
|
|
@@ -20,11 +20,13 @@ not a claim that every real Provider scenario has been validated.
|
|
|
20
20
|
| Question | Current document |
|
|
21
21
|
| --- | --- |
|
|
22
22
|
| How do Session, AgentRun, messages and activation fit together? | [Session and AgentRun runtime](../managed-turn-and-session-runtime.md) |
|
|
23
|
+
| What can Web control, and how do queue, steer and interrupt differ? | [Web permissions](capabilities-and-resources.md#cli-and-web) · [Input timing](../managed-turn-and-session-runtime.md#input-timing-queue-steer-and-interrupt) |
|
|
23
24
|
| Who consumes results, synthesis and review? | [Result consumption](../agent-result-consumption.md) |
|
|
24
25
|
| When is a WorkItem dependency satisfied? | [Task dependencies](../task-dag-semantics.md) |
|
|
25
26
|
| How are records referenced inside a Task? | [Task-local identity](../task-local-identity.md) |
|
|
26
27
|
| How do Roles, Profiles and run configuration take effect? | [Roles and configuration](../roles-and-configuration.md) |
|
|
27
28
|
| How do delivery, integration and archive work? | [Task delivery](../task-delivery.md) |
|
|
29
|
+
| What does Project refresh synchronize, and how are partial failures reported? | [Project refresh](../project-refresh.md) |
|
|
28
30
|
| How do Provider, ACP and configuration facts connect? | [Provider runtime](../provider-runtime.md) |
|
|
29
31
|
| Who interprets runtime observations and errors? | [Agent Drivers](../agent-runtime-drivers.md) |
|
|
30
32
|
| How are plugins created, validated and adopted? | [Plugin SDK](../plugin-sdk.md) |
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- [English README](../../README.md):安装、配置和日常使用。
|
|
10
10
|
- [中文 README](../../i18n/README.zh-CN.md):同一产品入口的中文说明。
|
|
11
|
-
- [总体架构](../../ARCHITECTURE.md)
|
|
11
|
+
- [总体架构](../../ARCHITECTURE.zh-CN.md):职责、权威和端到端流程。
|
|
12
12
|
- [能力、资源与 Surface](capabilities-and-resources.zh-CN.md):扩展入口、实例所有权和资源效果。
|
|
13
13
|
|
|
14
14
|
## 领域合同
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
| 问题 | 当前文档 |
|
|
17
17
|
| --- | --- |
|
|
18
18
|
| Session、AgentRun、消息和激活如何配合? | [执行与会话](../managed-turn-and-session-runtime.zh-CN.md) |
|
|
19
|
+
| Web 可以控制什么,queue、steer 和 interrupt 有何区别? | [Web 权限](capabilities-and-resources.zh-CN.md#cli-与-web) · [输入时机](../managed-turn-and-session-runtime.zh-CN.md#输入时机queuesteer-与-interrupt) |
|
|
19
20
|
| 谁消费结果、综合与审查? | [结果消费](../agent-result-consumption.zh-CN.md) |
|
|
20
21
|
| WorkItem 依赖何时满足? | [Task 依赖](../task-dag-semantics.zh-CN.md) |
|
|
21
22
|
| Task 内记录如何引用? | [局部身份](../task-local-identity.zh-CN.md) |
|
|
22
23
|
| Role、Profile 与运行配置如何生效? | [角色与配置](../roles-and-configuration.zh-CN.md) |
|
|
23
24
|
| 怎样交付、集成和归档? | [交付生命周期](../task-delivery.zh-CN.md) |
|
|
25
|
+
| Project refresh 同步什么,怎样报告部分失败? | [Project refresh](../project-refresh.zh-CN.md) |
|
|
24
26
|
| Provider、ACP 与配置事实如何接入? | [Provider Runtime](../provider-runtime.zh-CN.md) |
|
|
25
27
|
| 运行观察和错误由谁解释? | [Agent Drivers](../agent-runtime-drivers.zh-CN.md) |
|
|
26
28
|
| 如何创建、验证与采用插件? | [插件 SDK](../plugin-sdk.zh-CN.md) |
|
|
@@ -111,8 +111,33 @@ capability's original name. A Web panel accepts only controlled text, an HTTP(S)
|
|
|
111
111
|
link or a JSON query description — not author scripts or arbitrary HTML.
|
|
112
112
|
|
|
113
113
|
The Web listener is started and stopped by the Controller and allows loopback
|
|
114
|
-
only.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
only (`127.0.0.1`, `::1` or `localhost`; default port 4173). `yui web` opens this
|
|
115
|
+
local surface, not a remote multi-user service or an OS sandbox.
|
|
116
|
+
|
|
117
|
+
The page supplies a token that authenticates all HTTP API reads and writes via
|
|
118
|
+
`x-yui-web-token`; the server also checks the loopback Host. These controls act
|
|
119
|
+
as the trusted local user, not a Role selected by the request body. They support
|
|
120
|
+
Task metadata edits, messages, InputRequest answers, and explicit
|
|
121
|
+
`queue / steer / interrupt` for Task or Global Roles. Managed Agent capability
|
|
122
|
+
RPC retains its own Session authentication and scope; a browser token is not
|
|
123
|
+
a way for an Agent or plugin to bypass those boundaries.
|
|
124
|
+
|
|
125
|
+
Read-only dashboard, Context and query-panel projections remain separate from
|
|
126
|
+
these mutations. A query panel cannot borrow the browser's user authority to
|
|
127
|
+
mutate state or manage plugins. Task controls share the public CLI's domain
|
|
128
|
+
commands; Global Role controls share the Global handler but currently lack a
|
|
129
|
+
registered top-level CLI path. Message submission intent
|
|
130
|
+
(`record / discuss / develop`, default `discuss`) is
|
|
131
|
+
separate from [input timing](../managed-turn-and-session-runtime.md#input-timing-queue-steer-and-interrupt).
|
|
132
|
+
Transport acceptance does not establish implementation or Task acceptance.
|
|
133
|
+
|
|
134
|
+
A browser write uses an existing domain transaction. Errors distinguish proven
|
|
135
|
+
`not-submitted` from `unknown`, which can include an already-committed Message
|
|
136
|
+
whose native delivery failed or is unconfirmed. Read the original Message,
|
|
137
|
+
control receipt and current Session before choosing recovery; do not blindly
|
|
138
|
+
resubmit under a new request ID or switch actions.
|
|
139
|
+
|
|
140
|
+
A terminal WebSocket checks the token and same-origin handshake. It attaches a
|
|
141
|
+
client without taking over durable conversation ownership, and respects the
|
|
142
|
+
connection's `readOnly` flag. A terminal attachment is not a grant to control a
|
|
143
|
+
different Session.
|