@zq-silk/yui 1.1.2 → 2.1.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 (115) hide show
  1. package/ARCHITECTURE.md +2 -1
  2. package/ARCHITECTURE.zh-CN.md +2 -1
  3. package/README.md +2 -0
  4. package/dist/cli/commandCatalog.js +344 -324
  5. package/dist/cli/commandDiscovery.js +69 -0
  6. package/dist/cli/completion.js +22 -227
  7. package/dist/cli/dynamicCompletion.js +12 -6
  8. package/dist/cli/invocationAuthority.js +2 -2
  9. package/dist/cli/invocationRouter.js +18 -7
  10. package/dist/cli/managedDiagnostics.js +2 -1
  11. package/dist/cli/updatePorts.js +0 -5
  12. package/dist/cli.js +184 -130
  13. package/dist/commands/capabilityCommands.js +5 -5
  14. package/dist/commands/controllerCommands.js +2 -1
  15. package/dist/commands/globalRoleCommands.js +86 -44
  16. package/dist/commands/grantCommands.js +48 -8
  17. package/dist/commands/taskCommands.js +157 -196
  18. package/dist/commands/taskContextCommand.js +29 -6
  19. package/dist/commands/taskFactCommands.js +10 -69
  20. package/dist/commands/taskInputCommands.js +26 -35
  21. package/dist/commands/taskPublicationCommands.js +2 -35
  22. package/dist/commands/taskPublicationVerifyCommand.js +1 -2
  23. package/dist/commands/taskUpstreamCommands.js +2 -1
  24. package/dist/context/runContextPack.js +8 -29
  25. package/dist/context/sessionBootstrapManifest.js +5 -101
  26. package/dist/context/taskContext.js +104 -41
  27. package/dist/controller/clientRuntime.js +16 -8
  28. package/dist/controller/fileSchedulerStoreAdapter.js +24 -0
  29. package/dist/controller/runtime.js +6 -0
  30. package/dist/core/controllerClient.js +46 -21
  31. package/dist/core/protocol.js +12 -7
  32. package/dist/doctor/doctor.js +4 -3
  33. package/dist/errors/cliError.js +4 -4
  34. package/dist/errors/cliFailure.js +213 -0
  35. package/dist/executor/fileRoleLaunchPlanner.js +1 -4
  36. package/dist/grant/capabilityGrant.js +13 -0
  37. package/dist/grant/taskAuthorization.js +127 -0
  38. package/dist/kernel/accessAssessment.js +1 -0
  39. package/dist/kernel/builtinCapabilities.js +88 -9
  40. package/dist/kernel/capabilityRegistry.js +64 -19
  41. package/dist/kernel/kernelPorts.js +2 -2
  42. package/dist/nativeAgent/agent.js +173 -0
  43. package/dist/nativeAgent/contracts.js +1 -0
  44. package/dist/nativeAgent/demo.js +29 -0
  45. package/dist/nativeAgent/index.js +3 -0
  46. package/dist/nativeAgent/mockProvider.js +43 -0
  47. package/dist/nativeAgent/textTools.js +156 -0
  48. package/dist/nativeAgent/validation.js +95 -0
  49. package/dist/output/boundedRead.js +118 -0
  50. package/dist/plugins/pluginService.js +98 -8
  51. package/dist/release/releaseWorkflowEngine.js +8 -1
  52. package/dist/resources/projectResourceService.js +23 -1
  53. package/dist/runtime/managedCaller.js +2 -2
  54. package/dist/runtime/managedIdentity.js +6 -0
  55. package/dist/runtime/runtimeCoherence.js +10 -8
  56. package/dist/storage/sqliteSchema.js +10 -0
  57. package/dist/storage/storageVersions.js +1 -1
  58. package/dist/storage/taskStore.js +3 -0
  59. package/dist/surface/surfaceContributions.js +3 -0
  60. package/dist/task/leaderArchive.js +111 -0
  61. package/dist/task/leaderArchiveAuthority.js +24 -0
  62. package/dist/task/taskAuthority.js +5 -10
  63. package/dist/tmux/commandExecutor.js +7 -5
  64. package/dist/web/assets/assetManifest.js +38 -22
  65. package/dist/web/assets/client/api.js +115 -0
  66. package/dist/web/assets/client/app.js +555 -863
  67. package/dist/web/assets/client/components.js +323 -897
  68. package/dist/web/assets/client/detail.js +333 -0
  69. package/dist/web/assets/client/dock.js +225 -0
  70. package/dist/web/assets/client/dom.js +55 -3
  71. package/dist/web/assets/client/evidence.js +172 -0
  72. package/dist/web/assets/client/format.js +25 -14
  73. package/dist/web/assets/client/forms.js +230 -0
  74. package/dist/web/assets/client/i18n.js +1020 -791
  75. package/dist/web/assets/client/overview.js +115 -0
  76. package/dist/web/assets/client/records.js +63 -0
  77. package/dist/web/assets/client/sections.js +313 -0
  78. package/dist/web/assets/client/sidebar.js +117 -0
  79. package/dist/web/assets/client/theme.js +61 -22
  80. package/dist/web/assets/icons.js +39 -0
  81. package/dist/web/assets/shell.js +124 -118
  82. package/dist/web/assets/styles/base.js +41 -0
  83. package/dist/web/assets/styles/components.js +167 -0
  84. package/dist/web/assets/styles/layout.js +49 -77
  85. package/dist/web/assets/styles/markdown.js +16 -24
  86. package/dist/web/assets/styles/responsive.js +33 -44
  87. package/dist/web/assets/styles/tokens.js +65 -89
  88. package/dist/web/assets/styles/views.js +289 -0
  89. package/dist/web/webServer.js +2 -1
  90. package/docs/architecture/README.md +1 -0
  91. package/docs/architecture/README.zh-CN.md +1 -0
  92. package/docs/cli-information-contract.md +107 -0
  93. package/docs/cli-information-contract.zh-CN.md +81 -0
  94. package/docs/managed-turn-and-session-runtime.md +9 -0
  95. package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
  96. package/docs/plugin-sdk.md +7 -3
  97. package/docs/plugin-sdk.zh-CN.md +5 -2
  98. package/docs/release-workflow.md +42 -9
  99. package/docs/release-workflow.zh-CN.md +30 -7
  100. package/docs/storage-baseline.md +5 -0
  101. package/docs/testing/verification-levels.md +2 -2
  102. package/i18n/README.zh-CN.md +2 -0
  103. package/package.json +1 -1
  104. package/skills/yui-leader/SKILL.md +8 -1
  105. package/skills/yui-leader/references/authorization.md +64 -0
  106. package/skills/yui-leader/references/execution.md +18 -4
  107. package/skills/yui-leader/references/task-plugins.md +4 -2
  108. package/skills/yui-operator/SKILL.md +9 -2
  109. package/skills/yui-runtime/SKILL.md +77 -9
  110. package/skills/yui-runtime/references/publication.md +3 -1
  111. package/dist/web/assets/client/taskSummary.js +0 -350
  112. package/dist/web/assets/client/taskSurface.js +0 -616
  113. package/dist/web/assets/client/view.js +0 -608
  114. package/dist/web/assets/styles/cards.js +0 -247
  115. package/dist/web/assets/styles/widgets.js +0 -168
