@zq-silk/yui 2.3.0 → 2.5.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.
Files changed (197) hide show
  1. package/README.md +15 -1
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +18 -9
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +65 -14
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +18 -3
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +39 -8
  23. package/dist/controller/globalInputDelivery.js +2 -4
  24. package/dist/controller/jobControl.js +2 -0
  25. package/dist/controller/nativeHostInput.js +37 -0
  26. package/dist/controller/providerRetryDelivery.js +4 -2
  27. package/dist/controller/runtime.js +93 -2
  28. package/dist/controller/runtimeEventInbox.js +2 -2
  29. package/dist/controller/runtimeEventProcessor.js +3 -3
  30. package/dist/doctor/doctor.js +10 -0
  31. package/dist/execution/reviewMainRun.js +1 -0
  32. package/dist/execution/workItemMainRun.js +1 -0
  33. package/dist/executor/agentAdapter.js +3 -5
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +15 -7
  37. package/dist/integration/gitIntegrationService.js +7 -12
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/interaction/notificationPresentation.js +85 -0
  40. package/dist/interaction/operatorPresentation.js +1 -2
  41. package/dist/job/httpPreview.js +61 -0
  42. package/dist/job/jobRunner.js +12 -2
  43. package/dist/kernel/builtinCapabilities.js +3 -1
  44. package/dist/message/message.js +15 -1
  45. package/dist/output/rolePresentation.js +4 -1
  46. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  47. package/dist/repository/gitCompatibility.js +62 -0
  48. package/dist/repository/gitWorkspace.js +48 -71
  49. package/dist/repository/gitWorktreeInventory.js +121 -0
  50. package/dist/repository/project.js +74 -25
  51. package/dist/resources/resourceDiscovery.js +2 -2
  52. package/dist/resources/resourceGc.js +7 -4
  53. package/dist/resources/resourceRegistrar.js +2 -1
  54. package/dist/runtime/agentEndpoint.js +8 -0
  55. package/dist/runtime/agentHost.js +170 -3
  56. package/dist/runtime/codexAppServerRuntime.js +1 -1
  57. package/dist/runtime/codexNativeAccess.js +175 -0
  58. package/dist/runtime/controlledCodexTui.js +116 -0
  59. package/dist/runtime/publicReply.js +23 -0
  60. package/dist/runtime/runtimeObservation.js +48 -0
  61. package/dist/runtime/structuredProviderHost.js +16 -7
  62. package/dist/runtime/taskCostMetrics.js +133 -0
  63. package/dist/runtime/taskUsageMetrics.js +11 -2
  64. package/dist/runtime/taskUsageQuery.js +38 -0
  65. package/dist/scheduler/leaderWakeupProcessor.js +1 -9
  66. package/dist/scheduler/taskExecutionProjection.js +2 -0
  67. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  68. package/dist/storage/homeLayout.js +13 -0
  69. package/dist/storage/sqliteSchema.js +44 -0
  70. package/dist/storage/sqliteStore.js +53 -0
  71. package/dist/storage/storageVersions.js +1 -1
  72. package/dist/storage/storeRpc.js +2 -0
  73. package/dist/storage/taskSearch.js +35 -0
  74. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  75. package/dist/task/ordinaryArchive.js +42 -0
  76. package/dist/tmux/tmuxManager.js +8 -8
  77. package/dist/web/assets/assetManifest.js +11 -51
  78. package/dist/web/assets/client/app/catalog.js +215 -0
  79. package/dist/web/assets/client/app/main.js +148 -0
  80. package/dist/web/assets/client/app/selection.js +139 -0
  81. package/dist/web/assets/client/app/settings.js +326 -0
  82. package/dist/web/assets/client/app/taskView.js +280 -0
  83. package/dist/web/assets/client/domain/context.js +96 -0
  84. package/dist/web/assets/client/domain/records.js +126 -0
  85. package/dist/web/assets/client/domain/runs.js +119 -0
  86. package/dist/web/assets/client/domain/taskForms.js +197 -0
  87. package/dist/web/assets/client/domain/vocab.js +69 -0
  88. package/dist/web/assets/client/domain/work.js +116 -0
  89. package/dist/web/assets/client/i18n/en.js +900 -0
  90. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  91. package/dist/web/assets/client/index.js +114 -0
  92. package/dist/web/assets/client/layout/resizer.js +37 -0
  93. package/dist/web/assets/client/layout/router.js +35 -0
  94. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  95. package/dist/web/assets/client/layout/sizing.js +95 -0
  96. package/dist/web/assets/client/layout/workspace.js +247 -0
  97. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  98. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  99. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  100. package/dist/web/assets/client/lib/i18n.js +54 -0
  101. package/dist/web/assets/client/lib/markdown.js +167 -0
  102. package/dist/web/assets/client/lib/prefs.js +28 -0
  103. package/dist/web/assets/client/lib/theme.js +68 -0
  104. package/dist/web/assets/client/ui/containers.js +101 -0
  105. package/dist/web/assets/client/ui/controls.js +51 -0
  106. package/dist/web/assets/client/ui/forms.js +48 -0
  107. package/dist/web/assets/client/ui/metrics.js +137 -0
  108. package/dist/web/assets/client/ui/primitives.js +92 -0
  109. package/dist/web/assets/client/ui/text.js +33 -0
  110. package/dist/web/assets/client/ui/toast.js +12 -0
  111. package/dist/web/assets/client/views/bodySearch.js +172 -0
  112. package/dist/web/assets/client/views/createTask.js +85 -0
  113. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  114. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  115. package/dist/web/assets/client/views/dock/preview.js +129 -0
  116. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  117. package/dist/web/assets/client/views/globalInput.js +67 -0
  118. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  119. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  120. package/dist/web/assets/client/views/skills.js +152 -0
  121. package/dist/web/assets/client/views/task/actions.js +36 -0
  122. package/dist/web/assets/client/views/task/delivery.js +185 -0
  123. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  124. package/dist/web/assets/client/views/task/files.js +272 -0
  125. package/dist/web/assets/client/views/task/observation.js +153 -0
  126. package/dist/web/assets/client/views/task/overview.js +161 -0
  127. package/dist/web/assets/client/views/task/page.js +131 -0
  128. package/dist/web/assets/client/views/task/records.js +163 -0
  129. package/dist/web/assets/client/views/task/runtime.js +130 -0
  130. package/dist/web/assets/client/views/task/timeline.js +190 -0
  131. package/dist/web/assets/shared/geometry.js +19 -0
  132. package/dist/web/assets/shared/taskTabs.js +8 -0
  133. package/dist/web/assets/shared/themes.js +93 -0
  134. package/dist/web/assets/shell/dialogs.js +63 -0
  135. package/dist/web/assets/shell/dock.js +40 -0
  136. package/dist/web/assets/shell/index.js +40 -0
  137. package/dist/web/assets/shell/settings.js +34 -0
  138. package/dist/web/assets/shell/sidebar.js +45 -0
  139. package/dist/web/assets/styles/index.js +41 -0
  140. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  141. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  142. package/dist/web/assets/styles/markdown.js +22 -3
  143. package/dist/web/assets/styles/tokens.js +10 -63
  144. package/dist/web/assets/styles/ui/controls.js +76 -0
  145. package/dist/web/assets/styles/ui/forms.js +46 -0
  146. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  147. package/dist/web/assets/styles/views/delivery.js +29 -0
  148. package/dist/web/assets/styles/views/dock.js +71 -0
  149. package/dist/web/assets/styles/views/page.js +32 -0
  150. package/dist/web/assets/styles/views/records.js +70 -0
  151. package/dist/web/assets/styles/views/settings.js +26 -0
  152. package/dist/web/assets/styles/views/sidebar.js +74 -0
  153. package/dist/web/assets/styles/views/task.js +80 -0
  154. package/dist/web/assets/styles/views/terminal.js +38 -0
  155. package/dist/web/taskPreviews.js +205 -0
  156. package/dist/web/tmuxWebTerminal.js +16 -9
  157. package/dist/web/webConversation.js +373 -0
  158. package/dist/web/webMaterials.js +155 -0
  159. package/dist/web/webServer.js +295 -18
  160. package/dist/web/webSettings.js +331 -0
  161. package/dist/web/webSkills.js +86 -0
  162. package/dist/web/webSnapshot.js +40 -8
  163. package/dist/web/webTaskSurface.js +106 -8
  164. package/dist/workspace/gitChangeSetCapture.js +3 -1
  165. package/docs/cli-information-contract.md +1 -1
  166. package/docs/cli-information-contract.zh-CN.md +1 -1
  167. package/docs/managed-turn-and-session-runtime.md +31 -0
  168. package/docs/managed-turn-and-session-runtime.zh-CN.md +22 -0
  169. package/docs/release-workflow.md +54 -0
  170. package/docs/release-workflow.zh-CN.md +39 -0
  171. package/docs/storage-baseline.md +38 -1
  172. package/docs/storage-baseline.zh-CN.md +22 -0
  173. package/docs/task-discovery.md +28 -0
  174. package/docs/testing/verification-levels.md +2 -2
  175. package/i18n/README.zh-CN.md +1 -1
  176. package/package.json +1 -1
  177. package/skills/yui-leader/SKILL.md +5 -4
  178. package/skills/yui-leader/references/execution.md +2 -1
  179. package/skills/yui-operator/SKILL.md +3 -0
  180. package/skills/yui-reviewer/SKILL.md +4 -2
  181. package/skills/yui-runtime/SKILL.md +43 -2
  182. package/skills/yui-worker/SKILL.md +3 -2
  183. package/dist/web/assets/client/app.js +0 -808
  184. package/dist/web/assets/client/components.js +0 -477
  185. package/dist/web/assets/client/detail.js +0 -379
  186. package/dist/web/assets/client/dock.js +0 -309
  187. package/dist/web/assets/client/evidence.js +0 -364
  188. package/dist/web/assets/client/forms.js +0 -230
  189. package/dist/web/assets/client/i18n.js +0 -1277
  190. package/dist/web/assets/client/markdown.js +0 -114
  191. package/dist/web/assets/client/records.js +0 -198
  192. package/dist/web/assets/client/sections.js +0 -349
  193. package/dist/web/assets/client/theme.js +0 -71
  194. package/dist/web/assets/shell.js +0 -161
  195. package/dist/web/assets/styles/components.js +0 -216
  196. package/dist/web/assets/styles/views.js +0 -309
  197. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -0,0 +1,35 @@
