@someonegg/agent-hub 0.0.0-stage → 0.7.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 (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -2
  3. package/README.zh-CN.md +99 -0
  4. package/README.zh-TW.md +99 -0
  5. package/dist/client/apple-touch-icon.png +0 -0
  6. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  7. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  8. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  11. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  12. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  13. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  14. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  17. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  18. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  19. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  20. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  21. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  22. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  23. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  24. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  25. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  26. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  27. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  28. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  29. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  30. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  31. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  32. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  33. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  34. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  35. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  36. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  37. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  38. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  44. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  45. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  46. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  47. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  48. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  49. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  50. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  51. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  52. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  53. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  54. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  55. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  56. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  57. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  58. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  59. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  60. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  61. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  62. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  63. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  64. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  65. package/dist/client/assets/index-4ejyszaV.css +1 -0
  66. package/dist/client/assets/index-CKKT4cMK.js +309 -0
  67. package/dist/client/favicon.ico +0 -0
  68. package/dist/client/favicon.svg +6 -0
  69. package/dist/client/icons/icon-192.png +0 -0
  70. package/dist/client/icons/icon-512.png +0 -0
  71. package/dist/client/icons/icon-maskable-512.png +0 -0
  72. package/dist/client/index.html +22 -0
  73. package/dist/client/manifest.webmanifest +21 -0
  74. package/dist/server/server/adapters/claude/claude-session-source.js +220 -0
  75. package/dist/server/server/adapters/claude/identity.js +28 -0
  76. package/dist/server/server/adapters/claude/interaction.js +68 -0
  77. package/dist/server/server/adapters/claude/prompt-readiness.js +12 -0
  78. package/dist/server/server/adapters/claude/ready-script.js +25 -0
  79. package/dist/server/server/adapters/claude/session-manager.js +106 -0
  80. package/dist/server/server/adapters/claude/session-normalizer.js +173 -0
  81. package/dist/server/server/adapters/claude/user-input-normalizer.js +71 -0
  82. package/dist/server/server/adapters/codex/catalog-source.js +2 -0
  83. package/dist/server/server/adapters/codex/codex-session-source.js +250 -0
  84. package/dist/server/server/adapters/codex/identity-resolver.js +104 -0
  85. package/dist/server/server/adapters/codex/interaction-parser.js +70 -0
  86. package/dist/server/server/adapters/codex/io-errors.js +2 -0
  87. package/dist/server/server/adapters/codex/jsonl-catalog-source.js +84 -0
  88. package/dist/server/server/adapters/codex/limits.js +2 -0
  89. package/dist/server/server/adapters/codex/message-normalizer.js +66 -0
  90. package/dist/server/server/adapters/codex/response-item-parser.js +64 -0
  91. package/dist/server/server/adapters/codex/rollout-descriptor.js +2 -0
  92. package/dist/server/server/adapters/codex/session-index-reader.js +101 -0
  93. package/dist/server/server/adapters/codex/session-manager.js +51 -0
  94. package/dist/server/server/adapters/codex/session-normalizer.js +175 -0
  95. package/dist/server/server/adapters/codex/subagent-identity.js +9 -0
  96. package/dist/server/server/adapters/codex/user-input-normalizer.js +121 -0
  97. package/dist/server/server/adapters/codex/value-readers.js +4 -0
  98. package/dist/server/server/adapters/shared/binding-protocol.js +54 -0
  99. package/dist/server/server/adapters/shared/io-errors.js +13 -0
  100. package/dist/server/server/adapters/shared/jsonl-decoder.js +263 -0
  101. package/dist/server/server/adapters/shared/limits.js +4 -0
  102. package/dist/server/server/adapters/shared/session-management.js +43 -0
  103. package/dist/server/server/adapters/shared/source-cache.js +23 -0
  104. package/dist/server/server/api/session-api-mapper.js +65 -0
  105. package/dist/server/server/application/interaction-session-reader.js +17 -0
  106. package/dist/server/server/application/session-read-service.js +86 -0
  107. package/dist/server/server/application/session-reader.js +2 -0
  108. package/dist/server/server/application/session-response.js +11 -0
  109. package/dist/server/server/catalog/config.js +4 -0
  110. package/dist/server/server/catalog/session-catalog-service.js +112 -0
  111. package/dist/server/server/cli/command-error.js +27 -0
  112. package/dist/server/server/cli/main.js +42 -0
  113. package/dist/server/server/cli/output.js +52 -0
  114. package/dist/server/server/cli/resource.js +104 -0
  115. package/dist/server/server/cli/session-client.js +64 -0
  116. package/dist/server/server/cli/session-follow.js +58 -0
  117. package/dist/server/server/cli/session-output.js +184 -0
  118. package/dist/server/server/cli/session-wait.js +44 -0
  119. package/dist/server/server/cli/session.js +318 -0
  120. package/dist/server/server/cli/skill.js +37 -0
  121. package/dist/server/server/config.js +196 -0
  122. package/dist/server/server/create-hub.js +91 -0
  123. package/dist/server/server/directory-lock.js +65 -0
  124. package/dist/server/server/domain/project-directory.js +5 -0
  125. package/dist/server/server/domain/session-domain.js +2 -0
  126. package/dist/server/server/domain/session-text.js +45 -0
  127. package/dist/server/server/hosting/diff-store.js +103 -0
  128. package/dist/server/server/hosting/routes.js +168 -0
  129. package/dist/server/server/hosting/store.js +162 -0
  130. package/dist/server/server/hosting/validation-error.js +4 -0
  131. package/dist/server/server/http/api-route-handler.js +2 -0
  132. package/dist/server/server/http/api-router.js +54 -0
  133. package/dist/server/server/http/create-server.js +218 -0
  134. package/dist/server/server/http/query-params.js +19 -0
  135. package/dist/server/server/http/request-body.js +48 -0
  136. package/dist/server/server/http/request-errors.js +59 -0
  137. package/dist/server/server/http/router.js +16 -0
  138. package/dist/server/server/http/security.js +19 -0
  139. package/dist/server/server/http/session-interaction-routes.js +128 -0
  140. package/dist/server/server/http/session-management-routes.js +105 -0
  141. package/dist/server/server/http/session-path.js +10 -0
  142. package/dist/server/server/http/session-read-routes.js +143 -0
  143. package/dist/server/server/http/terminal-preview-websocket.js +179 -0
  144. package/dist/server/server/http/upload-routes.js +124 -0
  145. package/dist/server/server/interaction/interaction-service.js +276 -0
  146. package/dist/server/server/interaction/message.js +19 -0
  147. package/dist/server/server/interaction/pane-binding-manager.js +62 -0
  148. package/dist/server/server/interaction/pane-observation.js +31 -0
  149. package/dist/server/server/interaction/reconcile-session-state.js +21 -0
  150. package/dist/server/server/interaction/runtime-capabilities.js +2 -0
  151. package/dist/server/server/interaction/session-runtime.js +86 -0
  152. package/dist/server/server/interaction/terminal-activity-service.js +81 -0
  153. package/dist/server/server/interaction/terminal-preview-diff.js +71 -0
  154. package/dist/server/server/interaction/terminal-preview-service.js +184 -0
  155. package/dist/server/server/interaction/tmux-launcher.js +84 -0
  156. package/dist/server/server/interaction/tmux-preview-listener.js +107 -0
  157. package/dist/server/server/interaction/tmux-service.js +334 -0
  158. package/dist/server/server/interaction/tmux-target.js +13 -0
  159. package/dist/server/server/interaction/transport.js +9 -0
  160. package/dist/server/server/main.js +54 -0
  161. package/dist/server/server/management/operation-service.js +307 -0
  162. package/dist/server/server/management/operation-state.js +3 -0
  163. package/dist/server/server/management/session-exit-service.js +50 -0
  164. package/dist/server/server/management/session-launch-executor.js +164 -0
  165. package/dist/server/server/management/session-lifecycle-executor.js +158 -0
  166. package/dist/server/server/management/session-manager.js +64 -0
  167. package/dist/server/server/repository/catalog-snapshot-store.js +318 -0
  168. package/dist/server/server/repository/diagnostic-logger.js +70 -0
  169. package/dist/server/server/repository/refresh-coordinator.js +14 -0
  170. package/dist/server/server/repository/session-queries.js +72 -0
  171. package/dist/server/server/security/opaque-id.js +17 -0
  172. package/dist/server/server/source/session-source.js +2 -0
  173. package/dist/server/server/updates/session-updates-service.js +151 -0
  174. package/dist/server/server/updates/update-revision.js +22 -0
  175. package/dist/server/server/uploads/store.js +304 -0
  176. package/dist/server/server/version.js +21 -0
  177. package/dist/server/shared/api-contract.js +2 -0
  178. package/dist/server/shared/diff.js +158 -0
  179. package/dist/server/shared/domain.js +2 -0
  180. package/dist/server/shared/interaction.js +22 -0
  181. package/dist/server/shared/task-name.js +6 -0
  182. package/dist/server/shared/uploads.js +8 -0
  183. package/package.json +98 -4
  184. package/skills/ahub-control/SKILL.md +93 -0
  185. package/skills/ahub-publish/SKILL.md +54 -0
@@ -0,0 +1,112 @@
1
+ import { CATALOG_PROBE_INTERVAL_MS, CATALOG_WAIT_TIMEOUT_MS, MAX_CATALOG_WAITERS, } from "./config.js";
2
+ export class CatalogCapacityError extends Error {
3
+ }
4
+ /** One probe loop for every waiting directory request. No loop when idle. */
5
+ export class SessionCatalogService {
6
+ sessions;
7
+ #waiters = new Set();
8
+ #timer;
9
+ #probe = null;
10
+ #closed = false;
11
+ #probing = false;
12
+ constructor(sessions) {
13
+ this.sessions = sessions;
14
+ }
15
+ async wait(after, signal) {
16
+ this.#assertOpen(signal);
17
+ if (this.#waiters.size >= MAX_CATALOG_WAITERS)
18
+ throw new CatalogCapacityError("Too many catalog requests are waiting; retry shortly");
19
+ return new Promise((resolve, reject) => {
20
+ let settled = false;
21
+ const cleanup = () => {
22
+ settled = true;
23
+ clearTimeout(timeout);
24
+ signal?.removeEventListener("abort", abort);
25
+ this.#waiters.delete(waiter);
26
+ if (this.#waiters.size === 0) {
27
+ clearTimeout(this.#timer);
28
+ this.#timer = undefined;
29
+ }
30
+ };
31
+ const waiter = {
32
+ after,
33
+ finish: (result) => {
34
+ if (!settled) {
35
+ cleanup();
36
+ resolve(result);
37
+ }
38
+ },
39
+ fail: (reason) => {
40
+ if (!settled) {
41
+ cleanup();
42
+ reject(reason);
43
+ }
44
+ },
45
+ };
46
+ const abort = () => waiter.fail(new DOMException("Aborted", "AbortError"));
47
+ const timeout = setTimeout(() => waiter.finish(null), CATALOG_WAIT_TIMEOUT_MS);
48
+ this.#waiters.add(waiter);
49
+ signal?.addEventListener("abort", abort, { once: true });
50
+ void this.#read().then((initial) => {
51
+ if (settled)
52
+ return;
53
+ if (initial.revision !== after)
54
+ waiter.finish(initial);
55
+ else
56
+ this.#schedule();
57
+ }, waiter.fail);
58
+ });
59
+ }
60
+ #assertOpen(signal) {
61
+ if (this.#closed || signal?.aborted)
62
+ throw new DOMException("Aborted", "AbortError");
63
+ }
64
+ #read(refresh = false) {
65
+ if (this.#probe !== null)
66
+ return this.#probe;
67
+ this.#probe = (async () => {
68
+ if (refresh)
69
+ await this.sessions.refresh();
70
+ return this.sessions.list();
71
+ })();
72
+ const probe = this.#probe;
73
+ void probe.then(() => {
74
+ if (this.#probe === probe)
75
+ this.#probe = null;
76
+ }, () => {
77
+ if (this.#probe === probe)
78
+ this.#probe = null;
79
+ });
80
+ return probe;
81
+ }
82
+ #schedule() {
83
+ if (this.#closed || this.#probing || this.#timer !== undefined || this.#waiters.size === 0)
84
+ return;
85
+ this.#timer = setTimeout(async () => {
86
+ this.#timer = undefined;
87
+ this.#probing = true;
88
+ try {
89
+ const snapshot = await this.#read(true);
90
+ for (const waiter of this.#waiters)
91
+ if (waiter.after !== snapshot.revision)
92
+ waiter.finish(snapshot);
93
+ }
94
+ catch (reason) {
95
+ for (const waiter of this.#waiters)
96
+ waiter.fail(reason);
97
+ }
98
+ finally {
99
+ this.#probing = false;
100
+ this.#schedule();
101
+ }
102
+ }, CATALOG_PROBE_INTERVAL_MS);
103
+ }
104
+ close() {
105
+ this.#closed = true;
106
+ clearTimeout(this.#timer);
107
+ this.#timer = undefined;
108
+ for (const waiter of this.#waiters)
109
+ waiter.finish(null);
110
+ }
111
+ }
112
+ //# sourceMappingURL=session-catalog-service.js.map
@@ -0,0 +1,27 @@
1
+ export class CommandError extends Error {
2
+ code;
3
+ exitCode;
4
+ diagnostics;
5
+ constructor(code, message, exitCode = 1, diagnostics) {
6
+ super(message);
7
+ this.code = code;
8
+ this.exitCode = exitCode;
9
+ this.diagnostics = diagnostics;
10
+ }
11
+ }
12
+ export function commandFailure(error, mutationSubmitted) {
13
+ if (error instanceof CommandError)
14
+ return error;
15
+ if (mutationSubmitted)
16
+ return new CommandError("operation_result_unknown", "Operation result unknown; the backend operation was not retried or canceled. Use list, inspect, or terminal to check the current state.", 2);
17
+ return new CommandError("query_failed", `Query failed: ${error instanceof Error ? error.message : String(error)}`);
18
+ }
19
+ export function httpFailure(status, method, error, mutationSubmitted) {
20
+ const unknown = (mutationSubmitted && (method === "GET" || error?.code === "operation_result_unknown")) ||
21
+ (status >= 500 && method !== "GET" && error?.code !== "interaction_failed");
22
+ if (unknown)
23
+ return new CommandError("operation_result_unknown", error?.message ??
24
+ "Operation result unknown; use list, inspect, or terminal to check the current state.", 2);
25
+ return new CommandError(error?.code === "after_out_of_range" ? error.code : "request_failed", error?.message ?? `HTTP ${status}`);
26
+ }
27
+ //# sourceMappingURL=command-error.js.map
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import { packageVersion } from "../version.js";
3
+ import { CommandError } from "./command-error.js";
4
+ import { outputFailure } from "./output.js";
5
+ const help = `Usage: ahub-cli <command> [options]
6
+ page Publish, list, or delete local hosted pages
7
+ diff Publish, list, or delete local review diffs
8
+ skill Locate bundled skills
9
+ session Manage sessions through an existing Hub HTTP server
10
+ version Show the current version (alias: --version)
11
+ Run ahub-cli <command> --help for details.`;
12
+ async function main() {
13
+ const [command, ...args] = process.argv.slice(2);
14
+ if (!command || command === "--help") {
15
+ console.log(help);
16
+ return;
17
+ }
18
+ if (command === "version" || command === "--version") {
19
+ if (args.length > 0)
20
+ throw new CommandError("invalid_arguments", "Usage: ahub-cli version");
21
+ console.log(await packageVersion());
22
+ }
23
+ else if (command === "skill") {
24
+ const { runSkillCommand } = await import("./skill.js");
25
+ await runSkillCommand(args);
26
+ }
27
+ else if (command === "session") {
28
+ const { runSessionCommand } = await import("./session.js");
29
+ await runSessionCommand(args);
30
+ }
31
+ else if (command === "page" || command === "diff") {
32
+ const { runResourceCommand } = await import("./resource.js");
33
+ await runResourceCommand(command, args[0], args.slice(1));
34
+ }
35
+ else {
36
+ throw new CommandError("invalid_arguments", `Unknown command: ${command}\n${help}`);
37
+ }
38
+ }
39
+ main().catch((error) => {
40
+ outputFailure(error, process.argv.slice(2).includes("--json"));
41
+ });
42
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,52 @@
1
+ import stringWidth from "string-width";
2
+ import { ResourceValidationError } from "../hosting/validation-error.js";
3
+ import { CommandError } from "./command-error.js";
4
+ export function singleLine(value) {
5
+ return value.replace(/[\r\n\t]/g, " ");
6
+ }
7
+ // Keep complete values (especially opaque IDs and URLs) and use deterministic
8
+ // widths for both terminals and redirected output. Ambiguous characters are narrow.
9
+ export function formatTable(headers, rows) {
10
+ const lines = [headers, ...rows].map((row) => row.map(singleLine));
11
+ const widths = headers.map((_, column) => lines.reduce((width, row) => Math.max(width, stringWidth(row[column] ?? "")), 0));
12
+ return lines
13
+ .map((row) => row
14
+ .map((cell, column) => column === headers.length - 1
15
+ ? cell
16
+ : cell + " ".repeat(widths[column] - stringWidth(cell) + 2))
17
+ .join("")
18
+ .trimEnd())
19
+ .join("\n");
20
+ }
21
+ export function formatTmux(target) {
22
+ if (!target)
23
+ return "unknown";
24
+ return singleLine(`${target.socketPath} ${target.sessionName}`);
25
+ }
26
+ export function output(result, json, human) {
27
+ console.log(json ? JSON.stringify(result) : human);
28
+ }
29
+ export function outputFailure(error, json) {
30
+ let failure;
31
+ if (error instanceof CommandError) {
32
+ failure = error;
33
+ }
34
+ else {
35
+ const code = error instanceof ResourceValidationError ? "invalid_arguments" : "command_failed";
36
+ const message = error instanceof Error ? error.message : "Command failed";
37
+ failure = new CommandError(code, message);
38
+ }
39
+ if (json)
40
+ console.log(JSON.stringify({
41
+ error: { code: failure.code, message: failure.message, ...failure.diagnostics },
42
+ }));
43
+ else {
44
+ const lines = [failure.message];
45
+ if (failure.diagnostics) {
46
+ lines.push(`Session: ${failure.diagnostics.sessionId ?? "unknown"}`, `Tmux: ${formatTmux(failure.diagnostics.tmux)}`);
47
+ }
48
+ console.error(lines.join("\n"));
49
+ }
50
+ process.exitCode = failure.exitCode;
51
+ }
52
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1,104 @@
1
+ import { join } from "node:path";
2
+ import { parseArgs } from "node:util";
3
+ import { resolveHomeDirectory } from "../config.js";
4
+ import { DiffStore } from "../hosting/diff-store.js";
5
+ import { HostingStore } from "../hosting/store.js";
6
+ import { CommandError } from "./command-error.js";
7
+ import { formatTable, output } from "./output.js";
8
+ function resourceHelp(resource) {
9
+ const publishUsage = resource === "page"
10
+ ? "page publish <directory> --project <directory> [--name <name>]"
11
+ : `${resource} publish <file>`;
12
+ return `Usage: ahub-cli ${resource} <command> [options]
13
+ ${publishUsage} [--title <title>]
14
+ ${resource} list
15
+ ${resource} delete <id>
16
+ Options: --home-dir <directory> --json --help
17
+ Commands operate on local Hub storage (default: ~/.agent-hub).`;
18
+ }
19
+ export async function runResourceCommand(resource, action, args) {
20
+ const { values, positionals, help } = parseResourceArguments(resource, action, args);
21
+ if (help) {
22
+ console.log(resourceHelp(resource));
23
+ return;
24
+ }
25
+ const directory = join(resolveHomeDirectory(values["home-dir"]), "hosting");
26
+ const pages = new HostingStore(directory);
27
+ const diffs = new DiffStore(directory);
28
+ if (action === "publish") {
29
+ validateResourcePublish(resource, values.project);
30
+ const entry = resource === "page"
31
+ ? await pages.publish(positionals[0], values.project, values.name, values.title)
32
+ : await diffs.publish(positionals[0], values.title);
33
+ const title = entry.title.replace(/[\\[\]]/g, "\\$&").replace(/[\r\n]/g, " ");
34
+ console.log(values.json ? JSON.stringify(entry) : `${entry.url}\n[${title}](${entry.url})`);
35
+ return;
36
+ }
37
+ if (action === "list") {
38
+ const entries = resource === "page"
39
+ ? (await pages.entries()).map(({ artifact }) => artifact)
40
+ : await diffs.list();
41
+ output(entries, values.json ?? false, formatResourceList(resource, entries));
42
+ return;
43
+ }
44
+ if (resource === "page")
45
+ await pages.delete(positionals[0]);
46
+ else
47
+ await diffs.delete(positionals[0]);
48
+ output({ id: positionals[0], deleted: true }, values.json ?? false, `Deleted ${resource} ${positionals[0]}`);
49
+ }
50
+ function formatResourceList(resource, entries) {
51
+ const table = formatTable(resource === "page" ? ["ID", "PROJECT", "TITLE", "URL"] : ["ID", "TITLE", "URL"], entries.map((entry) => [
52
+ entry.id,
53
+ ...(resource === "page" ? [entry.project ?? ""] : []),
54
+ entry.title,
55
+ entry.url,
56
+ ]));
57
+ return table + (entries.length ? "" : `\nNo ${resource === "page" ? "pages" : "diffs"}.`);
58
+ }
59
+ function parseResourceArgs(args) {
60
+ try {
61
+ return parseArgs({
62
+ args,
63
+ allowPositionals: true,
64
+ options: {
65
+ project: { type: "string" },
66
+ name: { type: "string" },
67
+ title: { type: "string" },
68
+ "home-dir": { type: "string" },
69
+ json: { type: "boolean" },
70
+ help: { type: "boolean" },
71
+ },
72
+ });
73
+ }
74
+ catch (error) {
75
+ throw new CommandError("invalid_arguments", error instanceof Error ? error.message : "Invalid arguments");
76
+ }
77
+ }
78
+ export function parseResourceArguments(resource, action, args) {
79
+ const { values, positionals } = parseResourceArgs(args);
80
+ if (values.help || action === "--help" || action === undefined) {
81
+ return { values, positionals, help: true };
82
+ }
83
+ if (!["publish", "list", "delete"].includes(action) ||
84
+ positionals.length !== (action === "list" ? 0 : 1))
85
+ throw new CommandError("invalid_arguments", "Expected page/diff publish <source>, list, or delete <id>");
86
+ const allowed = new Set(["home-dir", "json", "help"]);
87
+ if (action === "publish") {
88
+ allowed.add("title");
89
+ if (resource === "page") {
90
+ allowed.add("project");
91
+ allowed.add("name");
92
+ }
93
+ }
94
+ for (const option of Object.keys(values)) {
95
+ if (!allowed.has(option))
96
+ throw new CommandError("invalid_arguments", `Option --${option} is not supported for ${resource} ${action}`);
97
+ }
98
+ return { values, positionals, help: false };
99
+ }
100
+ export function validateResourcePublish(resource, project) {
101
+ if (resource === "page" && !project)
102
+ throw new CommandError("invalid_arguments", "page publish requires --project");
103
+ }
104
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1,64 @@
1
+ import { request } from "node:http";
2
+ import { request as secureRequest } from "node:https";
3
+ import { CommandError, httpFailure } from "./command-error.js";
4
+ /** Invocation-scoped HTTP state; submitted writes are never retried. */
5
+ export class SessionClient {
6
+ base;
7
+ signal;
8
+ mutationSubmitted = false;
9
+ constructor(base, signal) {
10
+ this.base = base;
11
+ this.signal = signal;
12
+ }
13
+ request = async (path, method = "GET", body) => {
14
+ if (this.signal.aborted)
15
+ throw new Error("Command interrupted or timed out");
16
+ const payload = body === undefined ? undefined : JSON.stringify(body);
17
+ return new Promise((accept, reject) => {
18
+ const sendRequest = this.base.protocol === "https:" ? secureRequest : request;
19
+ const req = sendRequest(new URL(path, this.base), {
20
+ method,
21
+ signal: this.signal,
22
+ headers: payload === undefined
23
+ ? {}
24
+ : {
25
+ "Content-Type": "application/json",
26
+ "Content-Length": Buffer.byteLength(payload),
27
+ },
28
+ }, (res) => {
29
+ const chunks = [];
30
+ res.on("data", (chunk) => chunks.push(chunk));
31
+ res.on("error", reject);
32
+ res.on("end", () => {
33
+ try {
34
+ const text = Buffer.concat(chunks).toString("utf8");
35
+ const data = text ? JSON.parse(text) : undefined;
36
+ const status = res.statusCode ?? 500;
37
+ if (status >= 400) {
38
+ reject(httpFailure(status, method, data?.error, this.mutationSubmitted));
39
+ }
40
+ else
41
+ accept(data);
42
+ }
43
+ catch (error) {
44
+ reject(error);
45
+ }
46
+ });
47
+ });
48
+ req.on("error", reject);
49
+ if (method !== "GET")
50
+ this.mutationSubmitted = true;
51
+ req.end(payload);
52
+ });
53
+ };
54
+ }
55
+ /** Reset an invalidated message cursor before the caller's single recovery attempt. */
56
+ export function recoverMessageQuery(error, query) {
57
+ if (!(error instanceof CommandError) || error.code !== "after_out_of_range")
58
+ return false;
59
+ query.set("after", "0");
60
+ query.delete("stateRevision");
61
+ query.delete("wait");
62
+ return true;
63
+ }
64
+ //# sourceMappingURL=session-client.js.map
@@ -0,0 +1,58 @@
1
+ import { CommandError } from "./command-error.js";
2
+ import { output } from "./output.js";
3
+ import { recoverMessageQuery, SessionClient } from "./session-client.js";
4
+ import { formatResult, mapInspect } from "./session-output.js";
5
+ /** Each round drains backlog before the next bounded tail wait. */
6
+ export async function followInspect(options) {
7
+ const { path, query, base, signal, timeoutMs, count, json } = options;
8
+ let previousState;
9
+ let emitted = false;
10
+ let recovered = false;
11
+ for (let round = 0; count === undefined || round < count; round++) {
12
+ for (;;) {
13
+ signal.throwIfAborted();
14
+ const controller = new AbortController();
15
+ const stop = () => controller.abort();
16
+ signal.addEventListener("abort", stop, { once: true });
17
+ const timer = setTimeout(stop, timeoutMs);
18
+ let observed;
19
+ try {
20
+ const client = new SessionClient(base, controller.signal);
21
+ observed = await client.request(`${path}/messages?${query}`);
22
+ }
23
+ catch (error) {
24
+ if (!recovered && recoverMessageQuery(error, query)) {
25
+ recovered = true;
26
+ continue;
27
+ }
28
+ throw error;
29
+ }
30
+ finally {
31
+ clearTimeout(timer);
32
+ signal.removeEventListener("abort", stop);
33
+ }
34
+ if (!observed)
35
+ break;
36
+ query.set("stateRevision", observed.stateRevision);
37
+ const result = mapInspect(observed);
38
+ const { updatedAt, ...sessionState } = result.session;
39
+ const state = JSON.stringify([sessionState, result.interaction]);
40
+ if (!emitted || result.items.length > 0 || previousState !== state) {
41
+ const text = formatResult("inspect", result, { emptyItemsText: "No new messages." });
42
+ output(result, json, (emitted ? "========================================\n" : "") + text);
43
+ emitted = true;
44
+ }
45
+ previousState = state;
46
+ const after = query.get("after");
47
+ if (result.hasMore && result.nextAfter <= Number(after ?? 0))
48
+ throw new CommandError("pagination_stalled", "Inspect pagination made no progress");
49
+ query.set("after", String(result.nextAfter));
50
+ if (!result.hasMore) {
51
+ query.set("wait", "true");
52
+ break;
53
+ }
54
+ query.delete("wait");
55
+ }
56
+ }
57
+ }
58
+ //# sourceMappingURL=session-follow.js.map
@@ -0,0 +1,184 @@
1
+ import { formatTable, formatTmux, singleLine } from "./output.js";
2
+ export function mapList(list, project, includeArchived = false) {
3
+ return {
4
+ sessions: list.sessions
5
+ .filter((s) => !s.isChild && (includeArchived || s.status !== "archived"))
6
+ .map((s) => ({
7
+ id: s.id,
8
+ title: s.title,
9
+ status: s.status,
10
+ agent: s.agent,
11
+ projectDirectory: s.projectDirectory,
12
+ ...(s.taskName ? { taskName: s.taskName } : {}),
13
+ updatedAt: s.updatedAt,
14
+ }))
15
+ .filter((s) => project === undefined || s.projectDirectory === project),
16
+ };
17
+ }
18
+ function inputHint(sessionId) {
19
+ return `Wait for connected and idle, then run session terminal ${sessionId} to check the current prompt and session keys ${sessionId} <key...> to select.`;
20
+ }
21
+ export function mapInspect(value) {
22
+ const s = value.session;
23
+ const i = value.interaction;
24
+ return {
25
+ session: {
26
+ id: s.id,
27
+ title: s.title,
28
+ status: s.status,
29
+ agent: s.origin.agent,
30
+ runtimeName: s.origin.runtimeName,
31
+ workingDirectory: s.workingDirectory,
32
+ projectDirectory: s.projectDirectory,
33
+ ...(s.taskName ? { taskName: s.taskName } : {}),
34
+ createdAt: s.createdAt,
35
+ updatedAt: s.updatedAt,
36
+ resume: { allowed: s.resume.allowed, reason: s.resume.reason },
37
+ },
38
+ interaction: i.supported
39
+ ? {
40
+ supported: true,
41
+ tmux: i.tmux
42
+ ? {
43
+ socketPath: i.tmux.socketPath,
44
+ sessionName: i.tmux.sessionName,
45
+ }
46
+ : null,
47
+ state: i.state,
48
+ activity: i.activity,
49
+ supersededBySessionId: i.supersededBySessionId,
50
+ }
51
+ : { supported: false, tmux: null },
52
+ items: value.items.flatMap((item) => {
53
+ const base = { id: item.id, ordinal: item.ordinal, timestamp: item.timestamp };
54
+ if (item.kind === "message")
55
+ return [
56
+ {
57
+ ...base,
58
+ kind: "message",
59
+ role: item.role,
60
+ phase: item.phase,
61
+ itemType: item.itemType,
62
+ markdown: item.markdown,
63
+ ...(item.role === "assistant" && item.phase === "response" && item.itemType === "plan"
64
+ ? { inputHint: inputHint(s.id) }
65
+ : {}),
66
+ },
67
+ ];
68
+ if (item.kind !== "user_input")
69
+ return [];
70
+ const input = { ...base, kind: "user_input" };
71
+ if (item.stage === "request")
72
+ return [
73
+ {
74
+ ...input,
75
+ stage: "request",
76
+ questions: item.questions.map((question) => ({
77
+ header: question.header,
78
+ question: question.question,
79
+ options: question.options.map((option) => ({
80
+ label: option.label,
81
+ description: option.description,
82
+ })),
83
+ })),
84
+ inputHint: inputHint(s.id),
85
+ },
86
+ ];
87
+ if (item.outcome === "answered")
88
+ return [
89
+ {
90
+ ...input,
91
+ stage: "response",
92
+ outcome: "answered",
93
+ answers: item.answers.flatMap((a) => a.answers),
94
+ },
95
+ ];
96
+ if (item.outcome === "unavailable")
97
+ return [{ ...input, stage: "response", outcome: "unavailable", summary: item.summary }];
98
+ if (item.outcome === "aborted")
99
+ return [{ ...input, stage: "response", outcome: "aborted" }];
100
+ return [];
101
+ }),
102
+ nextAfter: value.nextAfter,
103
+ hasMore: value.hasMore,
104
+ };
105
+ }
106
+ export function mapTerminal(s) {
107
+ return { content: s.content, truncated: s.truncated, capturedAt: s.capturedAt };
108
+ }
109
+ export function formatResult(command, result, options = {}) {
110
+ if ("content" in result)
111
+ return `${singleLine(result.capturedAt)}${result.truncated ? " (truncated)" : ""}\n${result.content}`;
112
+ if ("sessions" in result) {
113
+ const showTask = result.sessions.some((s) => Boolean(s.taskName));
114
+ return (formatTable(["ID", "STATUS", "AGENT", "PROJECT", ...(showTask ? ["TASK"] : []), "TITLE"], result.sessions.map((s) => [
115
+ s.id,
116
+ s.status,
117
+ s.agent ?? "",
118
+ s.projectDirectory ?? "",
119
+ ...(showTask ? [s.taskName ?? ""] : []),
120
+ s.title,
121
+ ])) + (result.sessions.length ? "" : "\nNo sessions."));
122
+ }
123
+ if (!("session" in result)) {
124
+ const lines = [`${command} ${singleLine(String(result.sessionId))}: ${result.status}`];
125
+ if (result.tmux)
126
+ lines.push(`Tmux: ${formatTmux(result.tmux)}`);
127
+ return lines.join("\n");
128
+ }
129
+ return formatInspect(result, options.emptyItemsText ?? "No visible items.");
130
+ }
131
+ function formatInspect(result, emptyItemsText) {
132
+ const { session, interaction } = result;
133
+ const summary = [
134
+ `Session: ${session.id}`,
135
+ `Title: ${session.title}`,
136
+ `Status: ${session.status}`,
137
+ `Interaction: ${interaction.supported ? interaction.state : "unsupported"}`,
138
+ ];
139
+ if (interaction.supported) {
140
+ if (interaction.tmux)
141
+ summary.push(`Tmux: ${formatTmux(interaction.tmux)}`);
142
+ summary.push(`Activity: ${interaction.activity ?? "unknown"}`);
143
+ if (interaction.supersededBySessionId)
144
+ summary.push(`Superseded by: ${interaction.supersededBySessionId}`);
145
+ }
146
+ summary.push(`Agent: ${session.agent ?? ""}`, `Project: ${session.projectDirectory ?? ""}`, ...(session.taskName ? [`Task: ${session.taskName}`] : []), `Working directory: ${session.workingDirectory ?? ""}`);
147
+ const header = summary.map(singleLine).join("\n");
148
+ const items = result.items.map(formatHistoryItem).join("\n\n");
149
+ return `${header}\n\n${items || emptyItemsText}\n\nNext after: ${result.nextAfter}\nHas more: ${result.hasMore ? "yes" : "no"}`;
150
+ }
151
+ function formatHistoryItem(item) {
152
+ if (item.kind === "message") {
153
+ const phase = [item.phase, item.itemType].filter(Boolean).join(" · ");
154
+ const title = `--- #${item.ordinal} ${item.role === "assistant" ? "Assistant" : "User"}${phase ? ` · ${singleLine(phase)}` : ""} ---`;
155
+ const body = item.inputHint ? `${item.markdown}\n\n${item.inputHint}` : item.markdown;
156
+ return `${title}\n${body}`;
157
+ }
158
+ if (item.stage === "request") {
159
+ const questions = item.questions
160
+ .map((question) => [
161
+ question.header,
162
+ question.question,
163
+ ...question.options.map((option, index) => `${index + 1}. ${option.label}\n ${option.description}`),
164
+ ].join("\n"))
165
+ .join("\n\n");
166
+ return [`--- #${item.ordinal} Question ---`, questions, item.inputHint]
167
+ .filter(Boolean)
168
+ .join("\n\n");
169
+ }
170
+ let body;
171
+ switch (item.outcome) {
172
+ case "answered":
173
+ body = item.answers.map((answer) => `- ${answer}`).join("\n");
174
+ break;
175
+ case "aborted":
176
+ body = "Cancelled.";
177
+ break;
178
+ case "unavailable":
179
+ body = `Unavailable: ${item.summary}`;
180
+ break;
181
+ }
182
+ return `--- #${item.ordinal} Answer ---\n${body}`;
183
+ }
184
+ //# sourceMappingURL=session-output.js.map