@@ -0,0 +1,111 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createTaskEvent } from "../event/taskEvent.js";
3
+ import { runTaskCommand } from "../commands/taskCommands.js";
4
+ import { archiveExecutionChecks, archiveSettlementChecks } from "./archivePreflight.js";
5
+ import { admitLeaderArchive, assertLeaderArchiveAdmission } from "./leaderArchiveAuthority.js";
6
+ import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./remoteDeliveryService.js";
7
+ import { selectNewTaskRoleSession } from "../executor/agentExecutor.js";
8
+ /** A single foreground Controller operation survives stopping its requesting
9
+ * Session. It has no retry worker: uncertain receipts are inspected, not
10
+ * replayed. All existing settlement, delivery and cleanup checks remain. */
11
+ export async function archiveLeaderTask(store, coordinator, taskId, environment, request) {
12
+ const inputDigest = createHash("sha256").update(JSON.stringify(request)).digest("hex");
13
+ const previous = store.listEvents(taskId).find(event => event.type === "task.leader-archive-started" && event.payload.requestId === request.requestId);
14
+ if (previous) {
15
+ if (previous.payload.inputDigest !== inputDigest)
16
+ throw new Error("Archive request id names different input.");
17
+ const result = store.listEvents(taskId).find(event => event.type === "task.leader-archive-result" && event.payload.requestId === request.requestId);
18
+ return { taskId, requestId: request.requestId, status: result?.payload.status ?? "unknown",
19
+ detail: result?.payload.detail, replayed: true };
20
+ }
21
+ const admission = admitLeaderArchive(store, taskId, environment, request.sourceMessage, request.purpose);
22
+ const task = store.getTask(taskId);
23
+ const settlement = archiveSettlementChecks(store, task);
24
+ if (settlement.length)
25
+ throw new Error(`Archive is unsettled: ${JSON.stringify(settlement)}`);
26
+ const session = store.getTaskRoleSessionSet(taskId, "leader");
27
+ const ownInput = session?.providerBinding?.run;
28
+ // Only the requesting no-Run Leader may be stopped as part of this action.
29
+ // Another Run, Job, unknown input, or then handoff is not archive authority.
30
+ const ownResources = `role:${taskId}/leader`;
31
+ const execution = archiveExecutionChecks(store, taskId).filter(check => !(check.resource === ownResources
32
+ && ownInput?.runId === undefined && ownInput?.status === "accepted"
33
+ && ["unresolved-provider-input", "unresolved-mailbox"].includes(check.reason)));
34
+ if (execution.length)
35
+ throw new Error(`Archive execution is unresolved: ${JSON.stringify(execution)}`);
36
+ if (store.listMessages(taskId).some(message => message.interruptThen?.targetNativeSessionId === admission.source.nativeSessionId
37
+ && message.continuation === undefined && message.interruptThen.notDeliveredReason === undefined)) {
38
+ throw new Error("Settle the original input handoff before archive.");
39
+ }
40
+ let candidate = null;
41
+ const workspace = store.getTaskWorkspace(taskId);
42
+ if (task.projectBindings.length && workspace) {
43
+ const snapshot = await coordinator.preparer.snapshotDirectTaskMain(workspace, task.projectBindings.map(binding => binding.projectId));
44
+ candidate = { schemaVersion: 1, projects: snapshot.projects.map(project => ({
45
+ projectId: project.projectId, commit: project.headCommit
46
+ })) };
47
+ }
48
+ const proof = createTaskRemoteDeliveryProof(store, task, candidate);
49
+ assertTaskRemoteDeliveryIntegrated(proof.delivery);
50
+ for (const owned of store.listManagedWorkspaces(taskId)) {
51
+ const disposition = owned.owner.type === "work-item"
52
+ && store.getWorkItem(taskId, owned.owner.workItemId)?.status === "retired" ? "abandoned" : "integrated";
53
+ const checks = await coordinator.preparer.inspectWorkspaceCleanup(owned, disposition);
54
+ if (checks.some(check => !(owned.owner.type === "task" && check.reason === "git-worktree-registrations"))) {
55
+ throw new Error(`Archive workspace is not clean/removable: ${JSON.stringify(checks)}`);
56
+ }
57
+ }
58
+ // Reauthenticate after asynchronous inspection, before the first effect.
59
+ admitLeaderArchive(store, taskId, environment, request.sourceMessage, request.purpose);
60
+ assertLeaderArchiveAdmission(store, taskId, admission);
61
+ const record = (type, payload) => store.transaction(tx => tx.saveEvent(taskId, createTaskEvent(tx.nextEventId(taskId), taskId, type, { requestId: request.requestId, ...payload }, new Date())));
62
+ const started = store.transaction(tx => {
63
+ assertLeaderArchiveAdmission(tx, taskId, admission);
64
+ const events = tx.listEvents(taskId);
65
+ const prior = events.find(event => event.type === "task.leader-archive-started"
66
+ && event.payload.requestId === request.requestId);
67
+ if (prior) {
68
+ if (prior.payload.inputDigest !== inputDigest)
69
+ throw new Error("Archive request id names different input.");
70
+ return false;
71
+ }
72
+ const unsettled = events.find(event => event.type === "task.leader-archive-started"
73
+ && !events.some(result => result.type === "task.leader-archive-result"
74
+ && result.payload.requestId === event.payload.requestId));
75
+ if (unsettled)
76
+ throw new Error(`Inspect unresolved archive ${unsettled.payload.requestId} before another operation.`);
77
+ tx.saveEvent(taskId, createTaskEvent(tx.nextEventId(taskId), taskId, "task.leader-archive-started", { requestId: request.requestId, inputDigest, authorizationSource: JSON.stringify(admission.source) }, new Date()));
78
+ return true;
79
+ });
80
+ if (!started)
81
+ return { taskId, requestId: request.requestId, status: "unknown", replayed: true };
82
+ try {
83
+ await coordinator.runtime.stopTaskRoleSessions(taskId, ["leader"]);
84
+ store.transaction(tx => {
85
+ assertLeaderArchiveAdmission(tx, taskId, admission);
86
+ const stopped = tx.getTaskRoleSessionSet(taskId, "leader");
87
+ const current = stopped?.sessions[stopped.activeAgentId];
88
+ if (!stopped || current?.nativeSessionId !== admission.source.nativeSessionId
89
+ || current.status !== "ended" || current.endReason !== "stopped") {
90
+ throw new Error("Leader Session changed before archive runtime retirement.");
91
+ }
92
+ // Retain the exact stopped caller in history and clear its resumable
93
+ // binding. Generic workspace cleanup must not stop that caller again:
94
+ // the first verified stop already released its physical owner records.
95
+ tx.saveTaskRoleSessionSet(selectNewTaskRoleSession(stopped, stopped.activeAgentId, new Date()));
96
+ });
97
+ const cleanup = await coordinator.cleanupTaskForArchive(taskId, "integrated");
98
+ if (cleanup.status !== "removed")
99
+ throw new Error(cleanup.error ?? `Archive cleanup ${cleanup.status}.`);
100
+ runTaskCommand(["archive", taskId, "--integrated"], store, {
101
+ environment, archiveLeaderAdmission: admission, archiveRemoteDeliveryProof: proof
102
+ });
103
+ record("task.leader-archive-result", { status: "archived" });
104
+ return { taskId, requestId: request.requestId, status: "archived" };
105
+ }
106
+ catch (error) {
107
+ record("task.leader-archive-result", { status: store.getTask(taskId)?.status === "archived" ? "archived" : "failed",
108
+ detail: error instanceof Error ? error.message : String(error) });
109
+ throw error;
110
+ }
111
+ }
@@ -0,0 +1,24 @@
1
+ import { taskAuthorizationSource } from "../grant/taskAuthorization.js";
2
+ const admissions = new WeakSet();
3
+ const terminalIdentity = (store, taskId) => {
4
+ const task = store.getTask(taskId);
5
+ if (!task || !["completed", "cancelled"].includes(task.status))
6
+ throw new Error("Ordinary Leader archive requires a terminal Task.");
7
+ return JSON.stringify({
8
+ status: task.status, completedAt: task.completedAt, completionSummary: task.completionSummary,
9
+ retiredAt: task.retiredAt, retirementSummary: task.retirementSummary,
10
+ projectBindings: task.projectBindings
11
+ });
12
+ };
13
+ export function admitLeaderArchive(store, taskId, environment, messageId, purpose) {
14
+ const source = taskAuthorizationSource(store, taskId, environment, messageId, purpose);
15
+ const admission = Object.freeze({ taskId, source, terminalIdentity: terminalIdentity(store, taskId) });
16
+ admissions.add(admission);
17
+ return admission;
18
+ }
19
+ export function assertLeaderArchiveAdmission(store, taskId, admission) {
20
+ if (!admissions.has(admission) || admission.taskId !== taskId
21
+ || terminalIdentity(store, taskId) !== admission.terminalIdentity) {
22
+ throw new Error("Archive admission is untrusted or its terminal Task changed.");
23
+ }
24
+ }
@@ -1,5 +1,6 @@
1
+ import { hasManagedIdentity } from "../runtime/managedIdentity.js";
1
2
  import { usageError } from "../errors/cliError.js";