1
+ export const TASK_SEARCH_KINDS = ["brief", "message", "decision", "result", "completion"];
2
+ /** A read-only view of authoritative JSON, not a persisted index. Apply Task
3
+ * scope before body matching and return only page-sized snippets/identities.
4
+ * No derived state needs rebuilding when a source changes or is removed. */
5
+ export function queryTaskSearch(db, input) {
6
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 101) {
7
+ throw new Error("Task search query exceeds its bounded contract.");
8
+ }
9
+ const branches = [
10
+ `SELECT task_id AS taskId, 'brief' AS kind, 'task-brief' AS store, task_id AS refId,
11
+ j.fullkey AS field, j.value AS body FROM task_records, json_tree(brief) j
12
+ WHERE j.type = 'text' AND (j.key IN ('objective','technicalApproach','currentFocus','leaderSummary')
13
+ OR j.path = '$.boundaries')`,
14
+ `SELECT task_id, 'message', 'task-message', message_id, '$.body', json_extract(payload, '$.body') FROM messages`,
15
+ `SELECT task_id, 'decision', 'task-decision', decision_id, j.fullkey, j.value
16
+ FROM decisions, json_each(payload) j WHERE j.key IN ('title','rationale','supersededReason') AND j.type = 'text'`,
17
+ `SELECT task_id, 'result', 'run', turn_id, '$.result.output', json_extract(payload, '$.result.output') FROM turns`,
18
+ `SELECT task_id, 'completion', 'task', task_id, '$.completionSummary',
19
+ json_extract(payload, '$.completionSummary') FROM task_records`
20
+ ];
21
+ const params = {
22
+ query: input.query, taskId: input.taskId ?? null, kind: input.kind ?? null, limit: input.limit,
23
+ afterTask: input.after?.taskId ?? "", afterKind: input.after?.kind ?? "",
24
+ afterRef: input.after?.refId ?? "", afterField: input.after?.field ?? ""
25
+ };
26
+ // SQLite lower() folds ASCII only; other Unicode is matched literally.
27
+ // Positions and substr use Unicode code points, not UTF-16 units or bytes.
28
+ return db.prepare(`WITH sources AS (${branches.join(" UNION ALL ")}),
29
+ matches AS (SELECT *, instr(lower(body), lower(@query)) AS hit FROM sources
30
+ WHERE (@taskId IS NULL OR taskId = @taskId) AND (@kind IS NULL OR kind = @kind)
31
+ AND (taskId, kind, refId, field) > (@afterTask, @afterKind, @afterRef, @afterField))
32
+ SELECT taskId, kind, store, refId, field, max(0, hit - 81) AS offset,
33
+ substr(body, max(1, hit - 80), 320) AS snippet
34
+ FROM matches WHERE hit > 0 ORDER BY taskId, kind, refId, field LIMIT @limit`).all(params);
35
+ }
@@ -0,0 +1,52 @@
1
+ import { createHash } from "node:crypto";
2
+ import { knowledgeProposalFingerprint, validateProject } from "../../repository/project.js";
3
+ /**
4
+ * The declared 1.3 -> 1.4 payload transition. Never used by ordinary readers.
5
+ * Version 1 is the observed pre-migration head, not a claim that no earlier
6
+ * edits happened. Existing proposals and frozen Context snapshots retain their
7
+ * original identities, decisions and evidence; snapshots are never rewritten.
8
+ */
9
+ export function migrateProjectKnowledge(db) {
10
+ const rows = db.prepare("SELECT id, payload FROM projects ORDER BY id").all();
11
+ for (const row of rows) {
12
+ const project = JSON.parse(row.payload);
13
+ if (project.id !== row.id || !Array.isArray(project.knowledge) || !Array.isArray(project.knowledgeProposals)) {
14
+ throw new Error(`Invalid 1.3 Project knowledge container: ${row.id}.`);
15
+ }
16
+ const knowledgeProposals = project.knowledgeProposals.map(proposal => {
17
+ if (proposal.schemaVersion !== 1)
18
+ throw new Error(`Invalid 1.3 Knowledge proposal: ${proposal.id}.`);
19
+ const sourceKey = [proposal.projectId, proposal.source.taskId, proposal.source.decisionId ?? "",
20
+ proposal.source.milestoneId ?? "", proposal.source.commitSha ?? ""].join("|");
21
+ const previousFingerprint = createHash("sha256")
22
+ .update(`${sourceKey}\u0000${proposal.title}\u0000${proposal.body}`).digest("hex");
23
+ if (proposal.fingerprint !== previousFingerprint) {
24
+ throw new Error(`Invalid 1.3 Knowledge fingerprint: ${proposal.id}.`);
25
+ }
26
+ return { ...proposal, schemaVersion: 2, fingerprint: knowledgeProposalFingerprint(proposal) };
27
+ });
28
+ const knowledge = project.knowledge.map(entry => {
29
+ if (entry.schemaVersion !== 1 || "version" in entry || "history" in entry
30
+ || "scope" in entry || "expiresWhen" in entry) {
31
+ throw new Error(`Invalid 1.3 Project knowledge: ${entry.id}.`);
32
+ }
33
+ const proposal = knowledgeProposals.find(candidate => candidate.id === entry.provenance?.proposalId);
34
+ const backed = proposal !== undefined && proposal.status === "accepted"
35
+ && proposal.knowledgeId === entry.id && proposal.title === entry.title && proposal.body === entry.body
36
+ && proposal.source.taskId === entry.provenance?.taskId
37
+ && proposal.source.decisionId === entry.provenance?.decisionId
38
+ && proposal.source.milestoneId === entry.provenance?.milestoneId
39
+ && proposal.source.commitSha === entry.provenance?.commitSha;
40
+ return {
41
+ ...entry, schemaVersion: 2, version: 1, history: [],
42
+ ...(backed ? {
43
+ ...(proposal.scope === undefined ? {} : { scope: proposal.scope }),
44
+ ...(proposal.expiresWhen === undefined ? {} : { expiresWhen: proposal.expiresWhen }),
45
+ provenance: { ...entry.provenance, fingerprint: proposal.fingerprint }
46
+ } : {})
47
+ };
48
+ });
49
+ const migrated = validateProject({ ...project, knowledge, knowledgeProposals });
50
+ db.prepare("UPDATE projects SET payload=? WHERE id=?").run(JSON.stringify(migrated), row.id);
51
+ }
52
+ }
@@ -0,0 +1,42 @@
1
+ import { runTaskCommand, validateTaskArchiveRequest } from "../commands/taskCommands.js";
2
+ import { taskArchiveDiagnostics } from "./archiveDiagnostics.js";
3
+ import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./remoteDeliveryService.js";
4
+ /** Read-only admission shared by CLI and Web. Cancelled Project Tasks need a
5
+ * workspace snapshot before delivery can be proved; that later preparation
6
+ * may have effects and must not be reported as a known non-submission. */
7
+ export function validateOrdinaryTaskArchive(store, taskId, options) {
8
+ validateTaskArchiveRequest([taskId, "--integrated"], store, options);
9
+ const task = store.getTask(taskId);
10
+ if (task.status !== "archived" && (task.status === "completed" || !task.projectBindings.length)) {
11
+ assertTaskRemoteDeliveryIntegrated(createTaskRemoteDeliveryProof(store, task).delivery);
12
+ }
13
+ }
14
+ /** The local user's ordinary archive path. No force, abandonment, source
15
+ * impersonation, or private cleanup policy is accepted by this port. */
16
+ export async function archiveOrdinaryTask(coordinator, taskId, options) {
17
+ const { store, preparer } = coordinator;
18
+ const args = [taskId, "--integrated"];
19
+ validateOrdinaryTaskArchive(store, taskId, options);
20
+ const task = store.getTask(taskId);
21
+ if (task.status !== "archived") {
22
+ let candidate = null;
23
+ if (task.status === "cancelled" && task.projectBindings.length) {
24
+ await preparer.prepareTaskWorkspace(taskId);
25
+ const workspace = store.getTaskWorkspace(taskId);
26
+ if (!workspace || workspace.owner.type !== "task" || workspace.owner.taskId !== taskId) {
27
+ throw new Error(`Task has no authoritative main workspace: ${taskId}.`);
28
+ }
29
+ const snapshot = await preparer.snapshotDirectTaskMain(workspace, task.projectBindings.map(binding => binding.projectId));
30
+ candidate = { schemaVersion: 1, projects: snapshot.projects.map(project => ({
31
+ projectId: project.projectId, commit: project.headCommit
32
+ })) };
33
+ }
34
+ const proof = createTaskRemoteDeliveryProof(store, task, candidate);
35
+ assertTaskRemoteDeliveryIntegrated(proof.delivery);
36
+ await coordinator.prepareTaskForArchive(taskId);
37
+ runTaskCommand(["archive", ...args], store, { ...options, archiveRemoteDeliveryProof: proof });
38
+ await coordinator.cleanupArchivedTask(taskId, "integrated");
39
+ }
40
+ const current = store.getTask(taskId);
41
+ return { task: current, ...taskArchiveDiagnostics(store, current) };
42
+ }
@@ -263,16 +263,16 @@ export class TmuxManager {
263
263
  // with terminal output that predates this attach.
264
264
  this.run(["set-option", "-t", clientSession, "status", "off"]);
265
265
  this.run(["set-option", "-t", clientSession, "mouse", "on"]);
266
+ // Arm automatic release only after a client has attached. Enabling it
267
+ // on the initially detached session would destroy it before attach.
268
+ // A client-detached hook is unsafe: after explicit session destruction,
269
+ // tmux can resolve that notification in a surviving session's context
270
+ // and execute its hook, cascading into unrelated viewer/writer leases.
266
271
  this.run([
267
- "set-hook", "-t", clientSession, "client-detached",
268
- `kill-session -t ${clientSession}`
272
+ "set-hook", "-t", clientSession, "client-attached",
273
+ `set-option -t ${clientSession} destroy-unattached on`
274
+ + (attachedNotice === undefined ? "" : ` ; display-message -d 10000 ${tmuxWord(attachedNotice)}`)
269
275
  ]);
270
- if (attachedNotice !== undefined) {
271
- this.run([
272
- "set-hook", "-t", clientSession, "client-attached",
273
- `display-message -d 10000 ${tmuxWord(attachedNotice)}`
274
- ]);
275
- }
276
276
  return clientSession;
277
277
  }
