@zq-silk/yui 0.11.3 → 0.12.1
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 +23 -4
- package/README.md +44 -22
- package/dist/cli/commandCatalog.js +13 -10
- package/dist/cli/interactionPolicy.js +5 -6
- package/dist/cli/updateCommand.js +3 -1
- package/dist/cli/updateOrchestrator.js +173 -28
- package/dist/cli/updatePorts.js +137 -8
- package/dist/cli/upgradeCommand.js +19 -9
- package/dist/cli.js +156 -57
- package/dist/commands/configCommands.js +13 -46
- package/dist/commands/executionAuditCommands.js +3 -2
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +234 -200
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +61 -13
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +55 -7
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +3 -3
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +39 -3
- package/dist/controller/controller.js +31 -27
- package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
- package/dist/controller/runtime.js +58 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeHookRunFence.js +19 -4
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/controller/structuredProviderObservation.js +20 -3
- package/dist/core/controllerClient.js +20 -2
- package/dist/core/controllerServer.js +1 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +52 -50
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +129 -45
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +69 -5
- package/dist/observability/executionAudit.js +5 -0
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/release/runtimeRelease.js +20 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/run/recoveryProjection.js +45 -6
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/agentHost.js +159 -85
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/conversationSwitch.js +277 -0
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/launchBroker.js +12 -0
- package/dist/runtime/processExitOutbox.js +88 -0
- package/dist/runtime/providerRuntimeIdentity.js +29 -1
- package/dist/runtime/runtimeHealthPolicy.js +5 -5
- package/dist/runtime/runtimeObservation.js +28 -0
- package/dist/runtime/runtimeProjection.js +22 -32
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/runtime/tmuxAdapters.js +4 -1
- package/dist/scheduler/activeRoleRunDelivery.js +34 -199
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +42 -138
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +53 -31
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +66 -23
- package/dist/storage/sqliteStore.js +46 -23
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/homeClassification.js +52 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -58
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +7 -1
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +60 -20
- package/skills/yui-operator/SKILL.md +13 -4
- package/skills/yui-reviewer/SKILL.md +35 -11
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/lifecycle/taskRoleSessionReset.js +0 -126
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { resolveContextBudget } from "../config/yuiConfig.js";
|
|
2
|
-
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
3
|
-
export function evaluateSessionContextBudget(events, session, configured) {
|
|
4
|
-
const budget = resolveContextBudget(configured);
|
|
5
|
-
const peak = observedPeakInputTokens(events, session);
|
|
6
|
-
const state = peak >= budget.hardTokens
|
|
7
|
-
? "hard"
|
|
8
|
-
: peak >= budget.softTokens
|
|
9
|
-
? "soft"
|
|
10
|
-
: "within";
|
|
11
|
-
return Object.freeze({ state, peakTokens: peak, budget });
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Largest per-request input peak observed for one Session generation. Usage
|
|
15
|
-
* observations carry cumulative session totals; consecutive deltas bound the
|
|
16
|
-
* per-request input of the window between them. The first observation for a
|
|
17
|
-
* generation is treated as one full request so a long-lived resumed Session
|
|
18
|
-
* that was only sampled late still counts its inherited context.
|
|
19
|
-
*/
|
|
20
|
-
export function observedPeakInputTokens(events, session) {
|
|
21
|
-
let previous = 0;
|
|
22
|
-
let peak = 0;
|
|
23
|
-
for (const event of events) {
|
|
24
|
-
const observation = runtimeObservationFromTaskEvent(event);
|
|
25
|
-
if (observation === null)
|
|
26
|
-
continue;
|
|
27
|
-
if (!matchesSession(observation.fence, session))
|
|
28
|
-
continue;
|
|
29
|
-
const usage = usageTotal(observation.payload.usage);
|
|
30
|
-
if (usage === null)
|
|
31
|
-
continue;
|
|
32
|
-
const delta = Math.max(0, usage - previous);
|
|
33
|
-
if (delta > peak)
|
|
34
|
-
peak = delta;
|
|
35
|
-
previous = usage;
|
|
36
|
-
}
|
|
37
|
-
return peak;
|
|
38
|
-
}
|
|
39
|
-
function matchesSession(fence, session) {
|
|
40
|
-
if (fence.taskId !== undefined && fence.taskId !== session.taskId)
|
|
41
|
-
return false;
|
|
42
|
-
if (fence.roleName !== session.roleName)
|
|
43
|
-
return false;
|
|
44
|
-
if (session.nativeSessionId !== undefined
|
|
45
|
-
&& fence.nativeSessionId !== undefined
|
|
46
|
-
&& fence.nativeSessionId !== session.nativeSessionId) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
if (session.launchId !== undefined
|
|
50
|
-
&& fence.launchId !== undefined
|
|
51
|
-
&& fence.launchId !== session.launchId) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
function usageTotal(usage) {
|
|
57
|
-
if (typeof usage !== "object" || usage === null || Array.isArray(usage))
|
|
58
|
-
return null;
|
|
59
|
-
const record = usage;
|
|
60
|
-
const inputTokens = integer(record.inputTokens);
|
|
61
|
-
if (inputTokens === null)
|
|
62
|
-
return null;
|
|
63
|
-
// RuntimeUsageSnapshot.inputTokens is the normalized processed input total.
|
|
64
|
-
// cachedInputTokens is an informational breakdown of that total, matching
|
|
65
|
-
// runtimeProjection and the Driver documentation; adding it again would
|
|
66
|
-
// fabricate context pressure and trigger premature Session rollover.
|
|
67
|
-
return inputTokens;
|
|
68
|
-
}
|
|
69
|
-
function integer(value) {
|
|
70
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
71
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
2
|
-
import { validateRoleSessionSet } from "../executor/agentExecutor.js";
|
|
3
|
-
import { createTaskEvent } from "../event/taskEvent.js";
|
|
4
|
-
import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
5
|
-
/**
|
|
6
|
-
* Issue 04 (context token budget): retires one native Session generation that
|
|
7
|
-
* crossed the hard context budget so the next Leader wake starts a fresh
|
|
8
|
-
* generation. This is a controlled rollover, not a failure: the durable Task
|
|
9
|
-
* records are the checkpoint, the bounded context snapshot re-establishes
|
|
10
|
-
* working context for the new generation, and the retired Session is kept in
|
|
11
|
-
* history as `stopped` with an auditable event. The Controller separately
|
|
12
|
-
* owns verified process cleanup through the runtime cleanup lane.
|
|
13
|
-
*/
|
|
14
|
-
export const CONTEXT_BUDGET_ROLLOVER_REASON = "context-budget-hard-limit";
|
|
15
|
-
export function rolloverTaskRoleSessionForContextBudget(store, taskId, roleName, evidence, now) {
|
|
16
|
-
const task = store.getTask(taskId);
|
|
17
|
-
if (task === null)
|
|
18
|
-
throw new Error(`Task not found: ${taskId}.`);
|
|
19
|
-
if (task.status !== "active") {
|
|
20
|
-
throw new Error(`Task is not active: ${task.id}/${task.status}.`);
|
|
21
|
-
}
|
|
22
|
-
const role = store.getRole(task.id, roleName);
|
|
23
|
-
if (role === null)
|
|
24
|
-
throw new Error(`Role not found: ${task.id}/${roleName}.`);
|
|
25
|
-
const set = store.getTaskRoleSessionSet(task.id, role.name);
|
|
26
|
-
const current = set?.sessions[set.activeAgentId];
|
|
27
|
-
if (set === null || set === undefined || current === undefined) {
|
|
28
|
-
// No live generation to retire; the caller can launch fresh directly.
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
const timestamp = now.toISOString();
|
|
32
|
-
const reason = `${CONTEXT_BUDGET_ROLLOVER_REASON} (peak ${evidence.peakTokens} >= hard ${evidence.hardTokens} tokens)`;
|
|
33
|
-
enqueueWork(store, runtimeLifecycleTarget({ scope: "task", taskId: task.id, roleName: role.name }), RUNTIME_CLEANUP_REQUIRED_REASON, now, [{ type: "task", id: task.id }]);
|
|
34
|
-
const retired = retireStoppedTaskRoleSession(set, timestamp);
|
|
35
|
-
store.saveTaskRoleSessionSet(retired);
|
|
36
|
-
const eventId = store.nextEventId(task.id);
|
|
37
|
-
store.saveEvent(task.id, createTaskEvent(eventId, task.id, "runtime.role-session-reset", {
|
|
38
|
-
roleName: role.name,
|
|
39
|
-
reason,
|
|
40
|
-
peakTokens: String(evidence.peakTokens),
|
|
41
|
-
hardTokens: String(evidence.hardTokens),
|
|
42
|
-
...(current.nativeSessionId === undefined
|
|
43
|
-
? {}
|
|
44
|
-
: { nativeSessionId: current.nativeSessionId }),
|
|
45
|
-
...(current.launchId === undefined ? {} : { launchId: current.launchId })
|
|
46
|
-
}, now));
|
|
47
|
-
return Object.freeze({
|
|
48
|
-
taskId: task.id,
|
|
49
|
-
roleName: role.name,
|
|
50
|
-
eventId,
|
|
51
|
-
...(current.nativeSessionId === undefined
|
|
52
|
-
? {}
|
|
53
|
-
: { retiredNativeSessionId: current.nativeSessionId }),
|
|
54
|
-
...(current.launchId === undefined ? {} : { retiredLaunchId: current.launchId })
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Retires the current generation to history as `stopped` (not `broken`:
|
|
59
|
-
* a budget rollover is not a Session failure) and clears the Turn fence so
|
|
60
|
-
* the next launch is a fresh generation.
|
|
61
|
-
*/
|
|
62
|
-
function retireStoppedTaskRoleSession(set, timestamp) {
|
|
63
|
-
validateRoleSessionSet(set);
|
|
64
|
-
const current = set.sessions[set.activeAgentId];
|
|
65
|
-
const sessions = { ...set.sessions };
|
|
66
|
-
delete sessions[set.activeAgentId];
|
|
67
|
-
const history = current === undefined
|
|
68
|
-
? set.history
|
|
69
|
-
: [
|
|
70
|
-
...(set.history ?? []),
|
|
71
|
-
{ ...current, status: "stopped", updatedAt: timestamp }
|
|
72
|
-
];
|
|
73
|
-
return validateRoleSessionSet({
|
|
74
|
-
...set,
|
|
75
|
-
sessions,
|
|
76
|
-
...(history === undefined ? {} : { history }),
|
|
77
|
-
inFlight: null,
|
|
78
|
-
providerBinding: null,
|
|
79
|
-
updatedAt: timestamp
|
|
80
|
-
});
|
|
81
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
2
|
-
import { resetTaskRoleSession } from "../executor/agentExecutor.js";
|
|
3
|
-
import { createTaskEvent } from "../event/taskEvent.js";
|
|
4
|
-
import { createTaskMessage } from "../message/message.js";
|
|
5
|
-
import { updateRoleStatus } from "../role/role.js";
|
|
6
|
-
import { createLeaderRecoveryNotification } from "../scheduler/operatorNotification.js";
|
|
7
|
-
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
8
|
-
import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
9
|
-
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
10
|
-
import { updateWorkItemStatus, workItemOwnsUnresolvedExecutionLane } from "../workItem/workItem.js";
|
|
11
|
-
import { terminalizeExactTaskRun } from "./exactRunTerminalization.js";
|
|
12
|
-
/**
|
|
13
|
-
* Resets the current native generation using Yui's own persisted identities.
|
|
14
|
-
* The caller supplies intent only; the Controller verifies process cleanup.
|
|
15
|
-
*/
|
|
16
|
-
export function resetTaskRoleSessionGeneration(store, taskId, roleName, reason, now) {
|
|
17
|
-
const task = store.getTask(requiredIdentity(taskId, "Task id"));
|
|
18
|
-
if (task === null)
|
|
19
|
-
throw new Error(`Task not found: ${taskId}.`);
|
|
20
|
-
if (task.status !== "active")
|
|
21
|
-
throw new Error(`Task is not active: ${task.id}/${task.status}.`);
|
|
22
|
-
const normalizedRole = requiredIdentity(roleName, "Role name");
|
|
23
|
-
const role = store.getRole(task.id, normalizedRole);
|
|
24
|
-
if (role === null)
|
|
25
|
-
throw new Error(`Role not found: ${task.id}/${normalizedRole}.`);
|
|
26
|
-
const summary = `Reset native Session: ${requiredText(reason, "Reset reason")}`;
|
|
27
|
-
let sessions = store.getTaskRoleSessionSet(task.id, role.name);
|
|
28
|
-
const current = sessions?.sessions[sessions.activeAgentId];
|
|
29
|
-
const activeRun = store.getActiveAgentRun(task.id, role.name);
|
|
30
|
-
if (activeRun !== null) {
|
|
31
|
-
const receiptId = sessions?.inFlight?.runId === activeRun.id
|
|
32
|
-
? sessions.inFlight.receiptId
|
|
33
|
-
: formatAgentRunReceiptId(task.id, activeRun.id);
|
|
34
|
-
const terminal = terminalizeExactTaskRun(store, {
|
|
35
|
-
taskId: task.id,
|
|
36
|
-
roleName: role.name,
|
|
37
|
-
agentId: activeRun.effective.agentId,
|
|
38
|
-
runId: activeRun.id,
|
|
39
|
-
receiptId,
|
|
40
|
-
...(current === undefined ? {} : {
|
|
41
|
-
nativeSessionId: current.nativeSessionId,
|
|
42
|
-
...(current.launchId === undefined ? {} : { launchId: current.launchId })
|
|
43
|
-
}),
|
|
44
|
-
outcome: { status: "failed", summary }
|
|
45
|
-
}, now);
|
|
46
|
-
if (terminal.disposition !== "applied" || terminal.run === null) {
|
|
47
|
-
throw new Error(`Task Role reset lost its exact Run fence: ${terminal.reason ?? "obsolete"}.`);
|
|
48
|
-
}
|
|
49
|
-
if (activeRun.purpose === "execution" && activeRun.workItemId !== undefined) {
|
|
50
|
-
const item = store.getWorkItem(task.id, activeRun.workItemId);
|
|
51
|
-
if (item !== null
|
|
52
|
-
&& !["completed", "failed", "retired"].includes(item.status)
|
|
53
|
-
&& !workItemOwnsUnresolvedExecutionLane(item, activeRun.executionGroupId, activeRun.executionLaneId)) {
|
|
54
|
-
store.saveWorkItem(task.id, updateWorkItemStatus(item, "failed", now, summary));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
enqueueWork(store, runtimeLifecycleTarget({
|
|
59
|
-
scope: "task",
|
|
60
|
-
taskId: task.id,
|
|
61
|
-
roleName: role.name
|
|
62
|
-
}), RUNTIME_CLEANUP_REQUIRED_REASON, now, [{ type: "task", id: task.id }]);
|
|
63
|
-
sessions = store.getTaskRoleSessionSet(task.id, role.name);
|
|
64
|
-
if (sessions !== null)
|
|
65
|
-
store.saveTaskRoleSessionSet(resetTaskRoleSession(sessions, now));
|
|
66
|
-
const updatedRole = store.getRole(task.id, role.name);
|
|
67
|
-
store.saveRole(task.id, updateRoleStatus(updatedRole, role.name === "leader" ? "failed" : "idle", now));
|
|
68
|
-
const message = createTaskMessage(store.nextMessageId(task.id), task.id, summary, "system", { type: "system" }, now, activeRun === null ? {} : {
|
|
69
|
-
runId: activeRun.id,
|
|
70
|
-
...(activeRun.workItemId === undefined ? {} : { workItemId: activeRun.workItemId })
|
|
71
|
-
});
|
|
72
|
-
store.saveMessage(task.id, message);
|
|
73
|
-
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "runtime.role-session-reset", {
|
|
74
|
-
roleName: role.name,
|
|
75
|
-
reason: summary,
|
|
76
|
-
...(activeRun === null ? {} : { runId: activeRun.id }),
|
|
77
|
-
...(current?.nativeSessionId === undefined
|
|
78
|
-
? {}
|
|
79
|
-
: { nativeSessionId: current.nativeSessionId })
|
|
80
|
-
}, now));
|
|
81
|
-
if (role.name === "leader") {
|
|
82
|
-
const nativeSessionId = current?.nativeSessionId ?? `reset-${task.id}`;
|
|
83
|
-
store.saveLeaderFailure(recordLeaderFailure(task.id, nativeSessionId, summary, now, store.getLeaderFailure(task.id)));
|
|
84
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(task.id, summary, now, store.getOperatorNotification(task.id)));
|
|
85
|
-
enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
|
|
86
|
-
{ type: "task", id: task.id },
|
|
87
|
-
{ type: "message", taskId: task.id, id: message.id },
|
|
88
|
-
...(activeRun === null ? [] : [{
|
|
89
|
-
type: "run",
|
|
90
|
-
taskId: task.id,
|
|
91
|
-
id: activeRun.id
|
|
92
|
-
}])
|
|
93
|
-
]);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
|
|
97
|
-
{ type: "message", taskId: task.id, id: message.id },
|
|
98
|
-
...(activeRun === null ? [] : [{
|
|
99
|
-
type: "run",
|
|
100
|
-
taskId: task.id,
|
|
101
|
-
id: activeRun.id
|
|
102
|
-
}])
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
taskId: task.id,
|
|
107
|
-
roleName: role.name,
|
|
108
|
-
run: activeRun === null ? null : store.getAgentRun(task.id, activeRun.id),
|
|
109
|
-
...(current === undefined ? {} : { nativeSessionId: current.nativeSessionId })
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function requiredIdentity(value, label) {
|
|
113
|
-
const normalized = requiredText(value, label);
|
|
114
|
-
if (["__proto__", "prototype", "constructor", ".", ".."].includes(normalized)
|
|
115
|
-
|| /[\/\\\0]/u.test(normalized))
|
|
116
|
-
throw new Error(`${label} is invalid.`);
|
|
117
|
-
return normalized;
|
|
118
|
-
}
|
|
119
|
-
function requiredText(value, label) {
|
|
120
|
-
if (typeof value !== "string" || value.includes("\0"))
|
|
121
|
-
throw new Error(`${label} is invalid.`);
|
|
122
|
-
const normalized = value.trim();
|
|
123
|
-
if (normalized.length === 0)
|
|
124
|
-
throw new Error(`${label} is required.`);
|
|
125
|
-
return normalized;
|
|
126
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export const CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION = 1;
|
|
2
|
-
export function createLeaderRecoveryNotification(taskId, message, now, existing) {
|
|
3
|
-
const timestamp = now.toISOString();
|
|
4
|
-
return {
|
|
5
|
-
schemaVersion: CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION,
|
|
6
|
-
taskId: requiredText(taskId, "Task id"),
|
|
7
|
-
type: "leader-recovery-failed",
|
|
8
|
-
message: requiredText(message, "Operator notification message"),
|
|
9
|
-
createdAt: existing?.type === "leader-recovery-failed"
|
|
10
|
-
? existing.createdAt
|
|
11
|
-
: timestamp,
|
|
12
|
-
updatedAt: timestamp
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
export function createTaskTerminalNotification(taskId, status, by, summary, now) {
|
|
16
|
-
const timestamp = now.toISOString();
|
|
17
|
-
return {
|
|
18
|
-
schemaVersion: CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION,
|
|
19
|
-
taskId: requiredText(taskId, "Task id"),
|
|
20
|
-
type: "task-terminal",
|
|
21
|
-
status,
|
|
22
|
-
by,
|
|
23
|
-
summary: requiredText(summary, "Task terminal summary"),
|
|
24
|
-
createdAt: timestamp,
|
|
25
|
-
updatedAt: timestamp
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
export function createLeaderStallNotification(taskId, runId, progressAt, evidenceKey, now, existing) {
|
|
29
|
-
const timestamp = now.toISOString();
|
|
30
|
-
const previous = existing?.type === "leader-stalled"
|
|
31
|
-
&& existing.runId === runId
|
|
32
|
-
&& existing.progressAt === progressAt
|
|
33
|
-
? existing
|
|
34
|
-
: undefined;
|
|
35
|
-
return {
|
|
36
|
-
schemaVersion: CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION,
|
|
37
|
-
taskId: requiredText(taskId, "Task id"),
|
|
38
|
-
type: "leader-stalled",
|
|
39
|
-
message: requiredText([
|
|
40
|
-
`Leader Run ${runId} is truly stalled for Task ${taskId}.`,
|
|
41
|
-
`lastProgressAt=${progressAt}`,
|
|
42
|
-
`classification=truly-stalled`,
|
|
43
|
-
`evidence=${evidenceKey}`,
|
|
44
|
-
"Inspect the Task context and decide whether to continue, reset, retry, change Agent, or request user input; Controller performed no automatic action."
|
|
45
|
-
].join(" "), "Operator notification message"),
|
|
46
|
-
runId: requiredText(runId, "Leader Run id"),
|
|
47
|
-
progressAt: requiredText(progressAt, "Leader progress timestamp"),
|
|
48
|
-
classification: "truly-stalled",
|
|
49
|
-
evidenceKey: requiredText(evidenceKey, "Leader stall evidence"),
|
|
50
|
-
createdAt: previous?.createdAt ?? timestamp,
|
|
51
|
-
updatedAt: timestamp
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function requiredText(value, label) {
|
|
55
|
-
const normalized = value.trim();
|
|
56
|
-
if (normalized.length === 0)
|
|
57
|
-
throw new Error(`${label} is required.`);
|
|
58
|
-
return normalized;
|
|
59
|
-
}
|