@zq-silk/yui 2.3.0 → 2.4.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/README.md +15 -1
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +17 -8
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +25 -2
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +25 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +7 -12
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +48 -71
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +2 -1
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +295 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +106 -8
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/release-workflow.md +28 -0
- package/docs/release-workflow.zh-CN.md +21 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/dist/web/assets/client/app.js +0 -808
- package/dist/web/assets/client/components.js +0 -477
- package/dist/web/assets/client/detail.js +0 -379
- package/dist/web/assets/client/dock.js +0 -309
- package/dist/web/assets/client/evidence.js +0 -364
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1277
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -198
- package/dist/web/assets/client/sections.js +0 -349
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -216
- package/dist/web/assets/styles/views.js +0 -309
- /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
export const SETTINGS_SCRIPT = String.raw `
|
|
2
|
+
import { h } from "/assets/js/lib/dom.js";
|
|
3
|
+
import { requestJson, submitMutation } from "/assets/js/lib/api.js";
|
|
4
|
+
import { createI18n } from "/assets/js/lib/i18n.js";
|
|
5
|
+
import { createThemeController } from "/assets/js/lib/theme.js";
|
|
6
|
+
import { clearPreference, readSessionAccessMode, writeSessionAccessMode } from "/assets/js/lib/prefs.js";
|
|
7
|
+
import { mountSkills } from "/assets/js/views/skills.js";
|
|
8
|
+
|
|
9
|
+
const $ = selector => document.querySelector(selector);
|
|
10
|
+
const i18n = createI18n($("#locale-select"));
|
|
11
|
+
const t = i18n.t;
|
|
12
|
+
mountSkills($("#skill-browser"), t);
|
|
13
|
+
const theme = createThemeController($("#theme-options"), t);
|
|
14
|
+
i18n.subscribe(() => theme.render());
|
|
15
|
+
const access = $("#access-mode");
|
|
16
|
+
access.value = readSessionAccessMode();
|
|
17
|
+
access.addEventListener("change", () => {
|
|
18
|
+
$("#browser-receipt").textContent = writeSessionAccessMode(access.value) ? t("settings.browserSaved") : t("settings.storageFailed");
|
|
19
|
+
});
|
|
20
|
+
$("#reset-layout").addEventListener("click", () => {
|
|
21
|
+
const keys = ["yui.sidebar.width", "yui.dock.width", "yui.dock.side", "yui.dock.open"];
|
|
22
|
+
const results = keys.map(clearPreference);
|
|
23
|
+
$("#browser-receipt").textContent = results.every(Boolean) ? t("settings.layoutReset") : t("settings.storageFailed");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const container = $("#settings-groups");
|
|
27
|
+
const status = $("#settings-status");
|
|
28
|
+
const search = $("#settings-search");
|
|
29
|
+
const groups = [];
|
|
30
|
+
let searchGeneration = 0;
|
|
31
|
+
let searchTimer;
|
|
32
|
+
const more = h("button.btn", { type: "button", hidden: true }, t("settings.more"));
|
|
33
|
+
container.after(more);
|
|
34
|
+
window.addEventListener("beforeunload", event => {
|
|
35
|
+
if (!groups.some(g => g.dirty)) return;
|
|
36
|
+
event.preventDefault(); event.returnValue = "";
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function format(value) { return typeof value === "string" ? value : JSON.stringify(value, null, 2); }
|
|
40
|
+
function inputValue(field, control) {
|
|
41
|
+
if (field.kind === "json") return JSON.parse(control.value);
|
|
42
|
+
if (field.kind === "number") {
|
|
43
|
+
if (!control.value.trim() || !Number.isFinite(Number(control.value))) throw new Error(t("settings.invalidNumber"));
|
|
44
|
+
return Number(control.value);
|
|
45
|
+
}
|
|
46
|
+
if (field.kind === "boolean") return control.value === "true";
|
|
47
|
+
return control.value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function editor(field, group, state) {
|
|
51
|
+
const labelText = t("settings.field." + field.key, field.label);
|
|
52
|
+
const label = h("label.field", null, h("span", null, labelText));
|
|
53
|
+
let control;
|
|
54
|
+
if (field.kind === "boolean" || field.choices && field.choices.length) {
|
|
55
|
+
const values = field.kind === "boolean" ? ["true", "false"] : field.choices;
|
|
56
|
+
control = h("select", null, values.map(v => h("option", { value: v }, v || t("settings.inherit"))));
|
|
57
|
+
if (!values.includes(String(field.value ?? ""))) control.append(h("option", { value: String(field.value ?? "") }, String(field.value ?? "")));
|
|
58
|
+
} else control = h(field.kind === "json" || field.kind === "multiline" ? "textarea" : "input", {
|
|
59
|
+
...(field.kind === "number" ? { type: "number", step: "any" } : {}),
|
|
60
|
+
...(field.kind === "json" || field.kind === "multiline" ? { rows: 4 } : {})
|
|
61
|
+
});
|
|
62
|
+
const initial = field.kind === "json" ? JSON.stringify(field.value, null, 2) : String(field.value ?? "");
|
|
63
|
+
control.value = initial;
|
|
64
|
+
label.append(control);
|
|
65
|
+
const reset = h("input", { type: "checkbox" });
|
|
66
|
+
const row = h("div.settings-field", { dataset: { field: field.key } }, label,
|
|
67
|
+
field.summary ? h("p", null, field.summary) : null,
|
|
68
|
+
h("small", null, t("settings.current") + ": " + (field.value === "" ? t("settings.inherit") : format(field.value)) +
|
|
69
|
+
(field.source ? " · " + t("settings.source." + field.source, field.source) : "") +
|
|
70
|
+
(Object.hasOwn(field, "defaultValue") ? " · " + t("settings.default") + ": " + format(field.defaultValue) : "")),
|
|
71
|
+
field.takesEffect ? h("small", null, field.takesEffect) : null,
|
|
72
|
+
field.reset ? h("label.settings-reset", null, reset, t("settings.reset")) : null);
|
|
73
|
+
let unavailable = false;
|
|
74
|
+
function mark() {
|
|
75
|
+
control.disabled = reset.checked || unavailable;
|
|
76
|
+
state.dirty = state.editors.some(e => e.changed());
|
|
77
|
+
state.save.disabled = !state.dirty || state.pending || state.capabilityPending || state.unknown;
|
|
78
|
+
if (!state.unknown && !state.pending) state.receipt.textContent = state.dirty ? t("settings.unsaved") : "";
|
|
79
|
+
}
|
|
80
|
+
function listen() {
|
|
81
|
+
control.addEventListener("input", mark);
|
|
82
|
+
control.addEventListener("change", () => { mark(); item.onChange?.(); });
|
|
83
|
+
}
|
|
84
|
+
reset.addEventListener("change", mark);
|
|
85
|
+
const hint = h("small");
|
|
86
|
+
const choiceActions = h("div.settings-actions");
|
|
87
|
+
row.append(choiceActions, hint);
|
|
88
|
+
const item = { row, get control() { return control; }, field,
|
|
89
|
+
get value() { return reset.checked ? "" : control.value; },
|
|
90
|
+
get blocked() { return unavailable && !reset.checked && control.value !== "" && control.value !== initial; },
|
|
91
|
+
changed: () => reset.checked || control.value !== initial,
|
|
92
|
+
change: () => reset.checked || (["model", "effort"].includes(field.key) && field.reset && control.value === "")
|
|
93
|
+
? { key: field.key, reset: true } : { key: field.key, value: inputValue(field, control) },
|
|
94
|
+
setChoices(choices, options = {}) {
|
|
95
|
+
const value = control.value;
|
|
96
|
+
unavailable = options.available === false || (!choices.length && options.allowCustom === false);
|
|
97
|
+
const values = choices.map(c => c.value);
|
|
98
|
+
const selected = h("select", null, h("option", { value: "" }, t("settings.inherit")),
|
|
99
|
+
choices.map(c => h("option", { value: c.value }, c.label)));
|
|
100
|
+
if (value && !values.includes(value)) selected.append(h("option", { value }, value + " · " + t("settings.notListed")));
|
|
101
|
+
control = choices.length ? selected : h("input");
|
|
102
|
+
control.value = value;
|
|
103
|
+
control.disabled = reset.checked || unavailable;
|
|
104
|
+
listen();
|
|
105
|
+
label.replaceChildren(h("span", null, labelText), control);
|
|
106
|
+
choiceActions.replaceChildren();
|
|
107
|
+
if (choices.length && options.allowCustom && !unavailable) {
|
|
108
|
+
const custom = h("button.btn", { type: "button" }, t("settings.custom"));
|
|
109
|
+
custom.addEventListener("click", () => {
|
|
110
|
+
const value = control.value;
|
|
111
|
+
control = h("input", { value });
|
|
112
|
+
control.disabled = reset.checked;
|
|
113
|
+
listen(); label.replaceChildren(h("span", null, labelText), control); choiceActions.replaceChildren();
|
|
114
|
+
control.focus();
|
|
115
|
+
});
|
|
116
|
+
choiceActions.append(custom);
|
|
117
|
+
}
|
|
118
|
+
hint.textContent = (options.reason || "") +
|
|
119
|
+
(value && !values.includes(value) ? " " + t("settings.notListedHelp") : "");
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
reset.addEventListener("change", () => item.onChange?.());
|
|
123
|
+
listen();
|
|
124
|
+
return item;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function applyCapabilities(state, result) {
|
|
128
|
+
const catalog = result.catalog;
|
|
129
|
+
const model = state.editors.find(e => e.field.key === "model");
|
|
130
|
+
const effort = state.editors.find(e => e.field.key === "effort");
|
|
131
|
+
if (!model) return;
|
|
132
|
+
const modelField = catalog.fields.find(f => f.key === "model");
|
|
133
|
+
model.setChoices(catalog.models, modelField || { allowCustom: true });
|
|
134
|
+
function updateEffort() {
|
|
135
|
+
if (!effort) return;
|
|
136
|
+
const selected = !model.value ? catalog.models.find(m => m.isDefault)
|
|
137
|
+
: catalog.models.find(m => m.value === model.value)
|
|
138
|
+
|| catalog.models.find(m => m.resolvedModel === model.value && !m.isDefault)
|
|
139
|
+
|| catalog.models.find(m => m.resolvedModel === model.value);
|
|
140
|
+
const field = catalog.fields.find(f => f.key === "effort");
|
|
141
|
+
const choices = selected?.efforts.length ? selected.efforts : field?.choices || [];
|
|
142
|
+
effort.setChoices(choices, {
|
|
143
|
+
allowCustom: !choices.length && field?.allowCustom !== false,
|
|
144
|
+
available: choices.length ? true : field?.available,
|
|
145
|
+
reason: selected?.defaultEffort ? t("settings.nativeEffort") + ": " + selected.defaultEffort : field?.reason
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
model.onChange = updateEffort;
|
|
149
|
+
updateEffort();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renderGroup(state, group) {
|
|
153
|
+
const generation = state.renderGeneration = (state.renderGeneration || 0) + 1;
|
|
154
|
+
state.group = group; state.editors = []; state.dirty = false; state.unknown = false;
|
|
155
|
+
state.capabilityPending = false;
|
|
156
|
+
const form = h("form.settings-stack");
|
|
157
|
+
const receipt = h("p.settings-receipt", { role: "status", "aria-live": "polite" });
|
|
158
|
+
const save = h("button.btn.btn-primary", { type: "submit", disabled: true }, t("settings.save"));
|
|
159
|
+
state.save = save; state.receipt = receipt;
|
|
160
|
+
form.append(h("p", null, group.notice));
|
|
161
|
+
const isRole = group.id.startsWith("role/");
|
|
162
|
+
if (isRole) form.append(h("p", null, "Agent: " + group.agentId + " · " + t("settings.roleScope")));
|
|
163
|
+
const advanced = h("details", { open: !!search.value.trim() }, h("summary", null, t("settings.advancedFields")));
|
|
164
|
+
const common = h("div.settings-grid");
|
|
165
|
+
group.fields.forEach(field => {
|
|
166
|
+
const item = editor(field, group, state);
|
|
167
|
+
state.editors.push(item);
|
|
168
|
+
if (isRole) (["model", "effort"].includes(field.key) ? common : advanced).append(item.row);
|
|
169
|
+
else form.append(item.row);
|
|
170
|
+
});
|
|
171
|
+
if (isRole) form.append(common, advanced);
|
|
172
|
+
const acknowledge = h("input", { type: "checkbox" });
|
|
173
|
+
if (group.id.startsWith("role/") || group.id.startsWith("agent/")) form.append(h("label.settings-reset", null,
|
|
174
|
+
acknowledge, t("settings.acknowledge")));
|
|
175
|
+
const compare = h("button.btn", { type: "button" }, t("settings.compare"));
|
|
176
|
+
compare.addEventListener("click", async () => {
|
|
177
|
+
compare.disabled = true;
|
|
178
|
+
try {
|
|
179
|
+
const current = await requestJson("/api/settings/group?" + new URLSearchParams({ id: group.id }));
|
|
180
|
+
const values = h("div.settings-observation", null,
|
|
181
|
+
h("p", null, current.notice),
|
|
182
|
+
current.fields.map(field => h("p", null,
|
|
183
|
+
t("settings.field." + field.key, field.label) + ": " +
|
|
184
|
+
(field.value === "" ? t("settings.inherit") : format(field.value)) +
|
|
185
|
+
(field.source ? " · " + t("settings.source." + field.source, field.source) : ""))));
|
|
186
|
+
const agentChanged = current.agentId !== group.agentId;
|
|
187
|
+
if (agentChanged) values.append(h("p.settings-error", null, t("settings.agentChanged")));
|
|
188
|
+
const adopt = h("button.btn", { type: "button", disabled: state.unknown || agentChanged }, t("settings.useBaseline"));
|
|
189
|
+
adopt.addEventListener("click", () => {
|
|
190
|
+
state.group = current;
|
|
191
|
+
receipt.textContent = t("settings.baselineChanged");
|
|
192
|
+
values.remove(); adopt.remove();
|
|
193
|
+
});
|
|
194
|
+
state.content.append(values, adopt);
|
|
195
|
+
} catch (error) { receipt.textContent = error.message; }
|
|
196
|
+
finally { compare.disabled = false; }
|
|
197
|
+
});
|
|
198
|
+
form.append(h("div.settings-actions", null, save, compare), receipt);
|
|
199
|
+
form.addEventListener("submit", async event => {
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
if (state.pending || state.capabilityPending || state.unknown) return;
|
|
202
|
+
let changes;
|
|
203
|
+
if (state.editors.some(e => e.blocked)) {
|
|
204
|
+
receipt.textContent = t("settings.unsupportedDraft"); return;
|
|
205
|
+
}
|
|
206
|
+
try { changes = state.editors.filter(e => e.changed()).map(e => e.change()); }
|
|
207
|
+
catch (error) { receipt.textContent = error.message; receipt.classList.add("settings-error"); return; }
|
|
208
|
+
if (!changes.length) return;
|
|
209
|
+
state.pending = true; save.disabled = true;
|
|
210
|
+
// Keep controls frozen during one submission. Failed saves keep all drafts.
|
|
211
|
+
const controls = [...form.querySelectorAll("input,select,textarea,button")];
|
|
212
|
+
const disabled = controls.map(c => c.disabled);
|
|
213
|
+
controls.forEach(c => { c.disabled = true; });
|
|
214
|
+
try {
|
|
215
|
+
const result = await submitMutation("settings/" + group.id, "/api/settings/group", {
|
|
216
|
+
id: group.id, revision: state.group.revision, changes, acknowledgeLive: acknowledge.checked
|
|
217
|
+
});
|
|
218
|
+
renderGroup(state, result.group);
|
|
219
|
+
state.receipt.textContent = t("settings.saved") + "\n" + result.output + "\n" + result.adoption + "\n" + result.refresh;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
state.unknown = error.disposition !== "not-submitted";
|
|
222
|
+
receipt.textContent = error.message + "\n" + (state.unknown ? t("settings.unknown") : t("settings.draftKept"));
|
|
223
|
+
receipt.classList.add("settings-error");
|
|
224
|
+
controls.forEach((c, i) => { c.disabled = disabled[i]; });
|
|
225
|
+
} finally {
|
|
226
|
+
state.pending = false;
|
|
227
|
+
state.save.disabled = !state.dirty || state.unknown;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
if (group.observation) form.append(h("details", null, h("summary", null, t("settings.observation")),
|
|
231
|
+
h("pre.settings-observation", null, JSON.stringify(group.observation, null, 2))));
|
|
232
|
+
if (group.agentId) {
|
|
233
|
+
const info = h("p", { role: "status" }, t("settings.notQueried"));
|
|
234
|
+
const query = h("button.btn", { type: "button" }, t("settings.queryCapabilities"));
|
|
235
|
+
const refresh = h("button.btn", { type: "button" }, t("settings.refreshCapabilities"));
|
|
236
|
+
const output = h("div");
|
|
237
|
+
async function load(force) {
|
|
238
|
+
if (state.pending || state.capabilityPending) return;
|
|
239
|
+
if (state.editors.some(e => e.field.key === "activeAgentId" && e.changed())) {
|
|
240
|
+
info.textContent = t("settings.saveAgentFirst"); return;
|
|
241
|
+
}
|
|
242
|
+
state.capabilityPending = true; save.disabled = true;
|
|
243
|
+
query.disabled = refresh.disabled = true; info.textContent = t("settings.querying");
|
|
244
|
+
try {
|
|
245
|
+
const result = await requestJson("/api/settings/capabilities?" + new URLSearchParams({ id: group.id, refresh: String(force) }));
|
|
246
|
+
if (state.renderGeneration !== generation) return;
|
|
247
|
+
if (state.pending) { info.textContent = t("settings.notQueried"); return; }
|
|
248
|
+
if (result.catalog.agentId !== group.agentId) {
|
|
249
|
+
info.textContent = t("settings.agentChanged"); return;
|
|
250
|
+
}
|
|
251
|
+
if (state.editors.some(e => e.field.key === "activeAgentId" && e.changed())) {
|
|
252
|
+
info.textContent = t("settings.saveAgentFirst"); return;
|
|
253
|
+
}
|
|
254
|
+
info.textContent = t("settings.capability." + result.source, result.source) + " · " + (result.fetchedAt || result.attemptedAt) +
|
|
255
|
+
(result.failure ? " · " + result.failure.code + ": " + result.failure.message : "");
|
|
256
|
+
applyCapabilities(state, result);
|
|
257
|
+
output.replaceChildren(
|
|
258
|
+
...result.catalog.warnings.map(w => h("p", null, w)),
|
|
259
|
+
h("details", null, h("summary", null, t("settings.capabilityDetails")),
|
|
260
|
+
h("pre.settings-observation", null, JSON.stringify(result.catalog, null, 2))));
|
|
261
|
+
} catch (error) { info.textContent = t("settings.queryFailed") + ": " + error.message; }
|
|
262
|
+
finally {
|
|
263
|
+
query.disabled = refresh.disabled = false;
|
|
264
|
+
if (state.renderGeneration === generation) {
|
|
265
|
+
state.capabilityPending = false;
|
|
266
|
+
state.save.disabled = !state.dirty || state.pending || state.unknown;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
query.addEventListener("click", () => load(false)); refresh.addEventListener("click", () => load(true));
|
|
271
|
+
const capabilities = h("div", null, h("div.settings-actions", null, query, refresh), info, output);
|
|
272
|
+
if (isRole) common.before(capabilities);
|
|
273
|
+
else form.append(capabilities);
|
|
274
|
+
}
|
|
275
|
+
state.content.replaceChildren(form);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function loadGroup(state) {
|
|
279
|
+
if (state.loaded || state.loading) return;
|
|
280
|
+
state.loading = true;
|
|
281
|
+
state.content.textContent = t("settings.loading");
|
|
282
|
+
try {
|
|
283
|
+
renderGroup(state, await requestJson("/api/settings/group?" + new URLSearchParams({ id: state.meta.id })));
|
|
284
|
+
state.loaded = true;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
state.content.replaceChildren(h("p.settings-error", null, error.message),
|
|
287
|
+
h("button.btn", { type: "button", onclick: () => loadGroup(state) }, t("settings.retry")));
|
|
288
|
+
} finally { state.loading = false; }
|
|
289
|
+
}
|
|
290
|
+
search.addEventListener("input", () => {
|
|
291
|
+
searchGeneration++;
|
|
292
|
+
clearTimeout(searchTimer);
|
|
293
|
+
searchTimer = setTimeout(() => start(), 200);
|
|
294
|
+
});
|
|
295
|
+
async function start(cursor) {
|
|
296
|
+
const generation = ++searchGeneration;
|
|
297
|
+
const query = search.value.trim();
|
|
298
|
+
status.textContent = t("settings.loading");
|
|
299
|
+
more.disabled = true;
|
|
300
|
+
try {
|
|
301
|
+
const data = await requestJson("/api/settings?" + new URLSearchParams({ q: query, cursor: cursor || "0" }));
|
|
302
|
+
if (generation !== searchGeneration) return;
|
|
303
|
+
if (!cursor) groups.forEach(state => { state.root.hidden = true; });
|
|
304
|
+
data.groups.forEach(meta => {
|
|
305
|
+
const existing = groups.find(g => g.meta.id === meta.id);
|
|
306
|
+
if (existing) {
|
|
307
|
+
existing.root.hidden = false;
|
|
308
|
+
if (query) { existing.root.open = true; void loadGroup(existing); }
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const content = h("div");
|
|
312
|
+
const root = h("details.settings-card", { open: meta.id === "system" || !!query },
|
|
313
|
+
h("summary", null, t("settings.section." + meta.section, meta.section) + " · " + meta.title), content);
|
|
314
|
+
const state = { meta, root, content, dirty: false, loaded: false, loading: false };
|
|
315
|
+
groups.push(state); container.append(root);
|
|
316
|
+
root.addEventListener("toggle", () => { if (root.open) void loadGroup(state); });
|
|
317
|
+
if (root.open) void loadGroup(state);
|
|
318
|
+
});
|
|
319
|
+
status.textContent = data.total ? String(data.total) + " " + t("settings.groups") : t("settings.noMatches");
|
|
320
|
+
more.hidden = !data.nextCursor;
|
|
321
|
+
more.onclick = () => start(data.nextCursor);
|
|
322
|
+
} catch (error) { status.textContent = error.message; }
|
|
323
|
+
finally { if (generation === searchGeneration) more.disabled = false; }
|
|
324
|
+
}
|
|
325
|
+
void start();
|
|
326
|
+
`;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
export const TASK_VIEW_SCRIPT = String.raw `
|
|
2
|
+
// The selected Task on screen: the center page, the dock discussion and its
|
|
3
|
+
// Session targets, and the section (tab) the reader is on. This module draws
|
|
4
|
+
// state.detail; app/selection loads it. A re-render is skipped while the
|
|
5
|
+
// reader has an unsent draft, an in-flight read or focus in the center. The
|
|
6
|
+
// only state field written here is state.activeTab.
|
|
7
|
+
import { h, isEditing } from "/assets/js/lib/dom.js";
|
|
8
|
+
import { releaseMutation } from "/assets/js/lib/api.js";
|
|
9
|
+
import { loadingBlock } from "/assets/js/ui/primitives.js";
|
|
10
|
+
import { entriesOf } from "/assets/js/domain/context.js";
|
|
11
|
+
import { renderOverview } from "/assets/js/views/overview.js";
|
|
12
|
+
import { renderTaskDetail, selectTab, TABS, TAB_ALIASES } from "/assets/js/views/task/page.js";
|
|
13
|
+
import { updateObservation } from "/assets/js/views/task/observation.js";
|
|
14
|
+
import { renderDiscussion, discussionHasUnsent } from "/assets/js/views/dock/discussion.js";
|
|
15
|
+
import { sameTarget } from "/assets/js/views/dock/terminal.js";
|
|
16
|
+
import { setSectionParam } from "/assets/js/layout/router.js";
|
|
17
|
+
|
|
18
|
+
// Only the observed identity participates: observation timestamps advance on
|
|
19
|
+
// every poll and would otherwise re-render continuously.
|
|
20
|
+
function runtimeSignature(detail) {
|
|
21
|
+
const runtime = detail.runtime;
|
|
22
|
+
return detail.runtimeStatus + "|" + JSON.stringify(runtime ? [
|
|
23
|
+
runtime.sessions && runtime.sessions.sessions, runtime.remoteDelivery,
|
|
24
|
+
runtime.execution && runtime.execution.attention, runtime.execution && runtime.execution.blockers,
|
|
25
|
+
runtime.execution && runtime.execution.next, runtime.execution && runtime.execution.status
|
|
26
|
+
] : null) + "|" + ((runtime && runtime.roles) || []).map(function (role) {
|
|
27
|
+
return role.name + ":" + (role.runtimeSession ? role.runtimeSession.nativeSessionId + "/" + role.runtimeSession.status : "-") + ":" + role.status;
|
|
28
|
+
}).join(",");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// deps: { el, state, t, locale, api, toast, terminal, workspace,
|
|
32
|
+
// selectTask, clearSelection, pickAttention, readPageSessions, refreshCatalog }
|
|
33
|
+
// el #detail, #center and the dock discussion
|
|
34
|
+
// state reads selected, detail, detailKey and the catalog fields the
|
|
35
|
+
// overview shows; writes activeTab
|
|
36
|
+
// api Task reads for lazy blocks and the Task page's writes
|
|
37
|
+
// terminal the dock's native Session (views/dock/terminal)
|
|
38
|
+
// workspace layout/workspace: dock toggles and opening Sessions
|
|
39
|
+
// selectTask(taskId) an overview row was chosen (app/selection)
|
|
40
|
+
// clearSelection() the Task page's back button (app/selection)
|
|
41
|
+
// pickAttention(kind), readPageSessions() overview actions (app/catalog)
|
|
42
|
+
// refreshCatalog() quiet catalog refresh after a write; resolves when done
|
|
43
|
+
export function createTaskView(deps) {
|
|
44
|
+
const view = {
|
|
45
|
+
deps: deps,
|
|
46
|
+
// The render keys of what is on screen; an unchanged key skips the redraw.
|
|
47
|
+
rendered: { detail: null, discussion: null },
|
|
48
|
+
controller: null
|
|
49
|
+
};
|
|
50
|
+
view.controller = {
|
|
51
|
+
enterTask: function () { enterTask(view); },
|
|
52
|
+
leaveTask: function () { clearDiscussion(deps.el); },
|
|
53
|
+
showLoading: function () { showLoading(deps); },
|
|
54
|
+
showDetailError: function () { showDetailError(deps); },
|
|
55
|
+
hasUnsent: function () { return hasUnsent(deps.el); },
|
|
56
|
+
render: function (force) { render(view, force); },
|
|
57
|
+
observationArrived: function (detail) { observationArrived(view, detail); },
|
|
58
|
+
relabel: function () { relabel(view); },
|
|
59
|
+
showOverview: function () { showOverview(deps); },
|
|
60
|
+
updateSessionTargets: function () { updateSessionTargets(deps); },
|
|
61
|
+
switchTab: function (tab, options) { switchTab(deps, tab, options); },
|
|
62
|
+
applySection: function (section) { applySection(deps, section); }
|
|
63
|
+
};
|
|
64
|
+
return view.controller;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// A Task was just selected: start on its overview tab with nothing drawn.
|
|
68
|
+
function enterTask(view) {
|
|
69
|
+
view.deps.state.activeTab = "overview";
|
|
70
|
+
view.rendered.detail = null;
|
|
71
|
+
view.rendered.discussion = null;
|
|
72
|
+
clearDiscussion(view.deps.el);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clearDiscussion(el) {
|
|
76
|
+
el.discussion.replaceChildren();
|
|
77
|
+
delete el.discussion.dataset.taskId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function showLoading(deps) {
|
|
81
|
+
const el = deps.el;
|
|
82
|
+
el.detail.replaceChildren(h("div.page", null, loadingBlock(deps.t("loading.detail"))));
|
|
83
|
+
delete el.detail.dataset.taskId;
|
|
84
|
+
el.center.scrollTop = 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function showDetailError(deps) {
|
|
88
|
+
deps.el.detail.replaceChildren(h("div.page", null, h("div.list-empty.is-error", null, h("p", null, deps.t("errors.detail")))));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// An unsent draft in the Task page or the dock discussion.
|
|
92
|
+
function hasUnsent(el) {
|
|
93
|
+
return !!(el.detail.querySelector('[data-unsent="true"]') || discussionHasUnsent(el.discussion));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The locale changed: redraw everything that carries text.
|
|
97
|
+
function relabel(view) {
|
|
98
|
+
view.rendered.discussion = null;
|
|
99
|
+
if (view.deps.state.detail) render(view, true); else showOverview(view.deps);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function render(view, force) {
|
|
103
|
+
const deps = view.deps;
|
|
104
|
+
const state = deps.state;
|
|
105
|
+
if (!state.detail) {
|
|
106
|
+
if (!state.selected) { view.rendered.detail = null; showOverview(deps); }
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
renderDock(view);
|
|
110
|
+
// Polling must not replace an unsent draft or an in-flight form, including
|
|
111
|
+
// after focus moved. This is view state, never Task state.
|
|
112
|
+
if (centerIsBusy(deps)) return;
|
|
113
|
+
const key = deps.locale() + "|" + state.detailKey + "|" + runtimeSignature(state.detail);
|
|
114
|
+
if (!force && key === view.rendered.detail) return;
|
|
115
|
+
drawDetail(deps, detailContext(view));
|
|
116
|
+
view.rendered.detail = key;
|
|
117
|
+
deps.workspace.syncDockButtons();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderDock(view) {
|
|
121
|
+
const deps = view.deps;
|
|
122
|
+
const detail = deps.state.detail;
|
|
123
|
+
const el = deps.el;
|
|
124
|
+
const key = deps.locale() + "|" + detail.task.id + "|" + deps.state.detailKey + "|" + detail.task.status;
|
|
125
|
+
if (key !== view.rendered.discussion || el.discussion.dataset.taskId !== detail.task.id) {
|
|
126
|
+
renderDiscussion(el.discussion, detail, deps.t, deps.locale(), dockActions(deps));
|
|
127
|
+
view.rendered.discussion = key;
|
|
128
|
+
}
|
|
129
|
+
updateSessionTargets(deps);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function observationArrived(view, detail) {
|
|
133
|
+
const deps = view.deps;
|
|
134
|
+
if (deps.el.detail.dataset.taskId === detail.task.id) updateObservation(deps.el.detail, detail, deps.t, deps.locale(), detailContext(view));
|
|
135
|
+
// Patching slots is not enough when the observed identity changed facts the
|
|
136
|
+
// detail drew earlier; the render key decides whether a re-render is due.
|
|
137
|
+
if (deps.state.detail === detail) render(view);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Everything the Task page (views/task/*) may call back into.
|
|
141
|
+
function detailContext(view) {
|
|
142
|
+
const deps = view.deps;
|
|
143
|
+
const api = deps.api;
|
|
144
|
+
return {
|
|
145
|
+
activeTab: deps.state.activeTab,
|
|
146
|
+
onTab: view.controller.switchTab,
|
|
147
|
+
onBack: deps.clearSelection,
|
|
148
|
+
showDock: deps.workspace.toggleDock,
|
|
149
|
+
openConversation: function (materials) {
|
|
150
|
+
deps.workspace.openConversation({ scope: "task", taskId: deps.state.selected, roleName: "leader" }, materials);
|
|
151
|
+
},
|
|
152
|
+
openSession: function (roleName) { deps.workspace.openSession({ scope: "task", taskId: deps.state.selected, roleName: roleName }); },
|
|
153
|
+
answerInput: function (input, answer, control) { return answerInput(deps, input, answer, control); },
|
|
154
|
+
taskAction: deps.api.taskAction,
|
|
155
|
+
inspect: api.inspect,
|
|
156
|
+
list: api.list,
|
|
157
|
+
artifacts: api.artifacts,
|
|
158
|
+
readArtifact: api.artifact,
|
|
159
|
+
evidence: api.evidence,
|
|
160
|
+
panels: api.panels,
|
|
161
|
+
readPanel: api.readPanel,
|
|
162
|
+
control: api.control,
|
|
163
|
+
updateTask: api.updateTask,
|
|
164
|
+
afterWrite: function () { deps.refreshCatalog(); }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Everything the dock discussion (views/dock/discussion) may call back into.
|
|
169
|
+
function dockActions(deps) {
|
|
170
|
+
return {
|
|
171
|
+
sendMessage: deps.api.sendMessage,
|
|
172
|
+
inspect: deps.api.inspect,
|
|
173
|
+
list: deps.api.list,
|
|
174
|
+
afterWrite: function () { deps.refreshCatalog(); }
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function answerInput(deps, input, answer, control) {
|
|
179
|
+
if (!deps.state.detail) return;
|
|
180
|
+
const taskId = deps.state.detail.task.id;
|
|
181
|
+
const card = control && control.closest(".input-card");
|
|
182
|
+
const controls = card ? Array.from(card.querySelectorAll("button, input")) : control ? [control] : [];
|
|
183
|
+
const receipt = card && card.querySelector("[data-input-receipt]");
|
|
184
|
+
if (card) card.dataset.unsent = "true";
|
|
185
|
+
controls.forEach(function (item) { item.disabled = true; });
|
|
186
|
+
if (receipt) receipt.textContent = deps.t("receipt.waiting");
|
|
187
|
+
try {
|
|
188
|
+
const result = await deps.api.answerInput(taskId, input.id, answer);
|
|
189
|
+
releaseMutation(taskId + "/input/" + input.id);
|
|
190
|
+
const text = result.request.id + " · " + result.request.status + " · "
|
|
191
|
+
+ (result.request.resolution ? result.request.resolution.answer.text : "");
|
|
192
|
+
if (receipt) receipt.textContent = text;
|
|
193
|
+
if (card) card.dataset.unsent = "false";
|
|
194
|
+
deps.toast(text);
|
|
195
|
+
const form = control && control.closest("form");
|
|
196
|
+
if (form) form.dataset.unsent = "false";
|
|
197
|
+
await deps.refreshCatalog();
|
|
198
|
+
} catch (error) {
|
|
199
|
+
controls.forEach(function (item) { item.disabled = error.disposition !== "not-submitted"; });
|
|
200
|
+
if (card && error.disposition === "not-submitted") card.dataset.unsent = "false";
|
|
201
|
+
const text = error.disposition === "not-submitted" ? error.message : deps.t("input.unknown");
|
|
202
|
+
if (receipt) receipt.textContent = text;
|
|
203
|
+
deps.toast(text);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function centerIsBusy(deps) {
|
|
208
|
+
const el = deps.el;
|
|
209
|
+
const active = document.activeElement;
|
|
210
|
+
return el.detail.dataset.taskId === (deps.state.detail && deps.state.detail.task.id)
|
|
211
|
+
&& (el.detail.querySelector('[data-unsent="true"]') || el.detail.querySelector('[data-reading="true"]')
|
|
212
|
+
|| isEditing(active) && el.center.contains(active));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Re-render the page, restoring what the reader had open, the focused tab and
|
|
216
|
+
// the scroll position.
|
|
217
|
+
function drawDetail(deps, ctx) {
|
|
218
|
+
const el = deps.el;
|
|
219
|
+
const detail = deps.state.detail;
|
|
220
|
+
const openSections = Array.from(el.detail.querySelectorAll("details[data-view-key][open]")).map(function (d) { return d.dataset.viewKey; });
|
|
221
|
+
const focusedTab = el.detail.querySelector(".tab:focus");
|
|
222
|
+
const focusedTabId = focusedTab && focusedTab.id;
|
|
223
|
+
const scroll = el.detail.dataset.taskId === detail.task.id ? el.center.scrollTop : 0;
|
|
224
|
+
renderTaskDetail(el.detail, detail, deps.t, deps.locale(), ctx);
|
|
225
|
+
el.detail.querySelectorAll("details[data-view-key]").forEach(function (d) { d.open = openSections.includes(d.dataset.viewKey); });
|
|
226
|
+
if (focusedTabId) el.detail.querySelector("#" + CSS.escape(focusedTabId)).focus({ preventScroll: true });
|
|
227
|
+
el.center.scrollTop = scroll;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The dock's Session chips: every role of the selected Task plus the global
|
|
231
|
+
// operator, keeping the Session currently open even when it left the list.
|
|
232
|
+
function updateSessionTargets(deps) {
|
|
233
|
+
const detail = deps.state.detail;
|
|
234
|
+
const targets = [];
|
|
235
|
+
if (detail) {
|
|
236
|
+
const runtimeRoles = (detail.runtime && detail.runtime.roles) || [];
|
|
237
|
+
entriesOf(detail.core, "role").forEach(function (entry) {
|
|
238
|
+
const runtimeRole = runtimeRoles.find(function (role) { return role.name === entry.ref.refId; });
|
|
239
|
+
targets.push({ scope: "task", taskId: detail.task.id, roleName: entry.ref.refId,
|
|
240
|
+
live: !!(runtimeRole && runtimeRole.runtimeSession && runtimeRole.runtimeSession.nativeSessionId) });
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
targets.push({ scope: "global", roleName: "operator" });
|
|
244
|
+
const current = deps.terminal.current();
|
|
245
|
+
if (current && !targets.some(function (target) { return sameTarget(target, current); })) targets.unshift(current);
|
|
246
|
+
deps.terminal.setTargets(targets);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function showOverview(deps) {
|
|
250
|
+
delete deps.el.detail.dataset.taskId;
|
|
251
|
+
renderOverview(deps.el.detail, deps.state, deps.t, deps.locale(), {
|
|
252
|
+
select: deps.selectTask,
|
|
253
|
+
filterAttention: deps.pickAttention,
|
|
254
|
+
readSessions: deps.readPageSessions,
|
|
255
|
+
openOperator: deps.workspace.openOperator
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function switchTab(deps, tab, options) {
|
|
260
|
+
if (!TABS.includes(tab)) return;
|
|
261
|
+
deps.state.activeTab = tab;
|
|
262
|
+
selectTab(deps.el.detail, tab);
|
|
263
|
+
if (!(options && options.keepScroll)) deps.el.center.scrollTop = 0;
|
|
264
|
+
setSectionParam(deps.state, tab);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// A section may name a tab, a retired tab name, or any anchored block inside
|
|
268
|
+
// one (for example "reviews" → the Delivery tab, scrolled to #detail-reviews).
|
|
269
|
+
function applySection(deps, section) {
|
|
270
|
+
const tab = (section && TAB_ALIASES[section]) || section;
|
|
271
|
+
if (!tab || TABS.includes(tab)) { switchTab(deps, tab || "overview"); return; }
|
|
272
|
+
const anchor = deps.el.detail.querySelector("#detail-" + CSS.escape(tab));
|
|
273
|
+
const panel = anchor && anchor.closest(".tab-panel");
|
|
274
|
+
if (!panel) { switchTab(deps, "overview"); return; }
|
|
275
|
+
deps.state.activeTab = panel.id.replace(/^panel-/, "");
|
|
276
|
+
selectTab(deps.el.detail, deps.state.activeTab);
|
|
277
|
+
if (anchor.tagName === "DETAILS") anchor.open = true;
|
|
278
|
+
anchor.scrollIntoView({ block: "start" });
|
|
279
|
+
}
|
|
280
|
+
`;
|