278
278
  catch (error) {
@@ -1,63 +1,23 @@
1
+ // Every asset the dashboard serves, keyed by URL path. Stylesheets come from
2
+ // the ordered styles registry, client modules from the client registry, and
3
+ // fonts and xterm from their packages; the shell links them in this order.
1
4
  import { readFileSync } from "node:fs";
2
5
  import { createRequire } from "node:module";
3
- import { API_SCRIPT } from "./client/api.js";
4
- import { APP_SCRIPT } from "./client/app.js";
5
- import { COMPONENTS_SCRIPT } from "./client/components.js";
6
- import { DETAIL_SCRIPT } from "./client/detail.js";
7
- import { DOCK_SCRIPT } from "./client/dock.js";
8
- import { DOM_SCRIPT } from "./client/dom.js";
9
- import { EVIDENCE_SCRIPT } from "./client/evidence.js";
10
- import { FORMAT_SCRIPT } from "./client/format.js";
11
- import { FORMS_SCRIPT } from "./client/forms.js";
12
- import { I18N_SCRIPT } from "./client/i18n.js";
13
- import { MARKDOWN_SCRIPT } from "./client/markdown.js";
14
- import { OVERVIEW_SCRIPT } from "./client/overview.js";
15
- import { RECORDS_SCRIPT } from "./client/records.js";
16
- import { SECTIONS_SCRIPT } from "./client/sections.js";
17
- import { SIDEBAR_SCRIPT } from "./client/sidebar.js";
18
- import { THEME_SCRIPT } from "./client/theme.js";
19
- import { DASHBOARD_HTML } from "./shell.js";
20
- import { FONT_FACE_STYLES } from "./fonts.js";
6
+ import { APP_SCRIPT, CLIENT_MODULES } from "./client/index.js";
21
7
  import { FONT_WOFF2_BASE64 } from "./fontData.js";
22
- import { BASE_STYLES } from "./styles/base.js";
23
- import { COMPONENT_STYLES } from "./styles/components.js";
24
- import { LAYOUT_STYLES } from "./styles/layout.js";
25
- import { MARKDOWN_STYLES } from "./styles/markdown.js";
26
- import { RESPONSIVE_STYLES } from "./styles/responsive.js";
27
- import { TOKEN_STYLES } from "./styles/tokens.js";
28
- import { VIEW_STYLES } from "./styles/views.js";
8
+ import { DASHBOARD_HTML } from "./shell/index.js";
9
+ import { STYLESHEETS } from "./styles/index.js";
29
10
  const require = createRequire(import.meta.url);
30
11
  const CSS = "text/css; charset=utf-8";
31
12
  const JS = "text/javascript; charset=utf-8";
32
13
  export const WEB_ASSETS = Object.freeze({
33
- "/assets/css/fonts.css": { contentType: CSS, body: FONT_FACE_STYLES },
34
- "/assets/css/tokens.css": { contentType: CSS, body: TOKEN_STYLES },
35
- "/assets/css/base.css": { contentType: CSS, body: BASE_STYLES },
36
- "/assets/css/layout.css": { contentType: CSS, body: LAYOUT_STYLES },
37
- "/assets/css/components.css": { contentType: CSS, body: COMPONENT_STYLES },
38
- "/assets/css/views.css": { contentType: CSS, body: VIEW_STYLES },
39
- "/assets/css/markdown.css": { contentType: CSS, body: MARKDOWN_STYLES },
40
- "/assets/css/responsive.css": { contentType: CSS, body: RESPONSIVE_STYLES },
41
- "/assets/js/dom.js": { contentType: JS, body: DOM_SCRIPT },
42
- "/assets/js/format.js": { contentType: JS, body: FORMAT_SCRIPT },
43
- "/assets/js/i18n.js": { contentType: JS, body: I18N_SCRIPT },
44
- "/assets/js/markdown.js": { contentType: JS, body: MARKDOWN_SCRIPT },
45
- "/assets/js/theme.js": { contentType: JS, body: THEME_SCRIPT },
46
- "/assets/js/api.js": { contentType: JS, body: API_SCRIPT },
47
- "/assets/js/components.js": { contentType: JS, body: COMPONENTS_SCRIPT },
48
- "/assets/js/records.js": { contentType: JS, body: RECORDS_SCRIPT },
49
- "/assets/js/forms.js": { contentType: JS, body: FORMS_SCRIPT },
50
- "/assets/js/evidence.js": { contentType: JS, body: EVIDENCE_SCRIPT },
51
- "/assets/js/sidebar.js": { contentType: JS, body: SIDEBAR_SCRIPT },
52
- "/assets/js/overview.js": { contentType: JS, body: OVERVIEW_SCRIPT },
53
- "/assets/js/sections.js": { contentType: JS, body: SECTIONS_SCRIPT },
54
- "/assets/js/detail.js": { contentType: JS, body: DETAIL_SCRIPT },
55
- "/assets/js/dock.js": { contentType: JS, body: DOCK_SCRIPT },
14
+ ...Object.fromEntries(STYLESHEETS.map(({ name, body }) => [`/assets/css/${name}.css`, { contentType: CSS, body }])),
15
+ ...Object.fromEntries(Object.entries(CLIENT_MODULES).map(([path, body]) => [`/assets/js/${path}.js`, { contentType: JS, body }])),
56
16
  "/assets/app.js": { contentType: JS, body: APP_SCRIPT },
57
17
  ...fontAssets(),
58
- "/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", "text/javascript; charset=utf-8"),
59
- "/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs", "text/javascript; charset=utf-8"),
60
- "/assets/vendor/xterm.css": vendorAsset("@xterm/xterm/css/xterm.css", "text/css; charset=utf-8")
18
+ "/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", JS),
19
+ "/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs", JS),
20
+ "/assets/vendor/xterm.css": vendorAsset("@xterm/xterm/css/xterm.css", CSS)
61
21
  });