2
- import { MANAGED_NATIVE_SESSION_ENV, currentManagedRuntime } from "../runtime/managedCaller.js";
3
+ import { currentManagedRuntime } from "../runtime/managedCaller.js";
3
4
  const LEADER_ROLE = "leader";
4
5
  export function taskActor(environment, taskId) {
5
6
  const env = environment ?? {};
@@ -16,9 +17,7 @@ export function taskActor(environment, taskId) {
16
17
  return "operator";
17
18
  throw usageError("A managed global Session may perform this action only as Operator.");
18
19
  }
19
- if (env.YUI_ROLE !== undefined
20
- || env.YUI_AGENT_ID !== undefined
21
- || env[MANAGED_NATIVE_SESSION_ENV] !== undefined) {
20
+ if (hasManagedIdentity(env)) {
22
21
  throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
23
22
  }
24
23
  return "user";
@@ -86,9 +85,7 @@ export function projectActor(environment) {
86
85
  return "operator";
87
86
  throw usageError("A managed global Session may manage Project Knowledge only as Operator.");
88
87
  }
89
- if (env.YUI_ROLE !== undefined
90
- || env.YUI_AGENT_ID !== undefined
91
- || env[MANAGED_NATIVE_SESSION_ENV] !== undefined) {
88
+ if (hasManagedIdentity(env)) {
92
89
  throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
93
90
  }
94
91
  return "user";
@@ -124,9 +121,7 @@ export function resolveJobCaller(environment, taskId) {
124
121
  nativeSessionId: env.CODEX_THREAD_ID ?? env.YUI_NATIVE_SESSION_ID
125
122
  };
126
123
  }
127
- if (env.YUI_ROLE !== undefined
128
- || env.YUI_AGENT_ID !== undefined
129
- || env[MANAGED_NATIVE_SESSION_ENV] !== undefined) {
124
+ if (hasManagedIdentity(env)) {
130
125
  throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
131
126
  }
132
127
  return { scope: "user" };
@@ -4,8 +4,8 @@ export class CommandExecutionError extends Error {
4
4
  code;
5
5
  exitStatus;
6
6
  stderr;
7
- constructor(code, exitStatus, stderr = "") {
8
- super(commandExecutionMessage(code));
7
+ constructor(code, exitStatus, stderr = "", options) {
8
+ super(commandExecutionMessage(code), options);
9
9
  this.code = code;
10
10
  this.exitStatus = exitStatus;
11
11
  this.stderr = stderr;
@@ -102,15 +102,17 @@ export class NodeCommandExecutor {
102
102
  }
103
103
  function stableCommandExecutionError(error) {
104
104
  const details = errorDetails(error);
105
+ const cause = typeof error === "object" && error !== null && "error" in error
106
+ && error.error instanceof Error ? { cause: error.error } : undefined;
105
107
  if (details.code === "ENOENT") {
106
- return new CommandExecutionError("COMMAND_NOT_FOUND", details.exitStatus, details.stderr);
108
+ return new CommandExecutionError("COMMAND_NOT_FOUND", details.exitStatus, details.stderr, cause);
107
109
  }
108
110
  if (details.code === "ETIMEDOUT"
109
111
  || details.signal === "SIGTERM"
110
112
  || details.signal === "SIGKILL") {
111
- return new CommandExecutionError("COMMAND_TIMED_OUT", details.exitStatus, details.stderr);
113
+ return new CommandExecutionError("COMMAND_TIMED_OUT", details.exitStatus, details.stderr, cause);
112
114
  }
113
- return new CommandExecutionError("COMMAND_FAILED", details.exitStatus, details.stderr);
115
+ return new CommandExecutionError("COMMAND_FAILED", details.exitStatus, details.stderr, cause);
114
116
  }
115
117
  function errorDetails(error) {
116
118
  if (typeof error !== "object" || error === null)
@@ -1,43 +1,59 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
+ import { API_SCRIPT } from "./client/api.js";
3
4
  import { APP_SCRIPT } from "./client/app.js";
4
5
  import { COMPONENTS_SCRIPT } from "./client/components.js";
6
+ import { DETAIL_SCRIPT } from "./client/detail.js";
7
+ import { DOCK_SCRIPT } from "./client/dock.js";
5
8
  import { DOM_SCRIPT } from "./client/dom.js";
9
+ import { EVIDENCE_SCRIPT } from "./client/evidence.js";
6
10
  import { FORMAT_SCRIPT } from "./client/format.js";
11
+ import { FORMS_SCRIPT } from "./client/forms.js";
7
12
  import { I18N_SCRIPT } from "./client/i18n.js";
8
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";
9
18
  import { THEME_SCRIPT } from "./client/theme.js";
10
- import { VIEW_SCRIPT } from "./client/view.js";
11
- import { TASK_SURFACE_SCRIPT } from "./client/taskSurface.js";
12
- import { TASK_SUMMARY_SCRIPT } from "./client/taskSummary.js";
13
19
  import { DASHBOARD_HTML } from "./shell.js";
14
20
  import { FONT_FACE_STYLES } from "./fonts.js";
15
21
  import { FONT_WOFF2_BASE64 } from "./fontData.js";
16
- import { CARD_STYLES } from "./styles/cards.js";
22
+ import { BASE_STYLES } from "./styles/base.js";
23
+ import { COMPONENT_STYLES } from "./styles/components.js";
17
24
  import { LAYOUT_STYLES } from "./styles/layout.js";
18
25
  import { MARKDOWN_STYLES } from "./styles/markdown.js";
19
26
  import { RESPONSIVE_STYLES } from "./styles/responsive.js";
20
27
  import { TOKEN_STYLES } from "./styles/tokens.js";
21
- import { WIDGET_STYLES } from "./styles/widgets.js";
28
+ import { VIEW_STYLES } from "./styles/views.js";
22
29
  const require = createRequire(import.meta.url);
30
+ const CSS = "text/css; charset=utf-8";
31
+ const JS = "text/javascript; charset=utf-8";
23
32
  export const WEB_ASSETS = Object.freeze({
24
- "/assets/css/tokens.css": { contentType: "text/css; charset=utf-8", body: TOKEN_STYLES },
25
- "/assets/css/fonts.css": { contentType: "text/css; charset=utf-8", body: FONT_FACE_STYLES },
26
- "/assets/css/layout.css": { contentType: "text/css; charset=utf-8", body: LAYOUT_STYLES },
27
- "/assets/css/widgets.css": { contentType: "text/css; charset=utf-8", body: WIDGET_STYLES },
28
- "/assets/css/cards.css": { contentType: "text/css; charset=utf-8", body: CARD_STYLES },
29
- "/assets/css/markdown.css": { contentType: "text/css; charset=utf-8", body: MARKDOWN_STYLES },
30
- "/assets/css/responsive.css": { contentType: "text/css; charset=utf-8", body: RESPONSIVE_STYLES },
31
- "/assets/js/dom.js": { contentType: "text/javascript; charset=utf-8", body: DOM_SCRIPT },
32
- "/assets/js/format.js": { contentType: "text/javascript; charset=utf-8", body: FORMAT_SCRIPT },
33
- "/assets/js/i18n.js": { contentType: "text/javascript; charset=utf-8", body: I18N_SCRIPT },
34
- "/assets/js/markdown.js": { contentType: "text/javascript; charset=utf-8", body: MARKDOWN_SCRIPT },
35
- "/assets/js/theme.js": { contentType: "text/javascript; charset=utf-8", body: THEME_SCRIPT },
36
- "/assets/js/components.js": { contentType: "text/javascript; charset=utf-8", body: COMPONENTS_SCRIPT },
37
- "/assets/js/view.js": { contentType: "text/javascript; charset=utf-8", body: VIEW_SCRIPT },
38
- "/assets/js/task-surface.js": { contentType: "text/javascript; charset=utf-8", body: TASK_SURFACE_SCRIPT },
39
- "/assets/js/task-summary.js": { contentType: "text/javascript; charset=utf-8", body: TASK_SUMMARY_SCRIPT },
40
- "/assets/app.js": { contentType: "text/javascript; charset=utf-8", body: APP_SCRIPT },
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 },
56
+ "/assets/app.js": { contentType: JS, body: APP_SCRIPT },
41
57
  ...fontAssets(),
42
58
  "/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", "text/javascript; charset=utf-8"),
43
59
  "/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs", "text/javascript; charset=utf-8"),
@@ -0,0 +1,115 @@
1
+ export const API_SCRIPT = String.raw `
2
+ // Loopback API client. Every request carries the page token; a write is keyed
3
+ // so an unresolved submission can never be silently replayed from this page.
4
+ const token = document.querySelector('meta[name="yui-web-token"]').content;
5
+ const unresolved = new Set();
6
+
7
+ export function pageToken() { return token; }
8
+
9
+ export async function requestJson(path, options) {
10
+ const response = await fetch(path, {
11
+ ...options,
12
+ headers: {
13
+ accept: "application/json",
14
+ "x-yui-web-token": token,
15
+ ...(options && options.headers ? options.headers : {})
16
+ }
17
+ });
18
+ if (!response.ok) {
19
+ let message = "HTTP " + response.status;
20
+ let disposition = "unknown";
21
+ try {
22
+ const body = await response.json();
23
+ if (body && body.error) message = body.error;
24
+ if (body && body.disposition === "not-submitted") disposition = "not-submitted";
25
+ } catch {}
26
+ throw Object.assign(new Error(message), { disposition, status: response.status });
27
+ }
28
+ return response.json();
29
+ }
30
+
31
+ function postJson(path, body, signal) {
32
+ return requestJson(path, {
33
+ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
34
+ ...(signal ? { signal } : {})
35
+ });
36
+ }
37
+
38
+ // A key stays blocked until the server either answers or proves the request
39
+ // was not submitted; an unknown outcome must be inspected, not resent.
40
+ export async function submitMutation(key, path, body) {
41
+ if (unresolved.has(key)) {
42
+ throw Object.assign(new Error("An earlier submission is unresolved; read current facts first."), { disposition: "blocked" });
43
+ }
44
+ unresolved.add(key);
45
+ try {
46
+ const receipt = await postJson(path, body);
47
+ unresolved.delete(key);
48
+ return receipt;
49
+ } catch (error) {
50
+ if (error.disposition === "not-submitted") unresolved.delete(key);
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ export function releaseMutation(key) { unresolved.delete(key); }
56
+
57
+ const task = function (taskId) { return "/api/tasks/" + encodeURIComponent(taskId); };
58
+
59
+ export const api = {
60
+ dashboard: function (query) { return requestJson("/api/dashboard?" + query); },
61
+ pageSessions: function (query) {
62
+ return requestJson("/api/dashboard/sessions?" + query, { signal: AbortSignal.timeout(5000) });
63
+ },
64
+ context: function (taskId) { return requestJson(task(taskId) + "/context"); },
65
+ observation: function (taskId) { return requestJson(task(taskId), { signal: AbortSignal.timeout(1500) }); },
66
+ inspect: async function (taskId, ref) {
67
+ const query = new URLSearchParams({ store: ref.store, ref: ref.refId });
68
+ if (ref.digest) query.set("digest", ref.digest);
69
+ let result = await requestJson(task(taskId) + "/inspect?" + query);
70
+ if (!result.contentPage) return result;
71
+ const source = result.contentPage.source;
72
+ const digest = result.contentPage.digest;
73
+ let text = "";
74
+ while (true) {
75
+ const page = result.contentPage;
76
+ if (!page || page.source !== source || page.digest !== digest || page.encoding !== "json"
77
+ || page.offset !== text.length || typeof page.text !== "string" || !page.text.length) {
78
+ throw new Error("Original read changed or returned an invalid page; refresh its Context reference.");
79
+ }
80
+ text += page.text;
81
+ if (page.complete) return JSON.parse(text);
82
+ if (!page.nextCursor) throw new Error("Original read is incomplete; refresh its Context reference.");
83
+ query.set("cursor", page.nextCursor);
84
+ result = await requestJson(task(taskId) + "/inspect?" + query);
85
+ }
86
+ },
87
+ artifacts: function (taskId) { return requestJson(task(taskId) + "/artifacts"); },
88
+ artifact: function (taskId, path, commit) {
89
+ return requestJson(task(taskId) + "/artifacts?" + new URLSearchParams({ path, commit }));
90
+ },
91
+ evidence: function (taskId) { return requestJson(task(taskId) + "/evidence"); },
92
+ panels: function (taskId) { return requestJson(task(taskId) + "/panels", { signal: AbortSignal.timeout(3000) }); },
93
+ readPanel: function (taskId, ref, input) {
94
+ return postJson(task(taskId) + "/panels", { ref, input }, AbortSignal.timeout(3000));
95
+ },
96
+ sendMessage: function (taskId, body, requestId, intent) {
97
+ return submitMutation(taskId + "/messages", task(taskId) + "/messages",
98
+ { body, requestId, ...(intent === undefined ? {} : { intent }) });
99
+ },
100
+ control: function (taskId, payload) {
101
+ return submitMutation(taskId + "/control/" + payload.requestId, task(taskId) + "/control", payload);
102
+ },
103
+ updateTask: function (taskId, patch, requestId) {
104
+ return submitMutation(taskId + "/metadata", task(taskId) + "/metadata", { patch, requestId });
105
+ },
106
+ answerInput: function (taskId, inputId, answer) {
107
+ return submitMutation(taskId + "/input/" + inputId,
108
+ task(taskId) + "/inputs/" + encodeURIComponent(inputId) + "/answer", answer);
109
+ },
110
+ globalState: function (role) { return requestJson("/api/roles/" + encodeURIComponent(role) + "/control"); },
111
+ globalControl: function (role, input) {
112
+ return submitMutation("global/" + role, "/api/roles/" + encodeURIComponent(role) + "/control", input);
113
+ }
114
+ };
115
+ `;