@zq-silk/yui 2.2.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 +19 -3
- 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 +25 -11
- package/dist/cli/interactionPolicy.js +4 -0
- 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 +30 -9
- package/dist/commands/taskIntegrationCommands.js +5 -16
- 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 +51 -39
- package/dist/controller/clientRuntime.js +3 -2
- 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 +15 -79
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobCleanupInspection.js +67 -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 +86 -80
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +18 -11
- 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/integrationRuntimeIsolation.js +21 -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/taskRuntimeIsolation.js +91 -21
- 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/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- 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} +28 -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} +62 -56
- 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/{icons.js → shared/icons.js} +4 -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} +15 -13
- 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 +306 -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 +114 -9
- 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/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +4 -3
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-leader/references/integration.md +20 -0
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +3 -0
- package/skills/yui-runtime/SKILL.md +4 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
- package/dist/web/assets/client/app.js +0 -804
- package/dist/web/assets/client/components.js +0 -418
- package/dist/web/assets/client/detail.js +0 -333
- package/dist/web/assets/client/dock.js +0 -225
- package/dist/web/assets/client/evidence.js +0 -172
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1161
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -63
- package/dist/web/assets/client/sections.js +0 -313
- 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 -169
- package/dist/web/assets/styles/views.js +0 -292
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
export const CONVERSATION_SCRIPT = String.raw `
|
|
2
|
+
import { h, clear } from "/assets/js/lib/dom.js";
|
|
3
|
+
import { api, releaseMutation, requestJson, submitMutation } from "/assets/js/lib/api.js";
|
|
4
|
+
import { richText } from "/assets/js/ui/text.js";
|
|
5
|
+
import { readSessionAccessMode, writeSessionAccessMode } from "/assets/js/lib/prefs.js";
|
|
6
|
+
import { inputCard } from "/assets/js/domain/work.js";
|
|
7
|
+
|
|
8
|
+
export function createConversationController(host, t, locale = function () { return "en"; }) {
|
|
9
|
+
let owner = null, selected = null, facts = null, cursor = null, next = null;
|
|
10
|
+
let generation = 0, busy = false, timer = null, pending = null, historyOk = false;
|
|
11
|
+
let recoveryUnavailable = false;
|
|
12
|
+
let rendered = new Map();
|
|
13
|
+
let materials = [], incomingMaterials = null;
|
|
14
|
+
let requestSignature = "";
|
|
15
|
+
let modelPending = null, modelCatalog = null, lastModelObservation = null;
|
|
16
|
+
const requests = h("div");
|
|
17
|
+
const questions = h("div");
|
|
18
|
+
let questionSignature = "";
|
|
19
|
+
const defaultMode = h("select", { "aria-label": t("conversation.defaultMode") },
|
|
20
|
+
h("option", { value: "structured" }, t("conversation.title")),
|
|
21
|
+
h("option", { value: "native" }, t("dock.session")));
|
|
22
|
+
defaultMode.value = readSessionAccessMode();
|
|
23
|
+
defaultMode.addEventListener("change", function () {
|
|
24
|
+
const saved = writeSessionAccessMode(defaultMode.value);
|
|
25
|
+
receipt.textContent = t(saved ? "conversation.defaultSaved" : "settings.storageFailed");
|
|
26
|
+
defaultMode.value = readSessionAccessMode();
|
|
27
|
+
});
|
|
28
|
+
const select = h("select", { "aria-label": t("conversation.sessions") });
|
|
29
|
+
const status = h("p.feed-note", { role: "status" });
|
|
30
|
+
const identity = h("p.feed-note");
|
|
31
|
+
const feed = h("div.feed", { tabIndex: 0, "aria-label": t("conversation.history") });
|
|
32
|
+
const text = h("textarea.composer-input", { rows: 3, "aria-label": t("conversation.input"), maxLength: 12000 });
|
|
33
|
+
const receipt = h("p.feed-note", { role: "status" });
|
|
34
|
+
const draftStatus = h("p.feed-note", { role: "status" });
|
|
35
|
+
const receiptDetail = h("pre");
|
|
36
|
+
const modelStatus = h("p.feed-note", { role: "status" });
|
|
37
|
+
const modelResult = h("p.feed-note", { role: "status" });
|
|
38
|
+
const modelSelect = h("select", { "aria-label": t("conversation.model") });
|
|
39
|
+
const modelLoad = h("button.btn", { type: "button" }, t("conversation.modelsLoad"));
|
|
40
|
+
const modelApply = h("button.btn", { type: "button" }, t("conversation.modelApply"));
|
|
41
|
+
const modelPanel = h("details", null, h("summary", null, t("conversation.model")),
|
|
42
|
+
modelStatus, h("div.composer-bar", null, modelLoad, modelSelect, modelApply), modelResult,
|
|
43
|
+
h("p.feed-note", null, t("conversation.modelBoundary")));
|
|
44
|
+
const slash = h("div.conversation-slash", { "aria-label": t("conversation.commands") });
|
|
45
|
+
const literal = h("button.btn", { type: "button", hidden: true }, t("conversation.literal"));
|
|
46
|
+
const send = h("button.btn.btn-primary", { type: "button" }, t("conversation.send"));
|
|
47
|
+
const stop = h("button.btn", { type: "button" }, t("conversation.stop"));
|
|
48
|
+
const check = h("button.btn", { type: "button" }, t("conversation.receipt"));
|
|
49
|
+
const older = h("button.btn", { type: "button" }, t("conversation.older"));
|
|
50
|
+
const latest = h("button.btn", { type: "button" }, t("conversation.latest"));
|
|
51
|
+
const current = h("button.btn", { type: "button" }, t("conversation.current"));
|
|
52
|
+
const moreSessions = h("button.btn", { type: "button", hidden: true }, t("conversation.moreSessions"));
|
|
53
|
+
const file = h("input", { type: "file", "aria-label": t("materials.attach") });
|
|
54
|
+
const materialList = h("div.row-stack");
|
|
55
|
+
const materialAlert = h("p.feed-note", { role: "alert" });
|
|
56
|
+
const materialBar = h("details", null, h("summary", null, t("materials.attach")), file,
|
|
57
|
+
h("p.feed-note", null, t("materials.boundary")));
|
|
58
|
+
host.append(h("div.dock-sub", null, select, current, moreSessions,
|
|
59
|
+
h("label", null, t("conversation.defaultMode"), defaultMode)), identity, status, feed,
|
|
60
|
+
h("div.conversation-requests", null, questions, requests),
|
|
61
|
+
h("div.composer-bar", null, older, latest),
|
|
62
|
+
h("div.composer-wrap", null, modelPanel, materialBar, materialList, materialAlert, text, slash, draftStatus,
|
|
63
|
+
h("div.composer-bar", null, send, stop, check, literal), receipt,
|
|
64
|
+
h("details", null, h("summary", null, t("conversation.receiptDetails")), receiptDetail),
|
|
65
|
+
h("p.feed-note", null, t("conversation.boundary"))));
|
|
66
|
+
|
|
67
|
+
function key() { return "yui.conversation." + JSON.stringify(owner) + "." + selected; }
|
|
68
|
+
function saved(suffix, value, expectedRequestId) {
|
|
69
|
+
try {
|
|
70
|
+
if (suffix === ".pending") {
|
|
71
|
+
const shared = localStorage.getItem(key() + suffix);
|
|
72
|
+
const identity = shared ? JSON.parse(shared) : null;
|
|
73
|
+
const tab = sessionStorage.getItem(key() + suffix);
|
|
74
|
+
recoveryUnavailable = false;
|
|
75
|
+
if (value === undefined) {
|
|
76
|
+
// Share only the unresolved identity, never another tab's draft.
|
|
77
|
+
return identity && (!tab || JSON.parse(tab).requestId !== identity.requestId) ? shared : tab;
|
|
78
|
+
}
|
|
79
|
+
if (value === null) {
|
|
80
|
+
if (!identity || identity.requestId === expectedRequestId) localStorage.removeItem(key() + suffix);
|
|
81
|
+
} else {
|
|
82
|
+
const record = JSON.parse(value);
|
|
83
|
+
localStorage.setItem(key() + suffix, JSON.stringify({ requestId: record.requestId, action: record.action }));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (value === undefined) return sessionStorage.getItem(key() + suffix);
|
|
87
|
+
if (value === null) sessionStorage.removeItem(key() + suffix);
|
|
88
|
+
else sessionStorage.setItem(key() + suffix, value);
|
|
89
|
+
} catch {
|
|
90
|
+
if (suffix === ".pending") recoveryUnavailable = true;
|
|
91
|
+
draftStatus.textContent = t("conversation.storageFailed");
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function url(extra) {
|
|
96
|
+
const q = new URLSearchParams({ scope: owner.scope, role: owner.roleName });
|
|
97
|
+
if (owner.scope === "task") q.set("task", owner.taskId);
|
|
98
|
+
Object.entries(extra || {}).forEach(function (entry) { if (entry[1] != null) q.set(entry[0], String(entry[1])); });
|
|
99
|
+
return "/api/conversation?" + q;
|
|
100
|
+
}
|
|
101
|
+
function controls() {
|
|
102
|
+
if (owner && selected) {
|
|
103
|
+
try { pending = JSON.parse(saved(".pending") || "null"); }
|
|
104
|
+
catch { recoveryUnavailable = true; }
|
|
105
|
+
}
|
|
106
|
+
defaultMode.value = readSessionAccessMode();
|
|
107
|
+
const active = facts && facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
|
|
108
|
+
const writable = historyOk && active && active.current && active.status === "active" && active.adapterId === "codex"
|
|
109
|
+
&& facts.authority && facts.authority.owner === "controller" && !facts.terminalWriter;
|
|
110
|
+
const turn = facts && facts.turn;
|
|
111
|
+
send.disabled = busy || recoveryUnavailable || !!pending || !!modelPending || !writable || (turn && ["submitting", "delivery-unknown"].includes(turn.status));
|
|
112
|
+
stop.disabled = busy || recoveryUnavailable || !!pending || !writable || !turn || turn.status !== "accepted" || !turn.nativeTurnId;
|
|
113
|
+
check.disabled = busy || !pending;
|
|
114
|
+
older.disabled = busy || !next;
|
|
115
|
+
literal.disabled = send.disabled;
|
|
116
|
+
modelLoad.disabled = busy || !active || active.adapterId !== "codex";
|
|
117
|
+
modelApply.disabled = busy || !!pending || !!modelPending || !writable || !modelSelect.value
|
|
118
|
+
|| !modelCatalog || modelCatalog.source === "fallback" || !!modelCatalog.failure
|
|
119
|
+
|| turn && ["submitting", "accepted", "delivery-unknown"].includes(turn.status);
|
|
120
|
+
materialBar.hidden = !owner || owner.scope !== "task";
|
|
121
|
+
file.disabled = busy || !!pending || !writable || materials.length >= 8;
|
|
122
|
+
materialList.querySelectorAll("button").forEach(function (control) { control.disabled = busy || !!pending; });
|
|
123
|
+
}
|
|
124
|
+
function showReceipt(result) {
|
|
125
|
+
const refused = result.interrupt && result.interrupt.state === "not-interrupted";
|
|
126
|
+
const state = refused ? result.interrupt.code === "DELIVERY_UNKNOWN" ? "unknown" : "failed" : result.state || "submitted";
|
|
127
|
+
receipt.textContent = t("conversation.receipt." + state)
|
|
128
|
+
+ (refused ? " · " + result.interrupt.code : "")
|
|
129
|
+
+ (result.messageId ? " · " + result.messageId : "")
|
|
130
|
+
+ (result.notDelivered ? " · " + (typeof result.notDelivered === "string" ? result.notDelivered : JSON.stringify(result.notDelivered)) : "");
|
|
131
|
+
receiptDetail.textContent = JSON.stringify(result, null, 2);
|
|
132
|
+
}
|
|
133
|
+
function showModel(observation) {
|
|
134
|
+
const axis = observation && observation.status === "observed"
|
|
135
|
+
&& observation.axes.find(function (a) { return a.key === "model"; });
|
|
136
|
+
const value = axis && axis.current.status === "observed" ? axis.current.value : null;
|
|
137
|
+
const session = facts && facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
|
|
138
|
+
modelStatus.textContent = t("conversation.launchModel") + ": " + (session && session.launchModel || "—")
|
|
139
|
+
+ " · " + t("conversation.confirmedModel") + ": " + (value || t("conversation.unknown"))
|
|
140
|
+
+ (value ? " · " + observation.observedAt : "");
|
|
141
|
+
if (modelPending) {
|
|
142
|
+
if (value === modelPending.model && observation.observedAt !== modelPending.previousObservation) {
|
|
143
|
+
modelPending = null; saved(".modelPending", null);
|
|
144
|
+
releaseMutation(url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?"));
|
|
145
|
+
} else modelStatus.textContent += " · " + t("conversation.modelUnknown");
|
|
146
|
+
}
|
|
147
|
+
lastModelObservation = value ? observation.observedAt : null;
|
|
148
|
+
}
|
|
149
|
+
async function loadModels() {
|
|
150
|
+
if (modelLoad.disabled) return;
|
|
151
|
+
const own = generation;
|
|
152
|
+
busy = true; controls();
|
|
153
|
+
try {
|
|
154
|
+
const result = await requestJson(url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?"));
|
|
155
|
+
if (own !== generation) return;
|
|
156
|
+
modelCatalog = result; clear(modelSelect);
|
|
157
|
+
(result.catalog.models || []).forEach(function (m) {
|
|
158
|
+
modelSelect.append(h("option", { value: m.value }, m.label + " · " + m.value));
|
|
159
|
+
});
|
|
160
|
+
modelResult.textContent = result.source + " · " + (result.fetchedAt || result.attemptedAt)
|
|
161
|
+
+ (result.failure ? " · " + result.failure.message : "");
|
|
162
|
+
} catch (error) { if (own === generation) modelResult.textContent = error.message; }
|
|
163
|
+
finally { finish(own); }
|
|
164
|
+
}
|
|
165
|
+
async function applyModel() {
|
|
166
|
+
if (modelApply.disabled) return;
|
|
167
|
+
const own = generation;
|
|
168
|
+
const endpoint = url({ session: selected }).replace("/api/conversation?", "/api/conversation/model?");
|
|
169
|
+
modelPending = { model: modelSelect.value, previousObservation: lastModelObservation };
|
|
170
|
+
saved(".modelPending", JSON.stringify(modelPending));
|
|
171
|
+
busy = true; controls();
|
|
172
|
+
modelResult.textContent = t("conversation.modelApplying");
|
|
173
|
+
try {
|
|
174
|
+
const result = await submitMutation(endpoint, endpoint, { model: modelPending.model });
|
|
175
|
+
if (own !== generation) return;
|
|
176
|
+
modelPending = null; saved(".modelPending", null);
|
|
177
|
+
showModel(result.runConfiguration);
|
|
178
|
+
modelResult.textContent = t("conversation.modelConfirmed");
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (own !== generation) return;
|
|
181
|
+
modelResult.textContent = error.message + " · " + t(error.disposition === "not-submitted" ? "conversation.modelFailed" : "conversation.modelUnknown");
|
|
182
|
+
if (error.disposition === "not-submitted") { modelPending = null; saved(".modelPending", null); }
|
|
183
|
+
} finally { finish(own); }
|
|
184
|
+
}
|
|
185
|
+
const commands = ["help", "status", "model", "stop", "latest"];
|
|
186
|
+
function drawCommands() {
|
|
187
|
+
clear(slash);
|
|
188
|
+
const value = text.value.trim();
|
|
189
|
+
literal.hidden = !value.startsWith("/");
|
|
190
|
+
if (!value.startsWith("/") || /\s/.test(value)) return;
|
|
191
|
+
commands.filter(function (name) { return ("/" + name).startsWith(value); }).forEach(function (name) {
|
|
192
|
+
const button = h("button.btn", { type: "button" }, "/" + name + " · " + t("conversation.command." + name));
|
|
193
|
+
button.addEventListener("click", function () {
|
|
194
|
+
text.value = "/" + name; saved(".draft", text.value); drawCommands(); text.focus?.();
|
|
195
|
+
});
|
|
196
|
+
slash.append(button);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function runCommand() {
|
|
200
|
+
const name = text.value.trim().slice(1);
|
|
201
|
+
if (!commands.includes(name)) { receipt.textContent = t("conversation.commandUnknown"); return; }
|
|
202
|
+
if (name === "stop") {
|
|
203
|
+
if (stop.disabled) receipt.textContent = t("conversation.stopUnavailable");
|
|
204
|
+
else submit(true);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (name === "model") { modelPanel.open = true; loadModels(); }
|
|
208
|
+
else if (name === "status") refresh();
|
|
209
|
+
else if (name === "latest") { cursor = null; refresh(); }
|
|
210
|
+
receipt.textContent = name === "help" ? t("conversation.commandHelp") : t("conversation.command." + name);
|
|
211
|
+
text.value = ""; saved(".draft", null); drawCommands();
|
|
212
|
+
}
|
|
213
|
+
function saveMaterials() { saved(".materials", JSON.stringify(materials)); }
|
|
214
|
+
function drawMaterials() {
|
|
215
|
+
clear(materialList);
|
|
216
|
+
materials.forEach(function (ref, index) {
|
|
217
|
+
const remove = h("button.btn", { type: "button" }, t("materials.remove"));
|
|
218
|
+
remove.disabled = busy || !!pending;
|
|
219
|
+
remove.addEventListener("click", function () { materials.splice(index, 1); saveMaterials(); drawMaterials(); controls(); });
|
|
220
|
+
materialList.append(h("div", null, h("span.small", null,
|
|
221
|
+
ref.relativePath + " · " + ref.commit + " · sha256:" + ref.digest), remove));
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function renderRequests(rows) {
|
|
225
|
+
const signature = JSON.stringify([selected, rows, !!facts.terminalWriter]);
|
|
226
|
+
if (signature === requestSignature) return;
|
|
227
|
+
requestSignature = signature; clear(requests);
|
|
228
|
+
rows.forEach(function (request) {
|
|
229
|
+
const card = h("section.composer-wrap", null, h("h3", null, t("conversation.nativeRequest")),
|
|
230
|
+
h("p.feed-note", null, request.method + " · " + request.turnId));
|
|
231
|
+
const params = request.params, answers = {};
|
|
232
|
+
const pendingKey = ".nativePending." + request.turnId + "." + request.id;
|
|
233
|
+
if (request.method === "item/tool/requestUserInput") {
|
|
234
|
+
(params.questions || []).forEach(function (question) {
|
|
235
|
+
const field = h("input", { type: question.isSecret ? "password" : "text", "aria-label": question.question });
|
|
236
|
+
const draftKey = ".nativeDraft." + request.turnId + "." + request.id + "." + question.id;
|
|
237
|
+
if (!question.isSecret) {
|
|
238
|
+
field.value = saved(draftKey) || "";
|
|
239
|
+
field.addEventListener("input", function () { saved(draftKey, field.value); });
|
|
240
|
+
}
|
|
241
|
+
card.append(h("label", null, question.question, field));
|
|
242
|
+
if (question.options) card.append(h("p.feed-note", null, question.options.map(function (o) { return o.label + ": " + o.description; }).join(" · ")));
|
|
243
|
+
answers[question.id] = field;
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
card.append(h("p", null, params.reason || params.command || t("conversation.nativeApproval")));
|
|
247
|
+
}
|
|
248
|
+
const outcome = h("p.feed-note", { role: "status" });
|
|
249
|
+
const buttons = [];
|
|
250
|
+
function button(label, result) {
|
|
251
|
+
const node = h("button.btn", { type: "button", disabled: !!facts.terminalWriter || !!saved(pendingKey) }, label);
|
|
252
|
+
buttons.push(node);
|
|
253
|
+
node.addEventListener("click", async function () {
|
|
254
|
+
const own = generation;
|
|
255
|
+
buttons.forEach(function (b) { b.disabled = true; });
|
|
256
|
+
saved(pendingKey, "pending");
|
|
257
|
+
// An answer is sent once. Unknown transport state is inspected by the
|
|
258
|
+
// next read, never retried automatically under a new request id.
|
|
259
|
+
try {
|
|
260
|
+
const endpoint = url({ session: selected });
|
|
261
|
+
await submitMutation(endpoint + "/native/" + request.id, endpoint, {
|
|
262
|
+
action: "native-respond", requestId: crypto.randomUUID(), nativeRequestId: request.id,
|
|
263
|
+
expectedTarget: request.turnId, result: result()
|
|
264
|
+
});
|
|
265
|
+
outcome.textContent = t("conversation.nativeAnswered");
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (own !== generation) return;
|
|
268
|
+
outcome.textContent = t("conversation.unknown") + " " + error.message;
|
|
269
|
+
if (error.disposition === "not-submitted") {
|
|
270
|
+
saved(pendingKey, null); buttons.forEach(function (b) { b.disabled = !!facts.terminalWriter; });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
card.append(node);
|
|
275
|
+
}
|
|
276
|
+
if (request.method === "item/tool/requestUserInput") button(t("conversation.send"), function () {
|
|
277
|
+
return { answers: Object.fromEntries(Object.entries(answers).map(function (entry) { return [entry[0], { answers: [entry[1].value] }]; })) };
|
|
278
|
+
});
|
|
279
|
+
else if (request.method === "item/permissions/requestApproval") button(t("conversation.decline"), function () { return { permissions: {}, scope: "turn" }; });
|
|
280
|
+
else if (request.method === "mcpServer/elicitation/request") {
|
|
281
|
+
card.append(h("p.feed-note", null, request.params.message || ""));
|
|
282
|
+
card.append(h("p.feed-note", null, t("conversation.nativeForm")));
|
|
283
|
+
button(t("conversation.decline"), function () { return { action: "decline", content: null }; });
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
button(t("conversation.approve"), function () { return { decision: "accept" }; });
|
|
287
|
+
button(t("conversation.decline"), function () { return { decision: "decline" }; });
|
|
288
|
+
}
|
|
289
|
+
card.append(outcome); requests.append(card);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function renderQuestions() {
|
|
293
|
+
const rows = selected === facts.currentSessionId ? facts.leaderQuestions || [] : [];
|
|
294
|
+
const signature = JSON.stringify([owner, rows, facts.questionsOmitted]);
|
|
295
|
+
if (signature === questionSignature) return;
|
|
296
|
+
questionSignature = signature; clear(questions);
|
|
297
|
+
const taskId = owner.taskId;
|
|
298
|
+
if (rows.length) questions.append(h("h3", null, t("conversation.leaderQuestions")));
|
|
299
|
+
rows.forEach(function (input) {
|
|
300
|
+
questions.append(inputCard(input, t, locale(), async function (original, answer, control) {
|
|
301
|
+
const own = generation;
|
|
302
|
+
control.disabled = true;
|
|
303
|
+
try {
|
|
304
|
+
await api.answerInput(taskId, original.id, answer);
|
|
305
|
+
releaseMutation(taskId + "/input/" + original.id);
|
|
306
|
+
if (own !== generation) return;
|
|
307
|
+
receipt.textContent = t("input.answered");
|
|
308
|
+
refresh();
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (own !== generation) return;
|
|
311
|
+
control.disabled = error.disposition !== "not-submitted";
|
|
312
|
+
receipt.textContent = error.disposition === "not-submitted" ? error.message : t("input.unknown");
|
|
313
|
+
}
|
|
314
|
+
}));
|
|
315
|
+
});
|
|
316
|
+
if (facts.questionsOmitted) questions.append(h("p.feed-note", null, t("overview.moreInputs")));
|
|
317
|
+
}
|
|
318
|
+
function choose(id) {
|
|
319
|
+
if (selected) saved(".draft", text.value);
|
|
320
|
+
generation++; selected = id; cursor = null; next = null; historyOk = false;
|
|
321
|
+
text.value = saved(".draft") || "";
|
|
322
|
+
try { pending = JSON.parse(saved(".pending") || "null"); } catch { pending = null; }
|
|
323
|
+
try { materials = JSON.parse(saved(".materials") || "[]"); } catch { materials = []; }
|
|
324
|
+
try { modelPending = JSON.parse(saved(".modelPending") || "null"); } catch { modelPending = null; }
|
|
325
|
+
modelCatalog = null; lastModelObservation = null; clear(modelSelect); modelStatus.textContent = ""; modelResult.textContent = ""; receiptDetail.textContent = "";
|
|
326
|
+
drawCommands();
|
|
327
|
+
materialAlert.textContent = "";
|
|
328
|
+
if (incomingMaterials) {
|
|
329
|
+
const combined = materials.concat(incomingMaterials).filter(function (ref, index, all) {
|
|
330
|
+
return all.findIndex(function (other) { return other.commit === ref.commit && other.relativePath === ref.relativePath; }) === index;
|
|
331
|
+
});
|
|
332
|
+
if (combined.length > 8) materialAlert.textContent = t("materials.capacity");
|
|
333
|
+
else { materials = combined; saveMaterials(); }
|
|
334
|
+
incomingMaterials = null;
|
|
335
|
+
}
|
|
336
|
+
drawMaterials();
|
|
337
|
+
receipt.textContent = pending ? t("conversation.unknown") + " " + pending.requestId : "";
|
|
338
|
+
clear(feed); rendered.clear();
|
|
339
|
+
clear(requests); requestSignature = "";
|
|
340
|
+
clear(questions); questionSignature = "";
|
|
341
|
+
controls();
|
|
342
|
+
}
|
|
343
|
+
function finish(own) {
|
|
344
|
+
busy = false; controls();
|
|
345
|
+
// A read for the old owner may have prevented the new selection's read.
|
|
346
|
+
// Only refresh view state; never repeat a mutation or receipt submission.
|
|
347
|
+
if (own !== generation && timer !== null) refresh();
|
|
348
|
+
}
|
|
349
|
+
function renderItems(items) {
|
|
350
|
+
const nodes = [], updated = new Map();
|
|
351
|
+
items.slice().reverse().forEach(function (item) {
|
|
352
|
+
const key = JSON.stringify([item.turnId, item.id]), signature = JSON.stringify(item);
|
|
353
|
+
const previous = rendered.get(key);
|
|
354
|
+
let node = previous && previous.signature === signature ? previous.node : null;
|
|
355
|
+
if (!node) {
|
|
356
|
+
const content = richText(null, item.text, t, { threshold: 1800 });
|
|
357
|
+
const meta = (item.source === "yui-input" ? t("conversation.linkedInput") + " " + item.messageId : item.kind)
|
|
358
|
+
+ " · " + item.turnId + (item.status ? " · " + item.status : "");
|
|
359
|
+
node = item.kind === "activity" ? h("details", null, h("summary", null, meta), content)
|
|
360
|
+
: h("article.msg.from-" + (item.kind === "user" ? "user" : "role"), null,
|
|
361
|
+
h("div.msg-main", null, h("header.msg-head", null, meta), h("div.msg-bubble", null, content),
|
|
362
|
+
item.truncated ? h("p.feed-note", null, t("conversation.truncated")) : null));
|
|
363
|
+
if (previous) {
|
|
364
|
+
if (item.kind === "activity") node.open = previous.node.open;
|
|
365
|
+
const oldProse = previous.node.querySelectorAll(".prose-block");
|
|
366
|
+
node.querySelectorAll(".prose-block").forEach(function (prose, index) {
|
|
367
|
+
if (oldProse[index] && !oldProse[index].classList.contains("is-collapsed")
|
|
368
|
+
&& prose.classList.contains("is-collapsed")) prose.querySelector("button.link-btn")?.click();
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
updated.set(key, { signature: signature, node: node }); nodes.push(node);
|
|
373
|
+
});
|
|
374
|
+
rendered = updated;
|
|
375
|
+
if (!nodes.length) nodes.push(h("p.feed-note", null, t("conversation.empty")));
|
|
376
|
+
if (feed.children.length !== nodes.length || nodes.some(function (n, i) { return feed.children[i] !== n; })) {
|
|
377
|
+
feed.replaceChildren(...nodes);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function drawSessions(rows, append) {
|
|
381
|
+
if (!append) clear(select);
|
|
382
|
+
rows.forEach(function (s) {
|
|
383
|
+
if ([...select.options].some(function (o) { return o.value === s.nativeSessionId; })) return;
|
|
384
|
+
select.append(h("option", { value: s.nativeSessionId },
|
|
385
|
+
(s.current ? t("conversation.current") : t("conversation.previous")) + " · " + s.executionAuthority + " · " + s.nativeSessionId));
|
|
386
|
+
});
|
|
387
|
+
select.value = selected || "";
|
|
388
|
+
moreSessions.hidden = !facts.nextOffset;
|
|
389
|
+
}
|
|
390
|
+
async function refresh() {
|
|
391
|
+
if (!owner || busy || host.hidden || host.closest("[hidden]") || document.hidden) return;
|
|
392
|
+
busy = true; controls();
|
|
393
|
+
let own = generation;
|
|
394
|
+
try {
|
|
395
|
+
const result = await requestJson(url());
|
|
396
|
+
if (own !== generation) return;
|
|
397
|
+
const previous = facts;
|
|
398
|
+
facts = result;
|
|
399
|
+
if (previous && previous.total === facts.total) facts.nextOffset = previous.nextOffset;
|
|
400
|
+
// Previously discovered old Sessions remain view metadata, not current
|
|
401
|
+
// authority. Refresh current flags from the newest canonical selection.
|
|
402
|
+
if (previous) previous.sessions.forEach(function (s) {
|
|
403
|
+
if (!facts.sessions.some(function (n) { return n.nativeSessionId === s.nativeSessionId; })) {
|
|
404
|
+
facts.sessions.push({ ...s, current: s.nativeSessionId === facts.currentSessionId });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
if (!selected) {
|
|
408
|
+
choose(facts.currentSessionId);
|
|
409
|
+
own = generation;
|
|
410
|
+
}
|
|
411
|
+
if (!selected) { status.textContent = t("conversation.noSession"); return; }
|
|
412
|
+
// Preserve a historical choice even if it is outside the newest page.
|
|
413
|
+
drawSessions(facts.sessions, false);
|
|
414
|
+
renderQuestions();
|
|
415
|
+
const page = await requestJson(url({ session: selected, cursor: cursor }));
|
|
416
|
+
if (own !== generation) return;
|
|
417
|
+
historyOk = true; next = page.nextCursor;
|
|
418
|
+
const turn = selected === facts.currentSessionId ? facts.turn : null;
|
|
419
|
+
let state = page.status === "waiting-user" ? "waiting" : page.status === "active" ? "active"
|
|
420
|
+
: page.status === "systemError" ? "failed" : page.status === "idle" ? "waiting" : "unknown";
|
|
421
|
+
const session = facts.sessions.find(function (s) { return s.nativeSessionId === selected; });
|
|
422
|
+
if (session && session.status === "ended") state = session.endReason === "failed" ? "failed" : "ended";
|
|
423
|
+
if (turn && turn.status === "delivery-unknown") state = "unknown";
|
|
424
|
+
status.textContent = t("conversation." + state) + " · " + page.observedAt;
|
|
425
|
+
if (page.live === "unavailable" && !cursor) status.textContent += " · " + t("conversation.liveUnavailable");
|
|
426
|
+
if (page.linkedInputsOmitted) status.textContent += " · " + t("conversation.linkedOmitted");
|
|
427
|
+
if (page.nativeRequestsOmitted) status.textContent += " · " + t("conversation.nativeOmitted");
|
|
428
|
+
status.textContent += " · " + t(facts.terminalWriter ? "conversation.terminalWriter" : "conversation.structuredWriter");
|
|
429
|
+
renderRequests(page.nativeRequests || []);
|
|
430
|
+
showModel(page.runConfiguration);
|
|
431
|
+
identity.textContent = (owner.taskId || "Global") + " / " + owner.roleName + " / " + selected
|
|
432
|
+
+ (turn ? " / " + (turn.nativeTurnId || turn.attemptId) + (turn.runId ? " / " + turn.runId : "") : "");
|
|
433
|
+
const scroll = feed.scrollTop, atEnd = feed.scrollHeight - feed.clientHeight - scroll < 40;
|
|
434
|
+
renderItems(page.items);
|
|
435
|
+
feed.scrollTop = atEnd ? feed.scrollHeight : scroll;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (own === generation) { historyOk = false; status.textContent = t("conversation.disconnected") + " · " + error.message; }
|
|
438
|
+
} finally { finish(own); }
|
|
439
|
+
if (own === generation && pending && historyOk) await inspectReceipt();
|
|
440
|
+
}
|
|
441
|
+
async function inspectReceipt() {
|
|
442
|
+
if (!pending || busy) return;
|
|
443
|
+
const own = generation;
|
|
444
|
+
const inspected = pending;
|
|
445
|
+
busy = true; controls();
|
|
446
|
+
try {
|
|
447
|
+
const result = await requestJson(url({ requestId: inspected.requestId }));
|
|
448
|
+
if (own !== generation) return;
|
|
449
|
+
showReceipt(result);
|
|
450
|
+
if (["accepted", "failed"].includes(result.state)) {
|
|
451
|
+
const submitted = inspected;
|
|
452
|
+
pending = null; saved(".pending", null, submitted.requestId);
|
|
453
|
+
if (result.state === "accepted" && submitted.action !== "interrupt" && text.value === submitted.body) {
|
|
454
|
+
text.value = ""; saved(".draft", null);
|
|
455
|
+
drawCommands();
|
|
456
|
+
materials = materials.filter(function (ref) {
|
|
457
|
+
return !(submitted.materials || []).some(function (sent) {
|
|
458
|
+
return sent.commit === ref.commit && sent.relativePath === ref.relativePath;
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
saveMaterials(); drawMaterials();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} catch (error) { if (own === generation) receipt.textContent = t("conversation.unknown") + " " + error.message; }
|
|
465
|
+
finally { finish(own); }
|
|
466
|
+
}
|
|
467
|
+
async function submit(interrupt, asLiteral) {
|
|
468
|
+
controls(); // Another tab may have acquired an unresolved input since refresh.
|
|
469
|
+
if (busy || pending || (interrupt ? stop.disabled : send.disabled)) return;
|
|
470
|
+
if (!interrupt && !text.value.trim()) return;
|
|
471
|
+
if (!interrupt && !asLiteral && text.value.trim().startsWith("/")) { runCommand(); return; }
|
|
472
|
+
const turn = facts.turn;
|
|
473
|
+
const action = interrupt ? "interrupt" : turn && turn.status === "accepted" ? "steer" : "queue";
|
|
474
|
+
const payload = { action: action, requestId: crypto.randomUUID() };
|
|
475
|
+
if (!interrupt) payload.body = text.value;
|
|
476
|
+
if (!interrupt && materials.length) payload.materials = materials.map(function (ref) {
|
|
477
|
+
return { taskId: ref.taskId, relativePath: ref.relativePath, commit: ref.commit, digest: ref.digest };
|
|
478
|
+
});
|
|
479
|
+
if (action !== "queue") payload.expectedTarget = turn.nativeTurnId || turn.attemptId;
|
|
480
|
+
pending = { requestId: payload.requestId, action: action, body: payload.body, materials: payload.materials };
|
|
481
|
+
saved(".pending", JSON.stringify(pending)); saved(".draft", text.value);
|
|
482
|
+
if (recoveryUnavailable) { controls(); return; }
|
|
483
|
+
const own = generation, endpoint = url({ session: selected });
|
|
484
|
+
busy = true; controls();
|
|
485
|
+
receipt.textContent = t("conversation.receipt.sending");
|
|
486
|
+
try {
|
|
487
|
+
const result = await submitMutation(endpoint + "/" + payload.requestId, endpoint, payload);
|
|
488
|
+
if (own !== generation) return;
|
|
489
|
+
showReceipt(result);
|
|
490
|
+
if (action === "interrupt" && result.interrupt?.state === "not-interrupted"
|
|
491
|
+
&& ["NO_ACTIVE_TURN", "TARGET_CHANGED", "INTERRUPT_UNSUPPORTED"].includes(result.interrupt.code)) {
|
|
492
|
+
// Preflight proved no native cancel was submitted, so no durable
|
|
493
|
+
// interrupt receipt exists. DELIVERY_UNKNOWN must stay unresolved.
|
|
494
|
+
pending = null; saved(".pending", null, payload.requestId);
|
|
495
|
+
}
|
|
496
|
+
// Saved/queued is not accepted. Only an exact receipt lookup settles it.
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (own !== generation) return;
|
|
499
|
+
receipt.textContent = t(error.disposition === "not-submitted" ? "conversation.receipt.failed" : "conversation.unknown") + " " + error.message;
|
|
500
|
+
if (error.disposition === "not-submitted") { pending = null; saved(".pending", null, payload.requestId); }
|
|
501
|
+
} finally { finish(own); }
|
|
502
|
+
if (own === generation) await inspectReceipt();
|
|
503
|
+
}
|
|
504
|
+
text.addEventListener("input", function () {
|
|
505
|
+
if (selected) { draftStatus.textContent = t("conversation.draftSaved"); saved(".draft", text.value); }
|
|
506
|
+
drawCommands();
|
|
507
|
+
});
|
|
508
|
+
text.addEventListener("keydown", function (event) {
|
|
509
|
+
if (event.isComposing) return;
|
|
510
|
+
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { event.preventDefault(); submit(false); }
|
|
511
|
+
if (event.key === "Tab" && text.value.startsWith("/")) {
|
|
512
|
+
const matches = commands.filter(function (name) { return ("/" + name).startsWith(text.value); });
|
|
513
|
+
if (matches.length === 1) {
|
|
514
|
+
event.preventDefault(); text.value = "/" + matches[0]; saved(".draft", text.value); drawCommands();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
modelLoad.addEventListener("click", loadModels);
|
|
519
|
+
modelSelect.addEventListener("change", controls);
|
|
520
|
+
modelApply.addEventListener("click", applyModel);
|
|
521
|
+
literal.addEventListener("click", function () { submit(false, true); });
|
|
522
|
+
file.addEventListener("change", async function () {
|
|
523
|
+
const chosen = file.files[0];
|
|
524
|
+
if (!chosen || file.disabled) return;
|
|
525
|
+
const own = generation;
|
|
526
|
+
const endpoint = url({ session: selected }).replace("/api/conversation?", "/api/conversation/material?");
|
|
527
|
+
const requestId = crypto.randomUUID();
|
|
528
|
+
busy = true; controls();
|
|
529
|
+
try {
|
|
530
|
+
if (chosen.size > 256 * 1024) throw Object.assign(new Error(t("materials.tooLarge")), { disposition: "not-submitted" });
|
|
531
|
+
const content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(await chosen.arrayBuffer());
|
|
532
|
+
if (own !== generation) return;
|
|
533
|
+
const ref = await submitMutation(endpoint + "/" + requestId, endpoint,
|
|
534
|
+
{ requestId: requestId, name: chosen.name, content: content });
|
|
535
|
+
if (own !== generation) return;
|
|
536
|
+
materials.push(ref); saveMaterials(); drawMaterials();
|
|
537
|
+
receipt.textContent = t("materials.saved");
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (own === generation) receipt.textContent = error.message + " · " + t("materials.inspectUpload")
|
|
540
|
+
+ " materials/" + requestId + "/" + chosen.name;
|
|
541
|
+
} finally { file.value = ""; finish(own); drawMaterials(); }
|
|
542
|
+
});
|
|
543
|
+
select.addEventListener("change", function () { choose(select.value); refresh(); });
|
|
544
|
+
current.addEventListener("click", function () { if (facts) { choose(facts.currentSessionId); refresh(); } });
|
|
545
|
+
older.addEventListener("click", function () { cursor = next; refresh(); });
|
|
546
|
+
latest.addEventListener("click", function () { cursor = null; refresh(); });
|
|
547
|
+
send.addEventListener("click", function () { submit(false); });
|
|
548
|
+
stop.addEventListener("click", function () { submit(true); });
|
|
549
|
+
check.addEventListener("click", inspectReceipt);
|
|
550
|
+
moreSessions.addEventListener("click", async function () {
|
|
551
|
+
if (busy || !facts.nextOffset) return;
|
|
552
|
+
const own = generation;
|
|
553
|
+
busy = true;
|
|
554
|
+
try {
|
|
555
|
+
const page = await requestJson(url({ offset: facts.nextOffset }));
|
|
556
|
+
if (own !== generation) return;
|
|
557
|
+
facts.sessions = facts.sessions.concat(page.sessions); facts.nextOffset = page.nextOffset;
|
|
558
|
+
drawSessions(page.sessions, true);
|
|
559
|
+
} catch (error) { status.textContent = error.message; }
|
|
560
|
+
finally { finish(own); }
|
|
561
|
+
});
|
|
562
|
+
controls();
|
|
563
|
+
return {
|
|
564
|
+
captureDraftTarget: function () {
|
|
565
|
+
controls();
|
|
566
|
+
return send.disabled || timer === null || host.hidden || host.closest("[hidden]")
|
|
567
|
+
? null : JSON.stringify([owner, selected]);
|
|
568
|
+
},
|
|
569
|
+
insertReference: function (target, citation) {
|
|
570
|
+
controls();
|
|
571
|
+
if (!target || target !== JSON.stringify([owner, selected]) || send.disabled
|
|
572
|
+
|| timer === null || host.hidden || host.closest("[hidden]")) {
|
|
573
|
+
throw new Error(t("bodySearch.targetChanged"));
|
|
574
|
+
}
|
|
575
|
+
const combined = text.value + (text.value ? "\n\n" : "") + citation;
|
|
576
|
+
if (combined.length > text.maxLength) throw new Error(t("bodySearch.capacity"));
|
|
577
|
+
text.value = combined;
|
|
578
|
+
saved(".draft", text.value);
|
|
579
|
+
drawCommands();
|
|
580
|
+
},
|
|
581
|
+
current: function () { return owner; },
|
|
582
|
+
selection: function () {
|
|
583
|
+
if (!owner || (selected && facts && selected !== facts.currentSessionId)) return null;
|
|
584
|
+
return { ...owner, ...(selected ? { nativeSessionId: selected } : {}) };
|
|
585
|
+
},
|
|
586
|
+
reconnect: function () {
|
|
587
|
+
if (!timer) timer = window.setInterval(function () { if (!cursor) refresh(); }, 2000);
|
|
588
|
+
refresh();
|
|
589
|
+
},
|
|
590
|
+
open: function (target, options) {
|
|
591
|
+
// Explicit workbench continuation must resolve today's current Session
|
|
592
|
+
// from the shared endpoint, not retain a historical selection or stale ID.
|
|
593
|
+
if (JSON.stringify(target) !== JSON.stringify(owner) || options && options.current) {
|
|
594
|
+
if (selected) saved(".draft", text.value);
|
|
595
|
+
generation++; owner = target; selected = null; facts = null; pending = null; clear(select); clear(feed);
|
|
596
|
+
cursor = null; next = null; historyOk = false; rendered.clear();
|
|
597
|
+
text.value = ""; receipt.textContent = ""; identity.textContent = ""; controls();
|
|
598
|
+
modelPending = null; modelCatalog = null; clear(modelSelect); modelStatus.textContent = "";
|
|
599
|
+
modelResult.textContent = "";
|
|
600
|
+
receiptDetail.textContent = ""; draftStatus.textContent = ""; drawCommands();
|
|
601
|
+
materials = []; incomingMaterials = null; clear(materialList); materialAlert.textContent = "";
|
|
602
|
+
clear(requests); requestSignature = "";
|
|
603
|
+
clear(questions); questionSignature = "";
|
|
604
|
+
}
|
|
605
|
+
if (options && options.materials) incomingMaterials = options.materials;
|
|
606
|
+
if (!timer) timer = window.setInterval(function () { if (!cursor) refresh(); }, 2000);
|
|
607
|
+
refresh();
|
|
608
|
+
},
|
|
609
|
+
close: function () { if (timer) window.clearInterval(timer); timer = null; generation++; }
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
`;
|