@zq-silk/yui 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commandCatalog.js +4 -3
- package/dist/commands/taskCommands.js +48 -15
- package/dist/commands/taskContextCommand.js +3 -2
- package/dist/context/sessionBootstrapManifest.js +8 -2
- package/dist/context/taskContext.js +7 -4
- package/dist/controller/fileSchedulerStoreAdapter.js +7 -2
- package/dist/controller/globalInputDelivery.js +2 -4
- package/dist/controller/providerRetryDelivery.js +4 -2
- package/dist/executor/agentAdapter.js +3 -5
- package/dist/executor/fileRoleLaunchPlanner.js +2 -2
- package/dist/interaction/notificationPresentation.js +85 -0
- package/dist/interaction/operatorPresentation.js +1 -2
- package/dist/kernel/builtinCapabilities.js +2 -1
- package/dist/runtime/builtinAgentErrorMappers.js +9 -5
- package/dist/runtime/providerRetry.js +38 -0
- package/dist/runtime/taskSessions.js +240 -0
- package/dist/scheduler/leaderWakeupProcessor.js +1 -9
- package/dist/scheduler/taskExecutionProjection.js +78 -6
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app/taskView.js +1 -0
- package/dist/web/assets/client/domain/records.js +70 -2
- package/dist/web/assets/client/domain/runs.js +3 -2
- package/dist/web/assets/client/domain/vocab.js +1 -1
- package/dist/web/assets/client/i18n/en.js +17 -1
- package/dist/web/assets/client/i18n/zh-CN.js +17 -1
- package/dist/web/assets/client/lib/api.js +4 -1
- package/dist/web/assets/client/lib/theme.js +31 -10
- package/dist/web/assets/client/views/task/actions.js +38 -21
- package/dist/web/assets/client/views/task/observation.js +12 -3
- package/dist/web/assets/client/views/task/overview.js +7 -40
- package/dist/web/assets/client/views/task/runtime.js +71 -34
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shell/index.js +1 -0
- package/dist/web/assets/shell/settings.js +1 -0
- package/dist/web/assets/styles/views/task.js +14 -0
- package/dist/web/webConversation.js +2 -2
- package/dist/web/webServer.js +12 -0
- package/dist/web/webSessions.js +2 -165
- package/dist/web/webSnapshot.js +9 -5
- package/dist/web/webTaskActivity.js +54 -0
- package/dist/web/webTaskSurface.js +1 -0
- package/docs/cli-information-contract.md +1 -1
- package/docs/cli-information-contract.zh-CN.md +1 -1
- package/docs/managed-turn-and-session-runtime.md +22 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +15 -0
- package/docs/observability/README.md +25 -1
- package/docs/observability/README.zh-CN.md +18 -1
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -4
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-operator/SKILL.md +3 -0
- package/skills/yui-reviewer/SKILL.md +4 -2
- package/skills/yui-runtime/SKILL.md +43 -2
- package/skills/yui-runtime/references/recovery.md +9 -0
- package/skills/yui-worker/SKILL.md +3 -2
|
@@ -408,8 +408,9 @@ const taskChildren = [
|
|
|
408
408
|
{
|
|
409
409
|
name: "context", discovery: { surface: "public", audiences: ["public", "operator", "leader", "assignment", "unbound"] },
|
|
410
410
|
summary: "Read compact authorized facts, fixed-bound delta, or inspect a Context reference.",
|
|
411
|
-
usage: "yui task context [read|list|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--cursor <cursor>] [--limit <n>] [--store <store> --ref <id>] [--digest <digest>]",
|
|
412
|
-
options: ["--after", "--continuation", "--cursor", "--limit", "--store", "--ref", "--digest", "--status", "--work-item"]
|
|
411
|
+
usage: "yui task context [read|list|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--cursor <cursor>] [--limit <n>] [--store <store> --ref <id>] [--digest <digest>] [--view <current|all>]",
|
|
412
|
+
options: ["--after", "--continuation", "--cursor", "--limit", "--store", "--ref", "--digest", "--status", "--work-item", "--view"],
|
|
413
|
+
optionValues: { "--view": ["current", "all"] }
|
|
413
414
|
},
|
|
414
415
|
{
|
|
415
416
|
name: "next-action", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
@@ -1096,7 +1097,7 @@ const taskChildren = [
|
|
|
1096
1097
|
sections: [{ id: "manage", title: "Commands", entries: ["list", "show", "retry", "resolve"] }],
|
|
1097
1098
|
children: [
|
|
1098
1099
|
{ name: "list", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "List recorded Leader wakes.", usage: "yui task wake list <task> [--limit <1..100>] [--cursor <cursor>]", options: ["--limit", "--cursor"] },
|
|
1099
|
-
{ name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show
|
|
1100
|
+
{ name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show fixed wake inputs/results; expand diagnostics with --history.", usage: "yui task wake show <task> <wake> [--history] [--cursor <cursor>]", options: ["--history", "--cursor"] },
|
|
1100
1101
|
{ name: "retry", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Retry a rejected notification after correcting its cause; preserve original input.",
|
|
1101
1102
|
usage: "yui task wake retry <task> <wake> --reason <correction>", options: ["--reason"] },
|
|
1102
1103
|
{ name: "resolve", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Release an unknown notification claim after explicit quiescence evidence, without replay.",
|
|
@@ -13,6 +13,7 @@ import { assertContextRecordReadable, readTaskContextResource } from "../context
|
|
|
13
13
|
import { parseTaskSearchOptions, searchTaskBodies } from "../context/taskSearch.js";
|
|
14
14
|
import { boundedDocument, readOptions, messageReceipt, recordPage } from "../output/boundedRead.js";
|
|
15
15
|
import { referencedWakeRunIds } from "../context/wakeRunReferences.js";
|
|
16
|
+
import { taskMessagePresentation, wakeChangeLabels, wakeMessages } from "../interaction/notificationPresentation.js";
|
|
16
17
|
import { completeProcessing, releaseProcessing } from "../coordination/workMailbox.js";
|
|
17
18
|
import { enqueueRoleRunDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
|
|
18
19
|
import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageError } from "../errors/cliError.js";
|
|
@@ -44,14 +45,14 @@ import { copyGlobalRoleToTaskRole, activeRoleAgentBinding, createRole, createRol
|
|
|
44
45
|
import { roleLaunchEventPayload, saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
|
|
45
46
|
import { RUNTIME_SESSION_REPLACE_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
46
47
|
import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
|
|
47
|
-
import { controlProviderRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
48
|
+
import { controlProviderRetry, providerRunRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
48
49
|
import { currentProviderConversation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
|
|
49
50
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
50
51
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
51
52
|
import { failureCapabilitiesCommand } from "../runtime/agentFailureContext.js";
|
|
52
53
|
import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
|
|
53
54
|
import { isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
|
|
54
|
-
import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
|
|
55
|
+
import { parseWakeReason, renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
|
|
55
56
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
56
57
|
import { planningRuntimeCwd } from "../storage/homeLayout.js";
|
|
57
58
|
import { StorageConflictError } from "../storage/taskStore.js";
|
|
@@ -4896,7 +4897,9 @@ function retryRun(args, store, options) {
|
|
|
4896
4897
|
return output(`Retry already recorded as ${existing.payload.runId}; retry that exact successor if needed.\n`);
|
|
4897
4898
|
}
|
|
4898
4899
|
const sessions = tx.getTaskRoleSessionSet(previous.taskId, previous.roleName);
|
|
4899
|
-
const retry = sessions?.providerBinding?.retry
|
|
4900
|
+
const retry = options.providerRetryChainId !== undefined ? sessions?.providerBinding?.retry
|
|
4901
|
+
: sessions?.providerBinding == null ? undefined
|
|
4902
|
+
: providerRunRetry(sessions.providerBinding, previous.id, clock(options).getTime());
|
|
4900
4903
|
if (options.providerRetryChainId !== undefined
|
|
4901
4904
|
&& (retry?.chainId !== options.providerRetryChainId || retry.status !== "waiting"
|
|
4902
4905
|
|| retry.previousRunId !== previous.id || clock(options).getTime() < Date.parse(retry.nextEligibleAt)
|
|
@@ -4908,7 +4911,10 @@ function retryRun(args, store, options) {
|
|
|
4908
4911
|
const successor = tx.getActiveRun(previous.taskId, previous.roleName);
|
|
4909
4912
|
tx.saveTaskRoleSessionSet({
|
|
4910
4913
|
...sessions, providerBinding: {
|
|
4911
|
-
...sessions.providerBinding,
|
|
4914
|
+
...sessions.providerBinding,
|
|
4915
|
+
...(sessions.providerBinding.retry !== undefined && sessions.providerBinding.retry.chainId !== retry.chainId
|
|
4916
|
+
? { retryHistory: [...(sessions.providerBinding.retryHistory ?? []), sessions.providerBinding.retry] } : {}),
|
|
4917
|
+
retry: {
|
|
4912
4918
|
...retry, status: "waiting", successorAutomatic: options.providerRetryChainId !== undefined,
|
|
4913
4919
|
...(options.providerRetryChainId === undefined ? {
|
|
4914
4920
|
intentAt: clock(options).toISOString(),
|
|
@@ -6932,12 +6938,16 @@ function taskWakeInspectionCommand(args, store) {
|
|
|
6932
6938
|
return output(`${JSON.stringify(data)}\n`, data);
|
|
6933
6939
|
}
|
|
6934
6940
|
if (command === "show") {
|
|
6935
|
-
const usage = "Task wake show usage: yui task wake show <task> <wake
|
|
6936
|
-
|
|
6937
|
-
const
|
|
6938
|
-
|
|
6941
|
+
const usage = "Task wake show usage: yui task wake show <task> <wake> [--history].";
|
|
6942
|
+
const history = rest.includes("--history");
|
|
6943
|
+
const positional = history ? rest.filter(arg => arg !== "--history") : rest;
|
|
6944
|
+
exactPositionals(positional, 2, usage);
|
|
6945
|
+
if (rest.filter(arg => arg === "--history").length > 1)
|
|
6946
|
+
throw usageError(usage);
|
|
6947
|
+
const task = requireTask(store, positional[0]);
|
|
6948
|
+
const wake = store.getTaskWake(task.id, positional[1]);
|
|
6939
6949
|
if (wake === null)
|
|
6940
|
-
throw dataError(`Wake not found: ${
|
|
6950
|
+
throw dataError(`Wake not found: ${positional[1]}.`);
|
|
6941
6951
|
const timeZone = store.getConfig().timeZone;
|
|
6942
6952
|
const fromMs = Date.parse(wake.fromCursor);
|
|
6943
6953
|
const toMs = Date.parse(wake.toCursor);
|
|
@@ -6953,8 +6963,20 @@ function taskWakeInspectionCommand(args, store) {
|
|
|
6953
6963
|
const currentClaim = store.getWorkMailbox(leaderMailbox(task.id))?.processing;
|
|
6954
6964
|
const referenced = (type, id) => wake.refs?.some(ref => ref.type === type && ref.id === id
|
|
6955
6965
|
&& (!("taskId" in ref) || ref.taskId === task.id)) === true;
|
|
6956
|
-
|
|
6957
|
-
|
|
6966
|
+
// Some existing producers carry the exact source only in a reason tag
|
|
6967
|
+
// (e.g. input-timeout:<request>). Do not hide its decision behind --history.
|
|
6968
|
+
const reasonRefs = new Set(wake.reasons.flatMap(tag => {
|
|
6969
|
+
const reason = parseWakeReason(tag);
|
|
6970
|
+
return reason.kind !== "force-wake" && reason.ref !== undefined ? [reason.ref] : [];
|
|
6971
|
+
}));
|
|
6972
|
+
const reasonEvent = (event) => Date.parse(event.createdAt) <= toMs && [
|
|
6973
|
+
event.id, event.payload.runId, event.payload.requestId, event.payload.jobId,
|
|
6974
|
+
event.payload.workItemId, event.payload.reviewRoundId
|
|
6975
|
+
].some(id => id !== undefined && reasonRefs.has(id));
|
|
6976
|
+
const windowEvents = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id) || reasonEvent(e));
|
|
6977
|
+
const events = history ? windowEvents : windowEvents.filter(e => referenced("event", e.id)
|
|
6978
|
+
|| reasonEvent(e) || ["run.completed", "run.failed", "run.cancelled"].includes(e.type));
|
|
6979
|
+
const messages = wakeMessages(wake, store.listMessages(task.id));
|
|
6958
6980
|
const allRuns = store.listRuns(task.id);
|
|
6959
6981
|
const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
|
|
6960
6982
|
for (const run of allRuns)
|
|
@@ -6963,7 +6985,7 @@ function taskWakeInspectionCommand(args, store) {
|
|
|
6963
6985
|
for (const message of messages)
|
|
6964
6986
|
if (message.resultRef?.type === "agent-run-result")
|
|
6965
6987
|
referencedRunIds.add(message.resultRef.runId);
|
|
6966
|
-
const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (inWindow(run.createdAt) || referencedRunIds.has(run.id)));
|
|
6988
|
+
const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (history && inWindow(run.createdAt) || referencedRunIds.has(run.id)));
|
|
6967
6989
|
const lines = [
|
|
6968
6990
|
`Wake: ${wake.id}`,
|
|
6969
6991
|
`Task: ${task.id}`,
|
|
@@ -6979,29 +7001,40 @@ function taskWakeInspectionCommand(args, store) {
|
|
|
6979
7001
|
`Recovery: correct the cause, then yui task wake retry ${task.id} ${wake.id} --reason <correction>.`
|
|
6980
7002
|
]),
|
|
6981
7003
|
`Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
|
|
7004
|
+
`Changes: ${wakeChangeLabels(wake.reasons).join("; ")}`,
|
|
6982
7005
|
`Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
|
|
6983
7006
|
`Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
|
|
6984
7007
|
...(wake.consumedAt === undefined
|
|
6985
7008
|
? []
|
|
6986
7009
|
: [`Consumed: ${presentTime(wake.consumedAt, timeZone)}`]),
|
|
6987
7010
|
`Events (${events.length}):`,
|
|
6988
|
-
...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
|
|
7011
|
+
...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)} → yui task event show ${task.id} ${e.id}`),
|
|
6989
7012
|
`Messages (${messages.length}):`,
|
|
6990
|
-
...messages.map((m) =>
|
|
7013
|
+
...messages.map((m) => {
|
|
7014
|
+
const item = taskMessagePresentation(m, (taskId, runId) => store.getRun(taskId, runId));
|
|
7015
|
+
return ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${item.fact}`
|
|
7016
|
+
+ (item.summary === undefined ? "" : `; author summary (${item.summarySource}): ${JSON.stringify(item.summary)}`)
|
|
7017
|
+
+ ` → yui task message show ${task.id}/${m.id}`;
|
|
7018
|
+
}),
|
|
6991
7019
|
`AgentRuns (${runs.length}):`,
|
|
6992
7020
|
...runs.map((run) => (` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} `
|
|
6993
7021
|
+ `${presentTime(run.createdAt, timeZone)}`
|
|
6994
7022
|
+ `${referencedRunIds.has(run.id)
|
|
6995
7023
|
? ` → yui task run show ${task.id}/${run.id}`
|
|
6996
|
-
: ""}`))
|
|
7024
|
+
: ""}`)),
|
|
7025
|
+
...(!history ? [`History/diagnostics: yui task wake show ${task.id} ${wake.id} --history`,
|
|
7026
|
+
"Read relevant originals in full; follow contentPage.nextCursor when present."] : [])
|
|
6997
7027
|
];
|
|
6998
7028
|
return output(lines.join("\n").concat("\n"), {
|
|
6999
7029
|
taskId: task.id,
|
|
7000
7030
|
deliveryEvents,
|
|
7001
7031
|
wake,
|
|
7032
|
+
view: history ? "history" : "focus",
|
|
7033
|
+
historyRead: `task wake show ${task.id} ${wake.id} --history`,
|
|
7002
7034
|
events: events.map(event => ({ id: event.id, type: event.type, createdAt: event.createdAt,
|
|
7003
7035
|
read: `task event show ${task.id} ${event.id}` })),
|
|
7004
7036
|
messages: messages.map(message => ({ id: message.id, kind: message.kind, author: message.author,
|
|
7037
|
+
...taskMessagePresentation(message, (taskId, runId) => store.getRun(taskId, runId)),
|
|
7005
7038
|
resultRef: message.resultRef, read: `task message show ${task.id}/${message.id}` })),
|
|
7006
7039
|
runs: runs.map(run => ({ id: run.id, status: run.status, purpose: run.purpose, roleName: run.roleName,
|
|
7007
7040
|
read: `task run show ${task.id}/${run.id}` }))
|
|
@@ -17,12 +17,12 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
17
17
|
const action = explicit ? args[0] : "read";
|
|
18
18
|
const taskId = args[explicit ? 1 : 0];
|
|
19
19
|
if (!taskId || taskId.startsWith("--"))
|
|
20
|
-
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--status <status>] [--work-item <id>].");
|
|
20
|
+
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--view <current|all>] [--status <status>] [--work-item <id>].");
|
|
21
21
|
const options = new Map();
|
|
22
22
|
const rest = args.slice(explicit ? 2 : 1);
|
|
23
23
|
const allowed = action === "read" ? [] : action === "delta"
|
|
24
24
|
? ["--after", "--continuation", "--limit"] : action === "list"
|
|
25
|
-
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
25
|
+
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item", "--view"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
26
26
|
for (let i = 0; i < rest.length; i += 2) {
|
|
27
27
|
const flag = rest[i];
|
|
28
28
|
const value = rest[i + 1];
|
|
@@ -40,6 +40,7 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
40
40
|
throw usageError("Context list requires --store.");
|
|
41
41
|
data = listTaskContext(store, taskId, family, {
|
|
42
42
|
cursor: options.get("--cursor"),
|
|
43
|
+
view: options.get("--view"),
|
|
43
44
|
status: options.get("--status"), after: options.get("--after"), workItemId: options.get("--work-item"),
|
|
44
45
|
...(options.has("--limit") ? { limit: Number(options.get("--limit")) } : {})
|
|
45
46
|
}, environment);
|
|
@@ -12,12 +12,18 @@ export function withSessionContextPointer(text, environment) {
|
|
|
12
12
|
if (manifest === undefined)
|
|
13
13
|
return text;
|
|
14
14
|
return [
|
|
15
|
-
|
|
16
|
-
"Follow its referenced Role Skills and role profile. It gives the exact Context load command.",
|
|
15
|
+
sessionContextInstructions(manifest),
|
|
17
16
|
"",
|
|
18
17
|
text
|
|
19
18
|
].join("\n");
|
|
20
19
|
}
|
|
20
|
+
/** No remembered/sent flag can prove instructions survived context compaction. */
|
|
21
|
+
export function sessionContextInstructions(manifestPath, digest) {
|
|
22
|
+
return `Yui Session Manifest: ${manifestPath}${digest === undefined ? "" : ` (digest ${digest})`}. `
|
|
23
|
+
+ "Apply yui-runtime and its Role Skills/profile by exact manifest paths. "
|
|
24
|
+
+ "Load on first entry, version change or missing context; reuse only same-version instructions "
|
|
25
|
+
+ "fully available now, never infer availability from prior reads.";
|
|
26
|
+
}
|
|
21
27
|
/** Read back one immutable Session Manifest and verify its content digest. */
|
|
22
28
|
export function readSessionBootstrapManifest(path) {
|
|
23
29
|
const source = resolve(path);
|
|
@@ -191,7 +191,10 @@ export function readTaskContextResource(store, taskId, selector, environment = {
|
|
|
191
191
|
export function listTaskContext(store, taskId, family, options = {}, environment = {}) {
|
|
192
192
|
return store.readTransaction(reader => {
|
|
193
193
|
const scope = authorizeContext(reader, taskId, environment);
|
|
194
|
-
|
|
194
|
+
if (options.view !== undefined && !["current", "all"].includes(options.view)) {
|
|
195
|
+
throw usageError("Context list view must be current or all.");
|
|
196
|
+
}
|
|
197
|
+
const sources = authorizedSources(reader, scope, options.view === "current", family, true);
|
|
195
198
|
if (!CONTEXT_FAMILIES.includes(family))
|
|
196
199
|
throw usageError(`Unknown Context store: ${family}.`);
|
|
197
200
|
if (options.after !== undefined && !Number.isFinite(Date.parse(options.after)))
|
|
@@ -216,7 +219,7 @@ export function listTaskContext(store, taskId, family, options = {}, environment
|
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
return recordPage(records, `task:${taskId}/${family}/${JSON.stringify({
|
|
219
|
-
status: options.status, after: options.after, workItemId: options.workItemId
|
|
222
|
+
view: options.view ?? "all", status: options.status, after: options.after, workItemId: options.workItemId
|
|
220
223
|
})}`, options);
|
|
221
224
|
});
|
|
222
225
|
}
|
|
@@ -447,7 +450,7 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
|
|
|
447
450
|
default: return null;
|
|
448
451
|
}
|
|
449
452
|
}
|
|
450
|
-
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily) {
|
|
453
|
+
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily, discovery = false) {
|
|
451
454
|
const taskId = task.id;
|
|
452
455
|
const sources = [];
|
|
453
456
|
const add = (family, id, value) => {
|
|
@@ -462,7 +465,7 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
|
|
|
462
465
|
const { count } = store.queryContextRecords(taskId, { ...selection, limit: 0 });
|
|
463
466
|
sources.push({ family, count, *read(offset = 0) {
|
|
464
467
|
for (const { id, value } of store.queryContextRecords(taskId, {
|
|
465
|
-
...selection, offset, limit: current ? 8 : MAX_RECORDS
|
|
468
|
+
...selection, offset, limit: current && !discovery ? 8 : MAX_RECORDS
|
|
466
469
|
}).records)
|
|
467
470
|
yield materialize(family, id, value);
|
|
468
471
|
} });
|
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
3
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
4
4
|
import { freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
5
|
+
import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
|
|
5
6
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
6
7
|
import { interruptThenTerminalState, prepareMessageContinuations } from "../message/messageContinuation.js";
|
|
7
8
|
import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
|
|
@@ -1848,7 +1849,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1848
1849
|
}
|
|
1849
1850
|
const nextAttempt = `notification:${taskId}/${wakeId}/${randomUUID()}`;
|
|
1850
1851
|
store.saveWorkMailbox({ ...mailbox, processing: { ...processing, batchId: nextAttempt } });
|
|
1851
|
-
|
|
1852
|
+
const wake = store.getTaskWake(taskId, wakeId);
|
|
1853
|
+
if (wake === null)
|
|
1854
|
+
throw new Error(`Notification wake is unavailable: ${taskId}/${wakeId}.`);
|
|
1855
|
+
return { wakeId, attemptId: nextAttempt, disposition: "submit",
|
|
1856
|
+
presentation: taskNotificationPresentation(wake, store) };
|
|
1852
1857
|
}
|
|
1853
1858
|
if (mailbox.pending === null || (provider !== null && provider !== undefined
|
|
1854
1859
|
&& ["submitting", "accepted", "delivery-unknown"].includes(provider.status)))
|
|
@@ -1918,7 +1923,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1918
1923
|
// batch, rather than delivering them with the chosen immediate successor.
|
|
1919
1924
|
if (remainingRefs.length > 0)
|
|
1920
1925
|
enqueueWork(store, target, "user-message", now, remainingRefs, { source: "controller", dedupeKey: `then-remainder:${wakeId}` });
|
|
1921
|
-
return { wakeId, attemptId, disposition: "submit" };
|
|
1926
|
+
return { wakeId, attemptId, disposition: "submit", presentation: taskNotificationPresentation(wake, store) };
|
|
1922
1927
|
});
|
|
1923
1928
|
}
|
|
1924
1929
|
settleLeaderNotification(taskId, attemptId, outcome, now, detail, failure) {
|
|
@@ -4,6 +4,7 @@ import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/
|
|
|
4
4
|
import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
|
|
5
5
|
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
6
6
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
7
|
+
import { globalMessagePresentation } from "../interaction/notificationPresentation.js";
|
|
7
8
|
/** Messages are the only queue. Host's atomic begin arbitrates native writers. */
|
|
8
9
|
export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
9
10
|
for (const role of store.listGlobalRoles()) {
|
|
@@ -119,10 +120,7 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
119
120
|
protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "submit-turn", nativeSessionId,
|
|
120
121
|
authority: { epoch: binding.authority.epoch, owner: "controller",
|
|
121
122
|
holderId: binding.authority.holderId },
|
|
122
|
-
run: { attemptId, boundedText:
|
|
123
|
-
`Yui Global Message: role=${role.name} message=${next.id}.`,
|
|
124
|
-
"Read your Session Context and the referenced Message in full, then act on its durable input."
|
|
125
|
-
].join("\n") }
|
|
123
|
+
run: { attemptId, boundedText: globalMessagePresentation(role.name, next.id, next) }
|
|
126
124
|
}
|
|
127
125
|
});
|
|
128
126
|
if (result.outcome === "rejected") {
|
|
@@ -10,6 +10,7 @@ import { cancelProviderRetry, providerRetryAttemptId, providerRetryPending, prov
|
|
|
10
10
|
import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
|
|
11
11
|
import { retryIntentBlocker } from "./providerRetryAdmission.js";
|
|
12
12
|
import { recordGlobalRuntimeAttention } from "./globalRuntimeAttention.js";
|
|
13
|
+
import { globalMessagePresentation, taskNotificationPresentation } from "../interaction/notificationPresentation.js";
|
|
13
14
|
/** Hooks in the existing Controller pass and nearest-deadline timer. SQLite's
|
|
14
15
|
* current Provider input is the only pending intent; nothing is queued here. */
|
|
15
16
|
export function createProviderRetryHooks(home, store, ports = {}) {
|
|
@@ -185,11 +186,12 @@ export function createProviderRetryHooks(home, store, ports = {}) {
|
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
else {
|
|
189
|
+
const wake = retry.input.kind === "wake" ? store.getTaskWake(retry.input.taskId, retry.input.wakeId) : null;
|
|
188
190
|
const original = retry.input.kind === "text" ? retry.input.text
|
|
189
191
|
: retry.input.kind === "wake"
|
|
190
|
-
?
|
|
192
|
+
? wake === null ? undefined : taskNotificationPresentation(wake, store)
|
|
191
193
|
: retry.input.kind === "message"
|
|
192
|
-
?
|
|
194
|
+
? globalMessagePresentation(retry.input.roleName, retry.input.messageId, store.listGlobalRoleMessages(retry.input.roleName).find(message => retry.input.kind === "message" && message.id === retry.input.messageId))
|
|
193
195
|
: undefined;
|
|
194
196
|
if (original === undefined) {
|
|
195
197
|
stop(set, "original-input-unavailable", at);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { sessionContextInstructions } from "../context/sessionBootstrapManifest.js";
|
|
4
5
|
import { isAgentAdapterId, supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
5
6
|
import { ownedArgumentsForAdapter, validateAgentAdvancedArguments, validateAgentBaseArguments } from "../agent/argumentPolicy.js";
|
|
6
7
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
@@ -434,10 +435,7 @@ function acpSessionBootstrap(input) {
|
|
|
434
435
|
])
|
|
435
436
|
]
|
|
436
437
|
: [
|
|
437
|
-
|
|
438
|
-
+ `${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). `
|
|
439
|
-
+ "Load each Skill and Role Profile by its manifest path before acting; Task "
|
|
440
|
-
+ "content is available only through the manifest's exact Context API."
|
|
438
|
+
sessionContextInstructions(input.sessionManifestPath, input.sessionManifestDigest)
|
|
441
439
|
];
|
|
442
440
|
const bootstrap = sections
|
|
443
441
|
.filter((value) => value !== undefined && value.trim().length > 0)
|
|
@@ -500,7 +498,7 @@ function codexThreadOptions(input, config) {
|
|
|
500
498
|
}
|
|
501
499
|
function codexSessionInstructions(input) {
|
|
502
500
|
if (input.sessionManifestPath !== undefined) {
|
|
503
|
-
return
|
|
501
|
+
return sessionContextInstructions(input.sessionManifestPath, input.sessionManifestDigest);
|
|
504
502
|
}
|
|
505
503
|
const instructions = [
|
|
506
504
|
input.developerInstructions,
|
|
@@ -9,7 +9,7 @@ import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironme
|
|
|
9
9
|
import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/agent.js";
|
|
10
10
|
import { NATIVE_AGENT_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment } from "../agent/launchEnvironment.js";
|
|
11
11
|
import { compileRoleSessionContext, roleSessionKind } from "../context/roleSessionContext.js";
|
|
12
|
-
import { materializeSessionBootstrap } from "../context/sessionBootstrapManifest.js";
|
|
12
|
+
import { materializeSessionBootstrap, sessionContextInstructions } from "../context/sessionBootstrapManifest.js";
|
|
13
13
|
import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
|
|
14
14
|
import { activeRoleAgentBinding } from "../role/role.js";
|
|
15
15
|
import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
|
|
@@ -358,7 +358,7 @@ export class FileRoleLaunchPlanner {
|
|
|
358
358
|
}
|
|
359
359
|
const sessionContext = {
|
|
360
360
|
...baseSessionContext,
|
|
361
|
-
developerInstructions:
|
|
361
|
+
developerInstructions: sessionContextInstructions(bootstrap.manifestPath, bootstrap.manifest.digest),
|
|
362
362
|
managedContextFile: bootstrap.manifestPath,
|
|
363
363
|
sessionManifestPath: bootstrap.manifestPath,
|
|
364
364
|
sessionManifestDigest: bootstrap.manifest.digest
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { parseWakeReason } from "../scheduler/wakeReason.js";
|
|
2
|
+
/** Optional producer-authored presentation, stored in the original text, not a
|
|
3
|
+
* parsed business outcome or a second fact store. Reject oversized summaries
|
|
4
|
+
* whole: clipping could remove a failure/authority qualifier. */
|
|
5
|
+
export function authoredNotificationSummary(text) {
|
|
6
|
+
const line = text?.trimStart().split(/\r?\n/u, 1)[0];
|
|
7
|
+
if (!line?.startsWith("Yui-Summary: "))
|
|
8
|
+
return undefined;
|
|
9
|
+
const summary = line.slice("Yui-Summary: ".length).trim();
|
|
10
|
+
return summary.length > 0 && Buffer.byteLength(summary) <= 512
|
|
11
|
+
&& !/[\u0000-\u001f\u007f]/u.test(summary) ? summary : undefined;
|
|
12
|
+
}
|
|
13
|
+
export function taskMessagePresentation(message, getRun) {
|
|
14
|
+
const run = message.resultRef?.type === "agent-run-result"
|
|
15
|
+
? getRun(message.taskId, message.resultRef.runId) : null;
|
|
16
|
+
const summary = authoredNotificationSummary(run?.result?.output ?? message.body);
|
|
17
|
+
const fact = run !== null
|
|
18
|
+
? `${run.purpose === "review" ? "Review" : "Execution"} result returned (${run.status}); disposition pending`
|
|
19
|
+
: message.kind === "role-result" ? "Role result returned; disposition pending"
|
|
20
|
+
: message.kind === "user" || message.kind === "operator" ? "Additional input received"
|
|
21
|
+
: "System message received";
|
|
22
|
+
return {
|
|
23
|
+
fact,
|
|
24
|
+
...(summary === undefined ? {} : {
|
|
25
|
+
summary, summarySource: run?.result?.output === undefined ? "message-body" : "agent-run-result"
|
|
26
|
+
})
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function wakeChangeLabels(reasons) {
|
|
30
|
+
return [...new Set(reasons.map(tag => {
|
|
31
|
+
const { kind } = parseWakeReason(tag);
|
|
32
|
+
switch (kind) {
|
|
33
|
+
case "operator-input":
|
|
34
|
+
case "user-message":
|
|
35
|
+
case "interrupt-then": return "Additional input received";
|
|
36
|
+
case "execution-started": return "Task execution enabled";
|
|
37
|
+
case "task-created": return "Task created";
|
|
38
|
+
case "task-reopened": return "Task reopened; check for scoped follow-up";
|
|
39
|
+
case "role-result": return "Role result returned; disposition pending";
|
|
40
|
+
case "review-failed": return "Review execution failed; inspect original failure";
|
|
41
|
+
case "agent-error":
|
|
42
|
+
case "leader-turn-failed":
|
|
43
|
+
case "role-turn-failed": return "Execution failure needs attention";
|
|
44
|
+
case "job-finished": return "Job ended; inspect outcome";
|
|
45
|
+
case "input-timeout": return "Input deadline reached; inspect recorded decision";
|
|
46
|
+
case "notification-retry": return "Notification retry after recorded correction";
|
|
47
|
+
default: return Buffer.byteLength(kind) <= 120 && !/[\u0000-\u001f\u007f]/u.test(kind)
|
|
48
|
+
? `Recorded update: ${kind}` : "Other recorded update; inspect wake";
|
|
49
|
+
}
|
|
50
|
+
}))];
|
|
51
|
+
}
|
|
52
|
+
export function wakeMessages(wake, messages) {
|
|
53
|
+
const ids = new Set(wake.refs?.filter(ref => ref.type === "message" && ref.taskId === wake.taskId).map(ref => ref.id));
|
|
54
|
+
const from = Date.parse(wake.fromCursor), to = Date.parse(wake.toCursor);
|
|
55
|
+
return messages.filter(message => ids.has(message.id)
|
|
56
|
+
|| Date.parse(message.createdAt) > from && Date.parse(message.createdAt) <= to);
|
|
57
|
+
}
|
|
58
|
+
export function taskNotificationPresentation(wake, store) {
|
|
59
|
+
const messages = wakeMessages(wake, store.listMessages(wake.taskId));
|
|
60
|
+
const labels = wakeChangeLabels(wake.reasons);
|
|
61
|
+
return [
|
|
62
|
+
`Yui Task notification: task=${wake.taskId} wake=${wake.id}.`,
|
|
63
|
+
`Changes: ${labels.slice(0, 4).join("; ")}${labels.length > 4 ? "; more in wake" : ""}.`,
|
|
64
|
+
...messages.slice(0, 4).map(message => {
|
|
65
|
+
const item = taskMessagePresentation(message, (task, run) => store.getRun(task, run));
|
|
66
|
+
return `- ${message.id} [${message.author.type}${message.author.type === "role" ? `:${message.author.roleName}` : ""}]: ${item.fact}.`
|
|
67
|
+
+ (item.summary === undefined ? "" : ` Author summary (${item.summarySource}): ${JSON.stringify(item.summary)}`);
|
|
68
|
+
}),
|
|
69
|
+
...(messages.length > 4 ? [`${messages.length - 4} more messages in the fixed wake.`] : []),
|
|
70
|
+
`Refresh: yui task context ${wake.taskId} --json`,
|
|
71
|
+
`Read inputs/results: yui task wake show ${wake.taskId} ${wake.id} --json`,
|
|
72
|
+
"Apply yui-leader (from the current Manifest); read relevant originals in full and advance authorized work."
|
|
73
|
+
].join("\n");
|
|
74
|
+
}
|
|
75
|
+
export function globalMessagePresentation(roleName, messageId, message) {
|
|
76
|
+
const summary = authoredNotificationSummary(message?.body);
|
|
77
|
+
return [
|
|
78
|
+
`Yui Global Message: role=${roleName} message=${messageId}.`,
|
|
79
|
+
`Changes: ${message?.author.type ?? "Recorded"} input received.`,
|
|
80
|
+
...(summary === undefined ? [] : [`Author summary (message-body): ${JSON.stringify(summary)}`]),
|
|
81
|
+
"Refresh Session Context using the current Manifest's loadCommand.",
|
|
82
|
+
`Read original: yui role message show ${roleName} ${messageId} --json`,
|
|
83
|
+
`Apply ${roleName === "operator" ? "yui-operator" : "yui-runtime"} from the current Manifest within existing authority.`
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
@@ -10,8 +10,7 @@ export function createOperatorBatchPresentation(batchId, items) {
|
|
|
10
10
|
receiptId: `operator-batch:${batchId}`,
|
|
11
11
|
text: [
|
|
12
12
|
"[Yui updates]",
|
|
13
|
-
"
|
|
14
|
-
"This notification is not a new execution assignment or authorization to replay prior operations.",
|
|
13
|
+
"Apply yui-operator from the current Manifest: inspect these originals and present material changes or decisions.",
|
|
15
14
|
...items.flatMap(renderItem)
|
|
16
15
|
].join("\n")
|
|
17
16
|
};
|
|
@@ -47,7 +47,7 @@ const definitions = [
|
|
|
47
47
|
name: "context.list", summary: "Discover one authorized record family with bounded summaries and exact references.",
|
|
48
48
|
effect: "query", requiredPermissions: ["task:read"], source: "listTaskContext",
|
|
49
49
|
inputSchema: object({ taskId: text, store: text, cursor: text, limit: { type: "integer" },
|
|
50
|
-
status: text, after: text, workItemId: text }, ["taskId", "store"]),
|
|
50
|
+
view: { type: "string", enum: ["current", "all"] }, status: text, after: text, workItemId: text }, ["taskId", "store"]),
|
|
51
51
|
outputSchema: { type: "object", required: ["items", "total", "complete", "nextCursor"] }
|
|
52
52
|
},
|
|
53
53
|
{
|
|
@@ -304,6 +304,7 @@ export function createBuiltinCapabilities(host, store, jobs, signal = () => unde
|
|
|
304
304
|
}, callerEnvironment(caller));
|
|
305
305
|
if (name === "context.list")
|
|
306
306
|
return listTaskContext(store, taskId, params.store, {
|
|
307
|
+
view: params.view,
|
|
307
308
|
cursor: params.cursor, limit: params.limit,
|
|
308
309
|
status: params.status, after: params.after,
|
|
309
310
|
workItemId: params.workItemId
|
|
@@ -26,7 +26,7 @@ function mapClaudeDisplayError(input) {
|
|
|
26
26
|
}
|
|
27
27
|
/** Provider-specific Codex failure recognition. */
|
|
28
28
|
export function mapCodexAgentError(input) {
|
|
29
|
-
return withTransientEvidence(mapCodexDisplayError(input), input);
|
|
29
|
+
return withTransientEvidence(mapCodexDisplayError(input), input, true);
|
|
30
30
|
}
|
|
31
31
|
function mapCodexDisplayError(input) {
|
|
32
32
|
const text = `${input.message}\n${input.raw}`;
|
|
@@ -208,7 +208,7 @@ function sessionUnavailable(code) {
|
|
|
208
208
|
}
|
|
209
209
|
/** Replay qualification is stricter than the readable, best-effort category.
|
|
210
210
|
* Never promote a number occurring in provider prose to submission safety. */
|
|
211
|
-
function structuredTransientError(input) {
|
|
211
|
+
function structuredTransientError(input, codex) {
|
|
212
212
|
if (/insufficient[_ -]quota|quota.{0,25}(exhaust|exceed)|billing[_ -]hard[_ -]limit|usageLimitExceeded|credits?.{0,20}(exhaust|insufficient)/iu.test(`${input.message}\n${input.raw}`))
|
|
213
213
|
return { category: "access", code: "provider.quota-exhausted", retryable: false };
|
|
214
214
|
const nodes = [];
|
|
@@ -228,7 +228,8 @@ function structuredTransientError(input) {
|
|
|
228
228
|
catch { /* Display classification may still use prose. */ }
|
|
229
229
|
const codes = nodes.flatMap(n => [n.code, n.type, n.error, n.codexErrorInfo]).filter(v => typeof v === "string");
|
|
230
230
|
if (codes.some(c => ["authentication_error", "permission_error", "invalid_api_key", "invalid_model",
|
|
231
|
-
"model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))
|
|
231
|
+
"model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))
|
|
232
|
+
|| codex && nodes.some(n => ["usageLimitExceeded", "unauthorized", "badRequest"].includes(String(n.codexErrorInfo)))) {
|
|
232
233
|
return { category: "access", code: "provider.access-denied", retryable: false };
|
|
233
234
|
}
|
|
234
235
|
const statuses = nodes.flatMap(n => [n.httpStatusCode, n.statusCode, n.status]).filter(v => typeof v === "number");
|
|
@@ -240,6 +241,9 @@ function structuredTransientError(input) {
|
|
|
240
241
|
if (statuses.includes(429) || codes.includes("rate_limit_error") || nativeCode === "rate_limit_error") {
|
|
241
242
|
result = recoverable("rate-limit", "provider.rate-limit");
|
|
242
243
|
}
|
|
244
|
+
else if (codex && nodes.some(n => n.codexErrorInfo === "serverOverloaded")) {
|
|
245
|
+
result = recoverable("availability", "provider.model-capacity");
|
|
246
|
+
}
|
|
243
247
|
else if (statuses.some(s => [500, 502, 503, 504].includes(s))
|
|
244
248
|
|| codes.some(c => ["server_error", "overloaded_error"].includes(c))
|
|
245
249
|
|| ["server_error", "overloaded_error"].includes(nativeCode)) {
|
|
@@ -277,8 +281,8 @@ function structuredTransientError(input) {
|
|
|
277
281
|
}
|
|
278
282
|
/** Add positive evidence without renaming established diagnostics or letting
|
|
279
283
|
* a transient nested cause override a known fatal Session/request failure. */
|
|
280
|
-
function withTransientEvidence(base, input) {
|
|
281
|
-
const evidence = structuredTransientError(input);
|
|
284
|
+
function withTransientEvidence(base, input, codex = false) {
|
|
285
|
+
const evidence = structuredTransientError(input, codex);
|
|
282
286
|
if (evidence === undefined)
|
|
283
287
|
return base;
|
|
284
288
|
const possiblyTransient = ["rate-limit", "availability", "transport", "unknown"].includes(base.category);
|
|
@@ -3,6 +3,44 @@ export const PROVIDER_RETRY_BUDGET_MS = 600_000;
|
|
|
3
3
|
export function providerRetryPending(binding) {
|
|
4
4
|
return binding?.retry?.status === "waiting" || binding?.retry?.status === "in-flight";
|
|
5
5
|
}
|
|
6
|
+
/** An explicit Run retry can carry exact failed-input proof even when the
|
|
7
|
+
* error did not qualify for automation. This does not grant automatic replay:
|
|
8
|
+
* the shared admission and native latest-Turn/quiescence checks still apply. */
|
|
9
|
+
export function providerRunRetry(binding, runId, at) {
|
|
10
|
+
const turn = binding.run;
|
|
11
|
+
const nativeSessionId = binding.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId;
|
|
12
|
+
if (turn?.runId !== runId || !["failed", "rejected", "deferred"].includes(turn.status)
|
|
13
|
+
|| binding.authority.owner !== "controller" || turn.authorityEpoch !== binding.authority.epoch
|
|
14
|
+
|| nativeSessionId === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const previous = binding.retry;
|
|
17
|
+
if (previous?.previousRunId === runId && previous.status !== "recovered"
|
|
18
|
+
&& previous.failedAttemptId === turn.attemptId
|
|
19
|
+
&& previous.authorityEpoch === binding.authority.epoch
|
|
20
|
+
&& previous.nativeSessionId === nativeSessionId)
|
|
21
|
+
return previous;
|
|
22
|
+
const failure = turn.failure;
|
|
23
|
+
if (turn.status !== "failed" || turn.nativeTurnId === undefined || turn.input === undefined
|
|
24
|
+
|| turn.retrySupported !== true || failure?.error.inputDisposition !== "accepted"
|
|
25
|
+
|| failure.error.sessionDisposition !== "recoverable")
|
|
26
|
+
return undefined;
|
|
27
|
+
// A manual attempt can cancel automation when its error changes character.
|
|
28
|
+
// Refresh its failed-Turn proof without granting the same input a new budget.
|
|
29
|
+
const continuing = previous?.status !== "recovered" && previous?.currentAttemptId === turn.attemptId
|
|
30
|
+
&& previous.authorityEpoch === binding.authority.epoch && previous.nativeSessionId === nativeSessionId;
|
|
31
|
+
return {
|
|
32
|
+
policyVersion: 1, chainId: continuing ? previous.chainId : turn.attemptId, status: "waiting",
|
|
33
|
+
input: continuing ? previous.input : turn.input, nativeSessionId, authorityEpoch: binding.authority.epoch,
|
|
34
|
+
attempts: continuing ? previous.attempts : 0,
|
|
35
|
+
transportSequence: continuing ? previous.transportSequence : 0,
|
|
36
|
+
startedAt: continuing ? previous.startedAt : iso(at), intentAt: iso(at),
|
|
37
|
+
deadline: continuing ? previous.deadline : iso(at + PROVIDER_RETRY_BUDGET_MS),
|
|
38
|
+
nextEligibleAt: iso(at), updatedAt: iso(at),
|
|
39
|
+
failedAttemptId: turn.attemptId, failureAttemptId: turn.attemptId,
|
|
40
|
+
failedNativeTurnId: turn.nativeTurnId, previousRunId: runId,
|
|
41
|
+
failureRef: failure.ref, error: failure.error, successorAutomatic: false
|
|
42
|
+
};
|
|
43
|
+
}
|
|
6
44
|
/** Called only in the transaction recording the exact failed/rejected input. */
|
|
7
45
|
export function recordProviderFailure(binding, input) {
|
|
8
46
|
let turn = binding.run;
|