@zq-silk/yui 2.7.1 → 2.8.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 +8 -7
- package/dist/cli/updateCommand.js +3 -0
- package/dist/commands/configCommands.js +1 -0
- package/dist/commands/globalRoleCommands.js +42 -35
- package/dist/commands/inboxCommands.js +60 -40
- package/dist/commands/taskCommands.js +7 -3
- package/dist/commands/taskRoleRuntimeStatus.js +2 -0
- package/dist/context/runContextPack.js +48 -3
- package/dist/context/taskContext.js +36 -12
- package/dist/controlPlaneCli.js +46 -12
- package/dist/controller/controller.js +5 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +9 -4
- package/dist/controller/globalInputDelivery.js +17 -5
- package/dist/controller/globalRuntimeAttention.js +135 -5
- package/dist/controller/runtime.js +4 -0
- package/dist/controller/structuredProviderObservation.js +51 -15
- package/dist/controller/taskHostTerminalRecovery.js +33 -0
- package/dist/executor/fileRoleLaunchPlanner.js +4 -9
- package/dist/message/inboxReceipt.js +30 -0
- package/dist/message/taskInboxOwnership.js +25 -3
- package/dist/models/modelManagement.js +469 -0
- package/dist/models/modelManagementConfig.js +46 -0
- package/dist/models/modelManagementFiles.js +90 -0
- package/dist/models/modelManagementHttp.js +90 -0
- package/dist/models/modelManagementTypes.js +1 -0
- package/dist/output/boundedRead.js +43 -14
- package/dist/runtime/agentEndpointIdentity.js +8 -4
- package/dist/runtime/agentEndpointOwnership.js +5 -6
- package/dist/runtime/agentHost.js +62 -10
- package/dist/runtime/agentHostImplementation.js +18 -0
- package/dist/runtime/managedCaller.js +25 -1
- package/dist/runtime/managedIdentity.js +1 -1
- package/dist/runtime/providerRetry.js +19 -1
- package/dist/runtime/sessionCaller.js +53 -0
- package/dist/storage/contextRecords.js +28 -0
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/sqliteStore.js +9 -5
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +4 -0
- package/dist/task/completionReadiness.js +3 -3
- package/dist/web/assets/assetManifest.js +28 -2
- package/dist/web/assets/client/app/manage.js +6 -0
- package/dist/web/assets/client/app/models.js +20 -0
- package/dist/web/assets/client/app/settings.js +41 -9
- package/dist/web/assets/client/i18n/en.js +180 -2
- package/dist/web/assets/client/i18n/zh-CN.js +178 -2
- package/dist/web/assets/client/index.js +8 -0
- package/dist/web/assets/client/layout/manageShell.js +94 -0
- package/dist/web/assets/client/lib/api.js +13 -1
- package/dist/web/assets/client/lib/i18n.js +5 -3
- package/dist/web/assets/client/views/dock/conversation.js +231 -80
- package/dist/web/assets/client/views/models.js +777 -0
- package/dist/web/assets/client/views/skills.js +22 -15
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shared/manageNav.js +13 -0
- package/dist/web/assets/shell/manage.js +54 -0
- package/dist/web/assets/shell/models.js +7 -0
- package/dist/web/assets/shell/settings.js +1 -35
- package/dist/web/assets/shell/settingsContent.js +30 -0
- package/dist/web/assets/shell/sidebar.js +1 -0
- package/dist/web/assets/styles/index.js +6 -0
- package/dist/web/assets/styles/views/conversation.js +133 -0
- package/dist/web/assets/styles/views/manage.js +71 -0
- package/dist/web/assets/styles/views/models.js +129 -0
- package/dist/web/assets/styles/views/settings.js +28 -14
- package/dist/web/assets/styles/views/sidebar.js +1 -0
- package/dist/web/webServer.js +59 -9
- package/docs/cli-information-contract.md +42 -11
- package/docs/cli-information-contract.zh-CN.md +28 -7
- package/docs/managed-turn-and-session-runtime.md +47 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +32 -0
- package/docs/release-workflow.md +58 -7
- package/docs/release-workflow.zh-CN.md +39 -4
- package/docs/storage-baseline.md +6 -1
- package/docs/storage-baseline.zh-CN.md +4 -1
- package/docs/testing/review-evaluation.md +148 -0
- package/docs/testing/review-evaluation.zh-CN.md +117 -0
- package/docs/testing/verification-levels.md +6 -2
- package/docs/testing/verification-levels.zh-CN.md +3 -0
- package/package.json +1 -1
- package/skills/yui-leader/VERSION +1 -1
- package/skills/yui-leader/references/execution.md +29 -0
- package/skills/yui-leader/references/replicated-execution.md +7 -0
- package/skills/yui-operator/SKILL.md +5 -1
- package/skills/yui-operator/VERSION +1 -1
- package/skills/yui-reviewer/SKILL.md +35 -0
- package/skills/yui-reviewer/VERSION +1 -1
- package/skills/yui-runtime/SKILL.md +53 -7
- package/skills/yui-runtime/VERSION +1 -1
- package/skills/yui-runtime/references/recovery.md +24 -5
|
@@ -4,14 +4,14 @@ import { taskNotFound, usageError } from "../errors/cliError.js";
|
|
|
4
4
|
import { expandTaskMessageResult } from "../message/message.js";
|
|
5
5
|
import { resolveManagedTaskReader } from "../runtime/managedCaller.js";
|
|
6
6
|
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
7
|
-
import {
|
|
7
|
+
import { providerRetrySummary } from "../runtime/providerRetry.js";
|
|
8
8
|
import { projectTaskRemoteDeliveryFromStore } from "../task/remoteDeliveryService.js";
|
|
9
9
|
import { retiredTaskRecordIds, TASK_RECORD_RETIRED_EVENT } from "../task/taskRecordRetirement.js";
|
|
10
10
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
11
11
|
import { contextContentDigest } from "./contextSnapshot.js";
|
|
12
12
|
import { buildRunContextPack } from "./runContextPack.js";
|
|
13
13
|
import { sourceRunContextValue } from "./sourceRunContext.js";
|
|
14
|
-
import { boundedDocument, recordPage } from "../output/boundedRead.js";
|
|
14
|
+
import { boundedDocument, fitsReadBudget, summaryText, recordPage } from "../output/boundedRead.js";
|
|
15
15
|
const MAX_RECORDS = 64;
|
|
16
16
|
const MAX_VALUE_BYTES = 2048;
|
|
17
17
|
const MAX_PAGE_BYTES = 24 * 1024;
|
|
@@ -41,13 +41,13 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
41
41
|
if (entry.ref.store === "run")
|
|
42
42
|
runtimeEvents ??= reader.listEventsByType(taskId, ["runtime.agent-error"]);
|
|
43
43
|
const valueBytes = Buffer.byteLength(JSON.stringify(entry.value));
|
|
44
|
-
const content = valueBytes > MAX_VALUE_BYTES || ["task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
|
|
44
|
+
const content = valueBytes > MAX_VALUE_BYTES || ["role", "task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
|
|
45
45
|
? { ref: entry.ref, summary: summarize(entry.value), omitted: true }
|
|
46
46
|
: { ...entry, omitted: false };
|
|
47
47
|
const record = { ...content, ...(entry.ref.store === "run" ? {
|
|
48
48
|
execution: runExecutionObservation(entry.value, reader.getTaskRoleSessionSet(taskId, entry.value.roleName)?.providerBinding, runtimeEvents)
|
|
49
49
|
} : {}), ...(entry.ref.store === "role" ? {
|
|
50
|
-
providerRetry:
|
|
50
|
+
providerRetry: providerRetrySummary(reader.getTaskRoleSessionSet(taskId, entry.value.name)?.providerBinding)
|
|
51
51
|
} : {}) };
|
|
52
52
|
const size = Buffer.byteLength(JSON.stringify(record));
|
|
53
53
|
if (bytes + size > MAX_PAGE_BYTES)
|
|
@@ -55,7 +55,7 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
55
55
|
records.push(record);
|
|
56
56
|
bytes += size;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
const result = {
|
|
59
59
|
taskId, coreCursor: encode(cursor), throughCursor: encode(cursor),
|
|
60
60
|
view: "current",
|
|
61
61
|
collections: Object.entries(sources.reduce((counts, source) => {
|
|
@@ -79,6 +79,18 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
79
79
|
limits: { records: MAX_RECORDS, valueBytes: MAX_VALUE_BYTES, pageBytes: 32 * 1024, recordBytes: MAX_PAGE_BYTES,
|
|
80
80
|
attentionRefsPerCategory: MAX_ATTENTION_REFS, attentionRefBytesPerCategory: MAX_VALUE_BYTES }
|
|
81
81
|
};
|
|
82
|
+
// Account for nested retry facts, collection counts, commands and JSON
|
|
83
|
+
// structure together. Exact omitted records stay discoverable by family.
|
|
84
|
+
while (!fitsReadBudget(result) && records.length > 0) {
|
|
85
|
+
records.pop();
|
|
86
|
+
result.collections.forEach(collection => {
|
|
87
|
+
collection.returned = records.filter(record => record.ref.store === collection.store).length;
|
|
88
|
+
});
|
|
89
|
+
result.omitted = { records: count - records.length, values: records.filter(record => record.omitted).length };
|
|
90
|
+
}
|
|
91
|
+
if (!fitsReadBudget(result))
|
|
92
|
+
throw usageError("Context identities exceed the response budget; use scoped discovery.");
|
|
93
|
+
return result;
|
|
82
94
|
});
|
|
83
95
|
}
|
|
84
96
|
function summarizeAttention(store, taskId, allow) {
|
|
@@ -93,7 +105,8 @@ function summarizeAttention(store, taskId, allow) {
|
|
|
93
105
|
["messages", "task-message", undefined]
|
|
94
106
|
]) {
|
|
95
107
|
const page = store.queryContextRecords(taskId, {
|
|
96
|
-
family, statuses, descending: category === "messages",
|
|
108
|
+
family, statuses, descending: category === "messages",
|
|
109
|
+
unhandledMessages: category === "messages", ids: allowedIds(allow, family), limit: MAX_ATTENTION_REFS
|
|
97
110
|
});
|
|
98
111
|
const summary = attention[category];
|
|
99
112
|
summary.count = page.count;
|
|
@@ -262,11 +275,11 @@ export async function withContextObservations(core, providers, timeoutMs = 250)
|
|
|
262
275
|
if (!["known", "partial", "unknown"].includes(value.coverage)
|
|
263
276
|
|| Buffer.byteLength(JSON.stringify(value)) > MAX_VALUE_BYTES)
|
|
264
277
|
throw new Error("unbounded observation");
|
|
265
|
-
return { source: provider.source
|
|
278
|
+
return { source: summaryText(provider.source, 200), observedAt: new Date().toISOString(),
|
|
266
279
|
coverage: value.coverage, status: "available", value: JSON.parse(JSON.stringify(value.value)) };
|
|
267
280
|
}
|
|
268
281
|
catch {
|
|
269
|
-
return { source: provider.source
|
|
282
|
+
return { source: summaryText(provider.source, 200), observedAt: new Date().toISOString(),
|
|
270
283
|
coverage: "unknown", status: "unavailable" };
|
|
271
284
|
}
|
|
272
285
|
finally {
|
|
@@ -275,7 +288,18 @@ export async function withContextObservations(core, providers, timeoutMs = 250)
|
|
|
275
288
|
controller.abort();
|
|
276
289
|
}
|
|
277
290
|
}));
|
|
278
|
-
|
|
291
|
+
const result = { ...core, observations: [] };
|
|
292
|
+
for (const observation of observations) {
|
|
293
|
+
result.observations.push(observation);
|
|
294
|
+
if (!fitsReadBudget(result)) {
|
|
295
|
+
result.observations.pop();
|
|
296
|
+
const { value: _value, ...identity } = observation;
|
|
297
|
+
result.observations.push({ ...identity, omitted: true });
|
|
298
|
+
if (!fitsReadBudget(result))
|
|
299
|
+
result.observations.pop();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return { ...result, omittedObservations: observations.length - result.observations.length };
|
|
279
303
|
}
|
|
280
304
|
/** Shared current/historical Session read identity; discovery may only narrow it. */
|
|
281
305
|
export function resolveContextReader(store, environment) {
|
|
@@ -513,7 +537,7 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
|
|
|
513
537
|
}
|
|
514
538
|
if (!current)
|
|
515
539
|
query("change-set");
|
|
516
|
-
query("task-message", { descending: true });
|
|
540
|
+
query("task-message", { descending: true, unhandledMessages: current });
|
|
517
541
|
query("run", { descending: true, ...(current ? { statuses: ["active"] } : {}) });
|
|
518
542
|
for (const id of allowedIds(allow, "source-turn") ?? []) {
|
|
519
543
|
const run = store.getRun(taskId, id);
|
|
@@ -585,8 +609,8 @@ function freeze(value) {
|
|
|
585
609
|
}
|
|
586
610
|
function summarize(value) {
|
|
587
611
|
const record = value;
|
|
588
|
-
return ["title", "scope", "expiresWhen", "version", "objective", "question", "summary", "body", "status", "type"]
|
|
612
|
+
return summaryText(["title", "name", "activeAgentId", "scope", "expiresWhen", "version", "objective", "question", "summary", "body", "status", "type"]
|
|
589
613
|
.flatMap((key) => typeof record[key] === "string" || (key === "version" && typeof record[key] === "number")
|
|
590
614
|
? [`${key}: ${record[key]}`] : [])
|
|
591
|
-
.join("; ")
|
|
615
|
+
.join("; "));
|
|
592
616
|
}
|
package/dist/controlPlaneCli.js
CHANGED
|
@@ -22,7 +22,10 @@ import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/i
|
|
|
22
22
|
import { routeInvocation } from "./cli/invocationRouter.js";
|
|
23
23
|
import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
|
|
24
24
|
import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
|
|
25
|
-
import { requireManagedGlobalCaller, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
|
|
25
|
+
import { requireManagedGlobalCaller, requireManagedGlobalReader, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
|
|
26
|
+
import { sessionCallerEnvironment, requireManagedCaller } from "./runtime/sessionCaller.js";
|
|
27
|
+
import { checkCurrentSkills, SKILL_REUSE_RULE } from "./context/currentSkills.js";
|
|
28
|
+
import { roleSkillIds } from "./context/roleSessionContext.js";
|
|
26
29
|
import { hasManagedIdentity } from "./runtime/managedIdentity.js";
|
|
27
30
|
import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
|
|
28
31
|
import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
|
|
@@ -63,6 +66,7 @@ import { FileCompletionManager } from "./completion/fileCompletionManager.js";
|
|
|
63
66
|
import { CONFIG_DOMAINS } from "./config/configCatalog.js";
|
|
64
67
|
import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
|
|
65
68
|
import { checkSessionSkills, readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
|
|
69
|
+
import { boundedDocument, readOptions } from "./output/boundedRead.js";
|
|
66
70
|
import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
|
|
67
71
|
import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
|
|
68
72
|
import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
|
|
@@ -796,13 +800,22 @@ export async function main() {
|
|
|
796
800
|
}
|
|
797
801
|
if (resolved[0] === "session") {
|
|
798
802
|
if (resolved[1] === "skills") {
|
|
799
|
-
|
|
800
|
-
|
|
803
|
+
const parsed = readOptions(resolved.slice(2), ["--cursor"]);
|
|
804
|
+
if (parsed.positionals.length !== 0)
|
|
805
|
+
throw usageError("Usage: yui session skills [--cursor <cursor>] --json");
|
|
801
806
|
const path = process.env.YUI_SESSION_MANIFEST;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const
|
|
807
|
+
const caller = requireManagedCaller(store, process.env);
|
|
808
|
+
const set = caller.scope === "global" ? store.getGlobalRoleSessionSet(caller.roleName)
|
|
809
|
+
: store.getTaskRoleSessionSet(caller.taskId, caller.roleName);
|
|
810
|
+
const effective = set.sessions[caller.agentId].effective;
|
|
811
|
+
const kind = caller.scope === "global" ? caller.roleName === "operator" ? "operator" : "global"
|
|
812
|
+
: effective.reviewRoundId !== undefined ? "reviewer" : caller.roleName === "leader" ? "leader" : "worker";
|
|
813
|
+
const checked = path === undefined ? {
|
|
814
|
+
skills: checkCurrentSkills(home, roleSkillIds(effective.context, kind)),
|
|
815
|
+
profile: effective.context, identity: caller, rule: SKILL_REUSE_RULE,
|
|
816
|
+
historicalPackagesAreAuditOnly: true
|
|
817
|
+
} : checkSessionSkills(home, path);
|
|
818
|
+
const data = boundedDocument(checked, "session:current-skills", parsed.cursor);
|
|
806
819
|
emit(JSON.stringify(data, null, 2), false, data);
|
|
807
820
|
return;
|
|
808
821
|
}
|
|
@@ -1986,6 +1999,19 @@ async function preflightManagedTaskControlPlane() {
|
|
|
1986
1999
|
if (taskFinalReviewInvocation.error !== undefined) {
|
|
1987
2000
|
throw new Error(taskFinalReviewInvocation.error);
|
|
1988
2001
|
}
|
|
2002
|
+
// Desktop clients may carry only their provider-native identity. Resolve
|
|
2003
|
+
// command-local hints before any public/Task/Global routing. This reads this
|
|
2004
|
+
// Home only, and never creates a Session or mutates its registration.
|
|
2005
|
+
if ((process.env.CODEX_THREAD_ID !== undefined || process.env.YUI_NATIVE_SESSION_ID !== undefined)
|
|
2006
|
+
&& (process.env.YUI_SESSION_SCOPE === undefined || process.env.YUI_ROLE === undefined)) {
|
|
2007
|
+
const registry = openCurrentTaskStore(resolveYuiHome(process.env));
|
|
2008
|
+
try {
|
|
2009
|
+
Object.assign(process.env, sessionCallerEnvironment(registry, process.env));
|
|
2010
|
+
}
|
|
2011
|
+
finally {
|
|
2012
|
+
registry.close();
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1989
2015
|
if (process.env.YUI_SESSION_SCOPE !== "task") {
|
|
1990
2016
|
if (process.env.YUI_SESSION_SCOPE === "global") {
|
|
1991
2017
|
return await preflightManagedGlobalControlPlane();
|
|
@@ -2064,8 +2090,8 @@ async function preflightManagedGlobalControlPlane() {
|
|
|
2064
2090
|
const home = resolveYuiHome(process.env);
|
|
2065
2091
|
const manifest = assertManagedSessionManifest(home, "global");
|
|
2066
2092
|
const expectedRoleKind = process.env.YUI_ROLE === "operator" ? "operator" : "global";
|
|
2067
|
-
if (manifest.owner.scope !== "global"
|
|
2068
|
-
|| manifest.roleKind !== expectedRoleKind) {
|
|
2093
|
+
if (manifest !== undefined && (manifest.owner.scope !== "global"
|
|
2094
|
+
|| manifest.roleKind !== expectedRoleKind)) {
|
|
2069
2095
|
throw new ManagedRuntimeDriftError("Managed global invocation does not match its Session Manifest.");
|
|
2070
2096
|
}
|
|
2071
2097
|
if (expectedRoleKind === "operator" && ["doctor", "upgrade", "update"].includes(args[0] ?? "")) {
|
|
@@ -2079,9 +2105,15 @@ async function preflightManagedGlobalControlPlane() {
|
|
|
2079
2105
|
const verifiedStore = openCurrentTaskStore(home);
|
|
2080
2106
|
const internalCallback = args[0] === "internal"
|
|
2081
2107
|
&& ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
|
|
2082
|
-
if (!internalCallback
|
|
2108
|
+
if (!internalCallback) {
|
|
2109
|
+
const selfRead = args[0] === "session" && args[1] === "context" && args[2] === process.env.YUI_ROLE
|
|
2110
|
+
|| args[0] === "role" && args[1] === "message" && ["list", "show"].includes(args[2] ?? "")
|
|
2111
|
+
&& args[3] === process.env.YUI_ROLE;
|
|
2083
2112
|
try {
|
|
2084
|
-
|
|
2113
|
+
if (selfRead)
|
|
2114
|
+
requireManagedGlobalReader(verifiedStore, process.env);
|
|
2115
|
+
else
|
|
2116
|
+
requireManagedGlobalCaller(verifiedStore, process.env);
|
|
2085
2117
|
}
|
|
2086
2118
|
catch (error) {
|
|
2087
2119
|
verifiedStore.close();
|
|
@@ -2093,7 +2125,9 @@ async function preflightManagedGlobalControlPlane() {
|
|
|
2093
2125
|
function assertManagedSessionManifest(home, scope) {
|
|
2094
2126
|
const manifestPath = process.env.YUI_SESSION_MANIFEST;
|
|
2095
2127
|
if (manifestPath === undefined) {
|
|
2096
|
-
|
|
2128
|
+
// A Manifest is a context pointer, not an authority credential. Native
|
|
2129
|
+
// identity is validated against the current Home immediately below.
|
|
2130
|
+
return undefined;
|
|
2097
2131
|
}
|
|
2098
2132
|
let manifest;
|
|
2099
2133
|
try {
|
|
@@ -692,6 +692,7 @@ export class FileTaskController {
|
|
|
692
692
|
#globalInputDelivery;
|
|
693
693
|
#providerRetry;
|
|
694
694
|
#continuationReconciler;
|
|
695
|
+
#hostTerminalRecovery;
|
|
695
696
|
#current;
|
|
696
697
|
#operatorCurrent;
|
|
697
698
|
#runtimeObserverCurrent;
|
|
@@ -741,6 +742,7 @@ export class FileTaskController {
|
|
|
741
742
|
this.#leaderWakeFence = options.leaderWakeFence;
|
|
742
743
|
this.#jobSupervisor = options.jobSupervisor;
|
|
743
744
|
this.#continuationReconciler = options.continuationReconciler;
|
|
745
|
+
this.#hostTerminalRecovery = options.hostTerminalRecovery;
|
|
744
746
|
this.#globalInputDelivery = options.globalInputDelivery;
|
|
745
747
|
this.#providerRetry = options.providerRetry;
|
|
746
748
|
this.#signalScheduler = new MailboxScheduler(async (keys) => { await this.#requestPass({ kind: "dirty", keys }); }, {
|
|
@@ -995,6 +997,9 @@ export class FileTaskController {
|
|
|
995
997
|
}
|
|
996
998
|
}
|
|
997
999
|
const firstRuntimeDrain = await this.#drainRuntimeEvents();
|
|
1000
|
+
if (scope.kind === "full" && this.#hostTerminalRecovery !== undefined) {
|
|
1001
|
+
await this.#observeMaintenance("Host terminal recovery", this.#hostTerminalRecovery);
|
|
1002
|
+
}
|
|
998
1003
|
await this.#providerRetry?.reconcile();
|
|
999
1004
|
await this.#globalInputDelivery?.({ reconcile: scope.kind === "full", now: this.#now() });
|
|
1000
1005
|
for (const taskId of runtimeTaskFailureIds(firstRuntimeDrain)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import { taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
3
|
+
import { deliveredMessageWake, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
4
4
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
5
5
|
import { freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
6
6
|
import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
|
|
@@ -29,7 +29,7 @@ import { activeRoleAgentBinding } from "../role/role.js";
|
|
|
29
29
|
import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
30
30
|
import { standardAgentError } from "../runtime/agentError.js";
|
|
31
31
|
import { recordTaskAgentError } from "./taskAgentError.js";
|
|
32
|
-
import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure } from "./globalRuntimeAttention.js";
|
|
32
|
+
import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
|
|
33
33
|
import { queueOperatorNotification } from "./operatorNotification.js";
|
|
34
34
|
import { claimOperatorAttention } from "../scheduler/operatorEvent.js";
|
|
35
35
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
@@ -450,6 +450,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
450
450
|
binding = settleProviderTurn(binding, { attemptId: receipt, nativeTurnId: fence.nativeTurnId,
|
|
451
451
|
status: input.kind === "turn.completed" ? "completed" : input.kind === "turn.cancelled" ? "cancelled" : "failed",
|
|
452
452
|
settledAt: at });
|
|
453
|
+
if (input.kind === "turn.completed")
|
|
454
|
+
recordGlobalRuntimeRecovery(store, fence.roleName, input.eventId, {
|
|
455
|
+
nativeSessionId: fence.nativeSessionId, evidence: `provider-structured:${receipt}`
|
|
456
|
+
}, now);
|
|
453
457
|
if (fence.nativeTurnId !== undefined)
|
|
454
458
|
sessions = rememberRoleAgentCompletedTurn(sessions, fence.agentId, fence.nativeSessionId, fence.nativeTurnId, new Date(at));
|
|
455
459
|
}
|
|
@@ -1875,7 +1879,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1875
1879
|
const session = sessions?.sessions[sessions.activeAgentId];
|
|
1876
1880
|
const obsoleteIds = new Set();
|
|
1877
1881
|
for (const message of store.listMessages(taskId)) {
|
|
1878
|
-
if (pendingMessageIds.has(message.id) && taskMessageInboxReceipt(store, message) !== undefined
|
|
1882
|
+
if (pendingMessageIds.has(message.id) && (taskMessageInboxReceipt(store, message) !== undefined
|
|
1883
|
+
|| deliveredMessageWake(store, message) !== undefined)) {
|
|
1879
1884
|
obsoleteIds.add(message.id);
|
|
1880
1885
|
}
|
|
1881
1886
|
}
|
|
@@ -1925,7 +1930,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1925
1930
|
const wakeId = store.nextTaskWakeId(taskId);
|
|
1926
1931
|
const wake = createTaskWake({
|
|
1927
1932
|
id: wakeId, taskId, reasons: mailbox.pending.reasons,
|
|
1928
|
-
refs: selectedRefs,
|
|
1933
|
+
refs: pinMessageRevisions(store, taskId, selectedRefs),
|
|
1929
1934
|
fromCursor: latest?.toCursor ?? task.createdAt,
|
|
1930
1935
|
toCursor: new Date(Math.max(now.getTime(), Date.parse(mailbox.pending.lastQueuedAt))).toISOString(), now
|
|
1931
1936
|
});
|
|
@@ -5,15 +5,13 @@ import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
|
|
|
5
5
|
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
6
6
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
7
7
|
import { globalMessagePresentation } from "../interaction/notificationPresentation.js";
|
|
8
|
-
import { recordGlobalRuntimeAttention } from "./globalRuntimeAttention.js";
|
|
8
|
+
import { recordGlobalRuntimeAttention, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
|
|
9
9
|
/** Messages are the only queue. Host's atomic begin arbitrates native writers. */
|
|
10
10
|
export async function deliverGlobalInputs(home, store, ensure, onError, options = {}) {
|
|
11
11
|
for (const role of store.listGlobalRoles()) {
|
|
12
12
|
try {
|
|
13
|
-
if (store.queryGlobalRoleMessages(role.name, { handling: true, limit: 0 }).count > 0)
|
|
14
|
-
continue;
|
|
15
13
|
let pending = store.queryGlobalRoleMessages(role.name, { pending: true, limit: 64 }).messages;
|
|
16
|
-
if (
|
|
14
|
+
if (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name }))))
|
|
17
15
|
continue;
|
|
18
16
|
let sessions = store.getGlobalRoleSessionSet(role.name);
|
|
19
17
|
// A detached Host keeps an active Session and may reconnect. An explicit
|
|
@@ -47,19 +45,31 @@ export async function deliverGlobalInputs(home, store, ensure, onError, options
|
|
|
47
45
|
authority: { epoch: bindingBefore.authority.epoch, owner: "controller",
|
|
48
46
|
holderId: bindingBefore.authority.holderId } }
|
|
49
47
|
});
|
|
48
|
+
if (observed.reconciliation === "unsupported") {
|
|
49
|
+
throw new Error("Host does not advertise reconcile-turn. Preserve the accepted input and original Host; consume retained Inbox evidence or use an explicitly authorized, quiescence-verified refresh.");
|
|
50
|
+
}
|
|
50
51
|
if (observed.outcome === "rejected" || ["idle", "failed", "exited"].includes(observed.snapshot.state)
|
|
51
52
|
&& observed.reconciliation !== "terminal-republished") {
|
|
52
53
|
throw new Error(observed.failure?.detail ?? observed.snapshot.detail ?? "Host cannot reconcile this exact terminal.");
|
|
53
54
|
}
|
|
55
|
+
if (observed.reconciliation === "terminal-republished") {
|
|
56
|
+
store.transaction(tx => recordGlobalRuntimeRecovery(tx, role.name, `terminal-reconcile:${ownedTurn.attemptId}`, {
|
|
57
|
+
nativeSessionId: currentProviderConversation(bindingBefore).conversationId,
|
|
58
|
+
reason: "host-terminal-unobservable", evidence: "exact-terminal-republished"
|
|
59
|
+
}, options.now ?? new Date()));
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
catch (error) {
|
|
56
63
|
store.transaction(tx => recordGlobalRuntimeAttention(tx, role.name, `terminal-reconcile:${currentProviderConversation(bindingBefore).conversationId}:${ownedTurn.attemptId}`, { attemptId: ownedTurn.attemptId, nativeTurnId: ownedTurn.nativeTurnId,
|
|
57
64
|
nativeSessionId: currentProviderConversation(bindingBefore).conversationId,
|
|
58
65
|
reason: "host-terminal-unobservable",
|
|
59
66
|
cause: observed === undefined ? "host-control-unavailable"
|
|
60
|
-
: observed.
|
|
67
|
+
: observed.reconciliation === "unsupported" ? "host-control-unsupported"
|
|
68
|
+
: observed.outcome === "rejected" ? "host-control-rejected" : "exact-terminal-not-retained",
|
|
61
69
|
hostState: observed?.snapshot.state ?? null,
|
|
62
70
|
endpointImplementation: observed?.snapshot.endpointImplementation ?? null,
|
|
71
|
+
hostImplementation: observed?.snapshot.hostImplementation ?? null,
|
|
72
|
+
controlCapabilities: observed?.snapshot.controlCapabilities ?? null,
|
|
63
73
|
eventDelivery: observed?.snapshot.eventDelivery ?? null,
|
|
64
74
|
// Endpoint generation is not a loaded Host/package version.
|
|
65
75
|
inputKind: ownedTurn.attemptId.startsWith("direct:") ? "direct" : "managed",
|
|
@@ -78,6 +88,8 @@ export async function deliverGlobalInputs(home, store, ensure, onError, options
|
|
|
78
88
|
if (pending.length === 0)
|
|
79
89
|
continue;
|
|
80
90
|
}
|
|
91
|
+
if (store.queryGlobalRoleMessages(role.name, { handling: true, limit: 0 }).count > 0)
|
|
92
|
+
continue;
|
|
81
93
|
// Interrupt-then reserves the next legal slot even behind a large queue.
|
|
82
94
|
const reservedPage = store.queryGlobalRoleMessages(role.name, {
|
|
83
95
|
pending: true, interruptThen: true, limit: 64
|
|
@@ -1,14 +1,115 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createGlobalRoleMessage } from "../message/message.js";
|
|
3
|
-
import { serializeAgentErrorRaw } from "../runtime/agentError.js";
|
|
3
|
+
import { redactAgentErrorText, serializeAgentErrorRaw } from "../runtime/agentError.js";
|
|
4
|
+
import { summaryMetadata, summaryText } from "../output/boundedRead.js";
|
|
5
|
+
const FAILURE_HEADER = "Yui Global failure evidence ";
|
|
6
|
+
const RECOVERY_HEADER = "Yui Global recovery evidence ";
|
|
7
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
8
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9
|
+
/** This is a projection of original Message documents, never a persisted
|
|
10
|
+
* aggregate or a new handling/recovery state. Exact observations remain in the
|
|
11
|
+
* existing record-only Message history. Polling the same identity is a no-op. */
|
|
12
|
+
function diagnosticIdentity(store, roleName, identity, facts) {
|
|
13
|
+
const value = object(facts), fence = object(value.fence);
|
|
14
|
+
const failure = object(value.failure), retry = object(value.retry);
|
|
15
|
+
const error = object(failure.error ?? value.error ?? retry.error);
|
|
16
|
+
const sessions = store.getGlobalRoleSessionSet(roleName);
|
|
17
|
+
const binding = sessions?.providerBinding;
|
|
18
|
+
const nativeSessionId = value.nativeSessionId ?? fence.nativeSessionId ?? retry.nativeSessionId
|
|
19
|
+
?? sessions?.sessions[sessions.activeAgentId]?.nativeSessionId;
|
|
20
|
+
const source = {
|
|
21
|
+
roleName, nativeSessionId,
|
|
22
|
+
host: binding?.authority.holderId,
|
|
23
|
+
endpointImplementation: value.endpointImplementation,
|
|
24
|
+
hostImplementation: value.hostImplementation,
|
|
25
|
+
controlCapabilities: value.controlCapabilities,
|
|
26
|
+
driverId: fence.driverId ?? binding?.providerNamespace,
|
|
27
|
+
source: error.source,
|
|
28
|
+
phase: error.phase,
|
|
29
|
+
reason: value.reason, cause: value.cause,
|
|
30
|
+
agentId: fence.agentId ?? sessions?.activeAgentId, providerNamespace: binding?.providerNamespace,
|
|
31
|
+
accountScope: binding?.accountScope
|
|
32
|
+
};
|
|
33
|
+
const classified = typeof nativeSessionId === "string" && (typeof error.code === "string" && error.code !== "unknown"
|
|
34
|
+
|| value.reason === "host-terminal-unobservable" && typeof value.cause === "string");
|
|
35
|
+
const classification = {
|
|
36
|
+
...source, category: error.category, code: error.code, cause: value.cause,
|
|
37
|
+
detail: value.reason === "host-terminal-unobservable" ? hash(value.detail ?? null) : undefined,
|
|
38
|
+
// Explicit impact, not Turn IDs or free-form provider text.
|
|
39
|
+
inputDisposition: error.inputDisposition ?? failure.inputDisposition,
|
|
40
|
+
sessionDisposition: error.sessionDisposition ?? failure.sessionDisposition,
|
|
41
|
+
confirmed: ["submitting", "accepted", "delivery-unknown", "completed", "failed", "cancelled"].includes(value.confirmed)
|
|
42
|
+
? value.confirmed : undefined,
|
|
43
|
+
status: value.status,
|
|
44
|
+
// Unclassified failures must never be guessed equivalent.
|
|
45
|
+
unclassified: classified ? undefined : identity
|
|
46
|
+
};
|
|
47
|
+
return { key: hash(classification), source, summary: summaryText(redactAgentErrorText(String(error.message ?? value.detail ?? value.reason ?? "Runtime failure; inspect original evidence"))
|
|
48
|
+
.replace(/[\u0000-\u001f\u007f]/gu, " ")) };
|
|
49
|
+
}
|
|
50
|
+
function evidence(message) {
|
|
51
|
+
if (message.kind !== "system" || ![FAILURE_HEADER, RECOVERY_HEADER].some(header => message.body.startsWith(header)))
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
return object(JSON.parse(message.body.slice(message.body.indexOf("\n") + 1)));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function runtimeDiagnosticGroups(messages) {
|
|
61
|
+
const groups = [];
|
|
62
|
+
const active = new Map();
|
|
63
|
+
for (const message of messages) {
|
|
64
|
+
const value = evidence(message);
|
|
65
|
+
if (!value)
|
|
66
|
+
continue;
|
|
67
|
+
if (message.body.startsWith(RECOVERY_HEADER)) {
|
|
68
|
+
for (const [key, group] of active) {
|
|
69
|
+
if (group.source.nativeSessionId !== value.nativeSessionId || group.source.host !== value.host
|
|
70
|
+
|| value.reason !== undefined && group.source.reason !== value.reason
|
|
71
|
+
|| value.reason === undefined && group.source.phase !== "turn-execute"
|
|
72
|
+
&& group.source.phase !== "turn-submit")
|
|
73
|
+
continue;
|
|
74
|
+
group.recoveredAt = message.createdAt;
|
|
75
|
+
group.recoveryEvidence = message.id;
|
|
76
|
+
active.delete(key);
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const diagnostic = value.diagnostic;
|
|
81
|
+
if (typeof diagnostic?.key !== "string")
|
|
82
|
+
continue; // Old evidence remains in ordinary history.
|
|
83
|
+
const existing = active.get(diagnostic.key);
|
|
84
|
+
if (existing) {
|
|
85
|
+
existing.count++;
|
|
86
|
+
existing.lastAt = message.createdAt;
|
|
87
|
+
existing.latestEvidence = message.id;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const group = { ...diagnostic, episode: message.id, count: 1,
|
|
91
|
+
firstAt: message.createdAt, lastAt: message.createdAt,
|
|
92
|
+
firstEvidence: message.id, latestEvidence: message.id };
|
|
93
|
+
groups.push(group);
|
|
94
|
+
active.set(diagnostic.key, group);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return groups;
|
|
98
|
+
}
|
|
99
|
+
export function runtimeDiagnosticEvidence(messages, key) {
|
|
100
|
+
return messages.filter(message => evidence(message)?.diagnostic?.key === key);
|
|
101
|
+
}
|
|
4
102
|
/** Global observations have no Task event history. Keep original failed
|
|
5
103
|
* attempts as ordinary record-only Messages before replacing the live binding. */
|
|
6
104
|
export function recordGlobalRuntimeFailure(store, roleName, identity, facts, now) {
|
|
7
|
-
const
|
|
105
|
+
const diagnostic = diagnosticIdentity(store, roleName, identity, facts);
|
|
106
|
+
const header = `${FAILURE_HEADER}${hash([identity, diagnostic.key])}\n`;
|
|
8
107
|
const previous = store.listGlobalRoleMessages(roleName).find(message => message.kind === "system" && message.body.startsWith(header));
|
|
9
108
|
if (previous !== undefined)
|
|
10
109
|
return `${roleName}/${previous.id}`;
|
|
11
|
-
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), roleName, header +
|
|
110
|
+
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), roleName, header + redactAgentErrorText(JSON.stringify({
|
|
111
|
+
...object(facts), diagnostic
|
|
112
|
+
})), "system", { type: "system" }, now);
|
|
12
113
|
store.saveGlobalRoleMessage(message);
|
|
13
114
|
return `${roleName}/${message.id}`;
|
|
14
115
|
}
|
|
@@ -18,17 +119,46 @@ export function recordGlobalRuntimeAttention(store, roleName, identity, facts, n
|
|
|
18
119
|
// A Home may have a custom Global Role without an Operator. Keep its own
|
|
19
120
|
// diagnostic readable; never create a Role or launch a substitute Agent.
|
|
20
121
|
const recipient = store.getGlobalRole("operator") === null ? roleName : "operator";
|
|
21
|
-
const
|
|
122
|
+
const failureRef = recordGlobalRuntimeFailure(store, roleName, identity, facts, now);
|
|
123
|
+
const diagnostic = diagnosticIdentity(store, roleName, identity, facts);
|
|
124
|
+
const group = runtimeDiagnosticGroups(store.listGlobalRoleMessages(roleName))
|
|
125
|
+
.reverse().find(group => group.key === diagnostic.key && group.recoveredAt === undefined);
|
|
126
|
+
const requestId = `runtime-attention:${hash([roleName, diagnostic.key, group?.episode ?? identity])}`;
|
|
22
127
|
if (store.queryGlobalRoleMessages(recipient, { requestId, limit: 1 }).messages.length > 0)
|
|
23
128
|
return;
|
|
24
129
|
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), recipient, [
|
|
130
|
+
`Yui-Summary: ${diagnostic.summary}`,
|
|
25
131
|
`Global Role ${roleName} needs attention. No recovery or new authority is implied.`,
|
|
26
132
|
"Inspect the exact Session and original input before choosing a next action; unknown effects must not be replayed.",
|
|
27
133
|
`Original failed attempts remain in Role ${roleName}'s record-only failure Messages; failureRef names the latest evidence.`,
|
|
28
|
-
|
|
134
|
+
redactAgentErrorText(JSON.stringify({ diagnosticKey: diagnostic.key, source: summaryMetadata(diagnostic.source), failureRef,
|
|
135
|
+
read: `yui role message list ${roleName} --diagnostic ${diagnostic.key}` }))
|
|
29
136
|
].join("\n"), "system", { type: "system" }, now, { inputControl: { action: "queue", requestId } });
|
|
30
137
|
store.saveGlobalRoleMessage(recipient === roleName ? {
|
|
31
138
|
...message,
|
|
32
139
|
notDelivered: { reason: "runtime-needs-attention; retained for user or successor inspection", at: now.toISOString() }
|
|
33
140
|
} : message);
|
|
34
141
|
}
|
|
142
|
+
/** Called only on an established exact successful observation. A diagnostic
|
|
143
|
+
* recovery says nothing about Message handling, prior effects or Task outcome. */
|
|
144
|
+
export function recordGlobalRuntimeRecovery(store, roleName, identity, facts, now) {
|
|
145
|
+
const messages = store.listGlobalRoleMessages(roleName);
|
|
146
|
+
const host = store.getGlobalRoleSessionSet(roleName)?.providerBinding?.authority.holderId;
|
|
147
|
+
const active = runtimeDiagnosticGroups(messages).filter(group => !group.recoveredAt
|
|
148
|
+
&& group.source.nativeSessionId === facts.nativeSessionId && group.source.host === host
|
|
149
|
+
&& (facts.reason !== undefined ? group.source.reason === facts.reason
|
|
150
|
+
: ["turn-execute", "turn-submit"].includes(group.source.phase)));
|
|
151
|
+
if (active.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
const header = `${RECOVERY_HEADER}${hash(identity)}\n`;
|
|
154
|
+
if (messages.some(message => message.body.startsWith(header)))
|
|
155
|
+
return;
|
|
156
|
+
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), roleName, header + serializeAgentErrorRaw({ ...facts, host }), "system", { type: "system" }, now);
|
|
157
|
+
store.saveGlobalRoleMessage(message);
|
|
158
|
+
const recipient = store.getGlobalRole("operator") === null ? roleName : "operator";
|
|
159
|
+
const notice = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), recipient, `Yui-Summary: Global Role ${roleName} has new runtime recovery evidence; no input handling or Task completion is implied.\n`
|
|
160
|
+
+ JSON.stringify({ evidence: `${roleName}/${message.id}`, recoveredGroups: active.length }), "system", { type: "system" }, now, { inputControl: { action: "queue", requestId: `runtime-recovery:${hash([roleName, identity])}` } });
|
|
161
|
+
store.saveGlobalRoleMessage(recipient === roleName ? { ...notice,
|
|
162
|
+
notDelivered: { reason: "runtime-recovery; retained for user or successor inspection", at: now.toISOString() }
|
|
163
|
+
} : notice);
|
|
164
|
+
}
|
|
@@ -40,8 +40,10 @@ import { createWebTaskSurface } from "../web/webTaskSurface.js";
|
|
|
40
40
|
import { createWebConversationSurface } from "../web/webConversation.js";
|
|
41
41
|
import { WebRequestRejected } from "../web/webMutation.js";
|
|
42
42
|
import { inspectAgentHost, sendAgentHostNativeControl, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
|
|
43
|
+
import { recoverTaskHostTerminals } from "./taskHostTerminalRecovery.js";
|
|
43
44
|
import { archiveOrdinaryTask } from "../task/ordinaryArchive.js";
|
|
44
45
|
import { createWebSettings } from "../web/webSettings.js";
|
|
46
|
+
import { createModelManagement } from "../models/modelManagement.js";
|
|
45
47
|
import { createWebSkills } from "../web/webSkills.js";
|
|
46
48
|
import { isTaskOwnedWorkspace, sameManagedWorkspaceIdentity } from "../worktree/managedWorkspace.js";
|
|
47
49
|
import { AgentRuntimeObserver } from "./agentRuntimeObserver.js";
|
|
@@ -410,6 +412,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
410
412
|
const web = createControllerWeb(store, {
|
|
411
413
|
previews: () => createTaskPreviews(store, home),
|
|
412
414
|
skills: createWebSkills(store),
|
|
415
|
+
models: createModelManagement(store),
|
|
413
416
|
settings: createWebSettings(store, {
|
|
414
417
|
environment: options.environment ?? process.env, catalogs,
|
|
415
418
|
refreshConfiguration: () => {
|
|
@@ -560,6 +563,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
560
563
|
}
|
|
561
564
|
}),
|
|
562
565
|
jobSupervisor,
|
|
566
|
+
hostTerminalRecovery: () => recoverTaskHostTerminals(home, store, options.onError ?? (() => undefined)),
|
|
563
567
|
globalInputDelivery: (deliveryOptions) => deliverGlobalInputs(home, store, async (roleName) => {
|
|
564
568
|
await lifecycleDispatcher("runtime.ensure-role-session", { scope: "global", roleName });
|
|
565
569
|
}, options.onError ?? (() => undefined), deliveryOptions),
|