62
22
  export { DASHBOARD_HTML };
63
23
  export function findWebAsset(pathname) {
@@ -0,0 +1,215 @@
1
+ export const CATALOG_SCRIPT = String.raw `
2
+ // The Task catalog: the refresh loop over the dashboard read, the sidebar
3
+ // (attention, status tabs, search, paged list), its filters from the URL and
4
+ // the explicit Session read of the current catalog page. This module is the
5
+ // only writer of the catalog fields of state. Only the newest request may
6
+ // apply its result.
7
+ import { fill, formatClock } from "/assets/js/lib/format.js";
8
+ import { h } from "/assets/js/lib/dom.js";
9
+ import { loadingBlock } from "/assets/js/ui/primitives.js";
10
+ import { renderAttentionBar, renderFilters, renderTasks, STATUS_FILTERS, ATTENTION_KINDS } from "/assets/js/views/sidebar.js";
11
+ import { syncUrl } from "/assets/js/layout/router.js";
12
+
13
+ // deps: { el, state, t, locale, api, toast, selectTask, showOverview, reloadSelected }
14
+ // el the sidebar elements, the sync state and the refresh button
15
+ // state writes the catalog fields; reads state.selected
16
+ // selectTask(taskId) a task row was chosen (app/selection)
17
+ // showOverview() redraw the center overview while nothing is selected
18
+ // reloadSelected() re-read the selected Task; rejects when the read fails
19
+ export function createCatalog(deps) {
20
+ // progress: whether a refresh is in flight and the newest request number.
21
+ const progress = { refreshing: false, request: 0 };
22
+ const catalog = {
23
+ refresh: function (options) { return refresh(deps, progress, options); },
24
+ reset: function () { reset(deps, progress); },
25
+ renderSidebar: function () { renderSidebar(deps, progress); },
26
+ renderTaskList: function () { renderTaskList(deps); },
27
+ pickAttention: function (kind) { pickAttention(deps, progress, kind); },
28
+ readPageSessions: function () { return readPageSessions(deps); },
29
+ applyFilters: function (params) { applyFilters(deps, params); }
30
+ };
31
+ bindCatalogControls(deps.el, deps.state, catalog);
32
+ return catalog;
33
+ }
34
+
35
+ function reset(deps, progress) {
36
+ deps.state.catalogCursor = null;
37
+ deps.state.nextCursor = null;
38
+ refresh(deps, progress);
39
+ }
40
+
41
+ async function refresh(deps, progress, options) {
42
+ const el = deps.el;
43
+ const state = deps.state;
44
+ const quiet = options && options.quiet;
45
+ if (progress.refreshing && quiet) return;
46
+ const request = ++progress.request;
47
+ progress.refreshing = true;
48
+ beginRefresh(el, state, deps.t, quiet);
49
+ const previousInputs = state.counts ? state.counts.openInputs : null;
50
+ try {
51
+ const query = catalogQuery(state);
52
+ const dashboard = await deps.api.dashboard(query);
53
+ if (request !== progress.request) return;
54
+ applyDashboard(state, dashboard, query);
55
+ showDashboard(deps, progress, previousInputs);
56
+ // A selected Task may live on another page or outside current filters;
57
+ // only its own read can establish that it no longer exists.
58
+ if (state.selected) {
59
+ try { await deps.reloadSelected(); }
60
+ catch { deps.toast(deps.t("errors.disconnected")); }
61
+ }
62
+ } catch {
63
+ if (request !== progress.request) return;
64
+ dashboardFailed(deps, quiet);
65
+ } finally {
66
+ if (request === progress.request) {
67
+ progress.refreshing = false;
68
+ el.refresh.disabled = false;
69
+ el.catalogNext.disabled = !state.nextCursor;
70
+ }
71
+ }
72
+ }
73
+
74
+ function showDashboard(deps, progress, previousInputs) {
75
+ deps.el.sync.dataset.state = "ok";
76
+ renderSidebar(deps, progress);
77
+ if (!deps.state.selected) deps.showOverview();
78
+ if (previousInputs !== null && deps.state.counts.openInputs > previousInputs) deps.toast(deps.t("input.new"));
79
+ }
80
+
81
+ function dashboardFailed(deps, quiet) {
82
+ deps.el.sync.dataset.state = "error";
83
+ if (quiet) return;
84
+ deps.el.tasks.replaceChildren(h("div.list-empty.is-error", null, h("p", null, deps.t("errors.dashboard"))));
85
+ deps.toast(deps.t("errors.dashboard"));
86
+ }
87
+
88
+ function renderSidebar(deps, progress) {
89
+ const el = deps.el;
90
+ const state = deps.state;
91
+ const t = deps.t;
92
+ renderAttentionBar(el.attention, state, t, function (kind) { pickAttention(deps, progress, kind); });
93
+ renderFilters(el.filters, state, t, function (filter) {
94
+ state.filter = filter;
95
+ state.attentionFilter = null;
96
+ state.catalogAll = false;
97
+ syncUrl(state, false);
98
+ reset(deps, progress);
99
+ });
100
+ el.catalogCount.textContent = fill(t("catalog.count"), { shown: state.tasks.length, total: state.catalogTotal });
101
+ el.catalogReset.hidden = !state.catalogCursor;
102
+ el.catalogNext.hidden = !state.nextCursor && !state.catalogCursor;
103
+ el.catalogAttentionReset.hidden = !state.attentionFilter;
104
+ renderTaskList(deps);
105
+ el.lastSync.textContent = state.generatedAt ? formatClock(state.generatedAt, deps.locale()) : "—";
106
+ }
107
+
108
+ // Redraw the list in place, keeping its scroll position.
109
+ function renderTaskList(deps) {
110
+ const scroll = deps.el.tasks.scrollTop;
111
+ renderTasks(deps.el.tasks, deps.state, deps.t, deps.locale(), deps.selectTask);
112
+ deps.el.tasks.scrollTop = scroll;
113
+ }
114
+
115
+ function pickAttention(deps, progress, kind) {
116
+ const state = deps.state;
117
+ state.attentionFilter = kind;
118
+ state.catalogAll = !!kind && !!state.catalogScope && state.catalogScope.archived === "included";
119
+ state.filter = "all";
120
+ state.query = "";
121
+ deps.el.search.value = "";
122
+ syncUrl(state, false);
123
+ reset(deps, progress);
124
+ }
125
+
126
+ function beginRefresh(el, state, t, quiet) {
127
+ el.catalogNext.disabled = true;
128
+ el.sync.dataset.state = "syncing";
129
+ if (quiet) return;
130
+ el.refresh.disabled = true;
131
+ if (!state.tasks.length) el.tasks.replaceChildren(loadingBlock(t("loading.dashboard")));
132
+ }
133
+
134
+ function catalogQuery(state) {
135
+ const query = new URLSearchParams();
136
+ if (state.catalogAll) query.set("all", "true");
137
+ if (state.filter !== "all") query.set("status", state.filter);
138
+ if (state.query.trim()) query.set("search", state.query.trim());
139
+ if (state.attentionFilter) query.set("attention", state.attentionFilter);
140
+ if (state.catalogCursor) query.set("cursor", state.catalogCursor);
141
+ return query.toString();
142
+ }
143
+
144
+ function taskIds(tasks, key) {
145
+ return JSON.stringify(tasks.map(function (task) { return task[key]; }));
146
+ }
147
+
148
+ function applyDashboard(state, dashboard, query) {
149
+ state.tasks = dashboard.tasks;
150
+ // A Session read describes exactly one catalog page.
151
+ if (state.catalogQuery !== query || (state.sessionOverview
152
+ && taskIds(state.sessionOverview.tasks, "taskId") !== taskIds(dashboard.tasks, "id"))) {
153
+ state.sessionOverview = null;
154
+ }
155
+ state.catalogQuery = query;
156
+ state.counts = { ...dashboard.counts, openInputs: dashboard.attention.openInputs.count };
157
+ state.catalogAttention = dashboard.attention;
158
+ state.catalogScope = dashboard.scope;
159
+ state.catalogTotal = dashboard.total;
160
+ state.nextCursor = dashboard.nextCursor;
161
+ state.attention = dashboard.attention.openInputs.refs.map(function (ref) {
162
+ const task = state.tasks.find(function (entry) { return entry.id === ref.taskId; });
163
+ return { taskId: ref.taskId, taskTitle: task ? task.title : ref.taskId };
164
+ });
165
+ state.generatedAt = new Date().toISOString();
166
+ }
167
+
168
+ async function readPageSessions(deps) {
169
+ const state = deps.state;
170
+ if (state.sessionLoading) return;
171
+ const query = state.catalogQuery;
172
+ state.sessionLoading = true;
173
+ deps.showOverview();
174
+ try {
175
+ const result = await deps.api.pageSessions(query);
176
+ if (query !== state.catalogQuery) return;
177
+ if (taskIds(result.tasks, "taskId") !== taskIds(state.tasks, "id")) throw new Error(deps.t("overview.sessionsPageChanged"));
178
+ state.sessionOverview = result;
179
+ state.sessionError = null;
180
+ } catch (error) { state.sessionError = error.message; }
181
+ finally {
182
+ state.sessionLoading = false;
183
+ if (!state.selected) deps.showOverview();
184
+ }
185
+ }
186
+
187
+ // Filters from the URL, on load and on back/forward.
188
+ function applyFilters(deps, params) {
189
+ const state = deps.state;
190
+ const filter = params.get("filter");
191
+ state.filter = STATUS_FILTERS.includes(filter) ? filter : "all";
192
+ state.query = params.get("q") || "";
193
+ state.attentionFilter = ATTENTION_KINDS.includes(params.get("attention")) ? params.get("attention") : null;
194
+ state.catalogAll = params.get("all") === "true";
195
+ deps.el.search.value = state.query;
196
+ }
197
+
198
+ function bindCatalogControls(el, state, catalog) {
199
+ let searchTimer = null;
200
+ el.search.addEventListener("input", function () {
201
+ state.query = el.search.value;
202
+ syncUrl(state, true);
203
+ window.clearTimeout(searchTimer);
204
+ searchTimer = window.setTimeout(catalog.reset, 200);
205
+ });
206
+ el.catalogNext.addEventListener("click", function () {
207
+ if (!state.nextCursor) return;
208
+ state.catalogCursor = state.nextCursor;
209
+ catalog.refresh();
210
+ });
211
+ el.catalogReset.addEventListener("click", catalog.reset);
212
+ el.catalogAttentionReset.addEventListener("click", function () { catalog.pickAttention(null); });
213
+ el.refresh.addEventListener("click", function () { catalog.refresh(); });
214
+ }
215
+ `;
@@ -0,0 +1,148 @@
1
+ export const APP_SCRIPT = String.raw `
2
+ // Composition root (served as /assets/app.js). It binds the static shell,
3
+ // owns the view state object and is the only place where controllers are
4
+ // wired to each other. Each controller receives one explicit deps object,
5
+ // documented at its factory: services, controllers created before it, and
6
+ // named callbacks into controllers created after it (bound here, late).
7
+ // Everything kept in the browser is view state; Task facts always come from
8
+ // the loopback API.
9
+ //
10
+ // layout/workspace sidebar | center | dock columns, dock modes, sizing
11
+ // app/taskView draws the selected Task, the dock discussion, the overview
12
+ // app/selection selected Task, its detail reads, URL-driven navigation
13
+ // app/catalog refresh loop, sidebar, catalog paging and filters
14
+ // layout/shortcuts keyboard shortcuts
15
+ //
16
+ // Flows:
17
+ // refresh catalog.refresh → sidebar, then the overview (nothing selected)
18
+ // or selection.reload → taskView.render
19
+ // select sidebar row / overview row / URL → selection.selectTask
20
+ // leave back button or Escape → selection.clearSelection
21
+ // write Task page or discussion afterWrite → quiet catalog refresh
22
+ // URL load and back/forward → catalog.applyFilters + selection.followUrl
23
+ import { createI18n } from "/assets/js/lib/i18n.js";
24
+ import { createThemeController } from "/assets/js/lib/theme.js";
25
+ import { api } from "/assets/js/lib/api.js";
26
+ import { createToast } from "/assets/js/ui/toast.js";
27
+ import { createTerminalController } from "/assets/js/views/dock/terminal.js";
28
+ import { createConversationController } from "/assets/js/views/dock/conversation.js";
29
+ import { createPreviewController } from "/assets/js/views/dock/preview.js";
30
+ import { bindGlobalInput } from "/assets/js/views/globalInput.js";
31
+ import { bindCreateTask } from "/assets/js/views/createTask.js";
32
+ import { bindBodySearch } from "/assets/js/views/bodySearch.js";
33
+ import { readQuery } from "/assets/js/layout/router.js";
34
+ import { createWorkspace } from "/assets/js/layout/workspace.js";
35
+ import { bindShortcuts } from "/assets/js/layout/shortcuts.js";
36
+ import { createTaskView } from "/assets/js/app/taskView.js";
37
+ import { createSelection } from "/assets/js/app/selection.js";
38
+ import { createCatalog } from "/assets/js/app/catalog.js";
39
+
40
+ const $ = function (selector) { return document.querySelector(selector); };
41
+ const el = {
42
+ sidebar: $(".sidebar"), sidebarDivider: $("#sidebar-divider"), center: $("#center"), detail: $("#detail"),
43
+ search: $("#search"), filters: $("#status-filters"), attention: $("#attention-bar"), tasks: $("#task-list"),
44
+ catalogNext: $("#catalog-next"), catalogReset: $("#catalog-reset"), catalogCount: $("#catalog-count"),
45
+ catalogAttentionReset: $("#catalog-attention-reset"),
46
+ refresh: $("#refresh"), sync: $("#sync-state"), lastSync: $("#last-sync"), toast: $("#toast"),
47
+ operator: $("#operator-terminal"), settingsOpen: $("#settings-open"), settings: $("#settings-dialog"),
48
+ locale: $("#locale-select"), themeOptions: $("#theme-options"),
49
+ dock: $("#dock"), divider: $("#dock-divider"), dockSwap: $("#dock-swap"), dockClose: $("#dock-close"),
50
+ dockTabDiscussion: $("#dock-tab-discussion"), dockTabSession: $("#dock-tab-session"),
51
+ dockTabConversation: $("#dock-tab-conversation"), conversation: $("#dock-conversation"),
52
+ dockTabPreview: $("#dock-tab-preview"), preview: $("#dock-preview"),
53
+ discussion: $("#dock-discussion"), session: $("#dock-session"),
54
+ terminalHost: $("#terminal-host"), terminalEmpty: $("#terminal-empty"), terminalState: $("#terminal-state"),
55
+ sessionTargets: $("#session-targets"), terminalCli: $("#terminal-cli")
56
+ };
57
+
58
+ // View state. Each group has exactly one writer.
59
+ const state = {
60
+ // app/catalog: the dashboard read, its filters and paging, the Session read.
61
+ tasks: [], counts: null, attention: [], catalogAttention: null, catalogScope: null, catalogQuery: "",
62
+ catalogAll: false, catalogTotal: 0, catalogCursor: null, nextCursor: null,
63
+ sessionOverview: null, sessionLoading: false, sessionError: null,
64
+ attentionFilter: null, generatedAt: null, filter: "all", query: "",
65
+ // app/selection: the selected Task and its loaded detail.
66
+ selected: null, detail: null, detailKey: null,
67
+ // app/taskView: the tab the reader is on.
68
+ activeTab: "overview"
69
+ };
70
+
71
+ const i18n = createI18n(el.locale);
72
+ const t = i18n.t;
73
+ const locale = i18n.getLocale;
74
+ const theme = createThemeController(el.themeOptions, t);
75
+ const toast = createToast(el.toast);
76
+ const conversation = createConversationController(el.conversation, t, locale);
77
+ const preview = createPreviewController(el.preview, t);
78
+ const terminal = createTerminalController({
79
+ host: el.terminalHost, empty: el.terminalEmpty, state: el.terminalState, targets: el.sessionTargets, cli: el.terminalCli
80
+ }, t, locale, toast);
81
+ theme.subscribe(function () { terminal.retheme(); });
82
+
83
+ // Controllers are created in dependency order; the arrow callbacks below are
84
+ // the only edges pointing at controllers created later.
85
+ let taskView = null;
86
+ let selection = null;
87
+ let catalog = null;
88
+ const workspace = createWorkspace({
89
+ el: el, state: state, terminal: terminal, conversation: conversation, preview: preview,
90
+ sessionOpened: function () { taskView.updateSessionTargets(); }
91
+ });
92
+ taskView = createTaskView({
93
+ el: el, state: state, t: t, locale: locale, api: api, toast: toast, terminal: terminal, workspace: workspace,
94
+ selectTask: function (taskId) { return selection.selectTask(taskId); },
95
+ clearSelection: function () { selection.clearSelection(); },
96
+ pickAttention: function (kind) { catalog.pickAttention(kind); },
97
+ readPageSessions: function () { return catalog.readPageSessions(); },
98
+ refreshCatalog: function () { return catalog.refresh({ quiet: true }); }
99
+ });
100
+ selection = createSelection({
101
+ state: state, t: t, api: api, toast: toast, workspace: workspace, taskView: taskView,
102
+ renderTaskList: function () { catalog.renderTaskList(); }
103
+ });
104
+ catalog = createCatalog({
105
+ el: el, state: state, t: t, locale: locale, api: api, toast: toast,
106
+ selectTask: selection.selectTask,
107
+ showOverview: taskView.showOverview,
108
+ reloadSelected: selection.reload
109
+ });
110
+ const globalInput = bindGlobalInput({ t: t, api: api });
111
+ bindCreateTask({ t: t, api: api, selectTask: selection.selectTask,
112
+ afterWrite: function () { catalog.refresh({ quiet: true }); } });
113
+ bindBodySearch({ t: t, api: api, selectTask: selection.selectTask,
114
+ currentTask: function () { return state.selected; },
115
+ captureDraftTarget: conversation.captureDraftTarget, insertReference: conversation.insertReference });
116
+ bindShortcuts({
117
+ el: el, state: state, globalInput: globalInput, workspace: workspace,
118
+ selection: selection, catalog: catalog, taskView: taskView
119
+ });
120
+ el.settingsOpen.addEventListener("click", function () { window.location.assign("/settings"); });
121
+
122
+ // The URL names the catalog filters and the selected Task (with its section).
123
+ function applyStateFromUrl() {
124
+ const params = readQuery();
125
+ catalog.applyFilters(params);
126
+ selection.followUrl(params);
127
+ }
128
+ window.addEventListener("popstate", function () {
129
+ applyStateFromUrl();
130
+ catalog.reset();
131
+ });
132
+
133
+ i18n.subscribe(function () {
134
+ theme.render();
135
+ terminal.relabel();
136
+ catalog.renderSidebar();
137
+ taskView.relabel();
138
+ workspace.updateLayout();
139
+ });
140
+
141
+ workspace.updateLayout();
142
+ taskView.showOverview();
143
+ conversation.open({ scope: "global", roleName: "operator" });
144
+ applyStateFromUrl();
145
+ workspace.restoreDefaultMode();
146
+ catalog.refresh();
147
+ window.setInterval(function () { catalog.refresh({ quiet: true }); }, 5000);
148
+ `;