@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,84 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { opaqueIdForPath } from "../../security/opaque-id.js";
4
+ import { isExpectedIoError } from "./io-errors.js";
5
+ const MAX_DEPTH = 8;
6
+ const ROLLOUT_NAME = /^rollout-[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl$/;
7
+ export class JsonlCatalogSource {
8
+ configDirectory;
9
+ archivedDescriptors;
10
+ constructor(configDirectory, archivedDescriptors = new Map()) {
11
+ this.configDirectory = configDirectory;
12
+ this.archivedDescriptors = archivedDescriptors;
13
+ }
14
+ async discover() {
15
+ const entries = [];
16
+ const diagnostics = [];
17
+ const unreadable = (code) => {
18
+ if (diagnostics.length < 50)
19
+ diagnostics.push({
20
+ code,
21
+ severity: "warning",
22
+ message: "A session directory or rollout could not be read.",
23
+ ordinal: null,
24
+ });
25
+ };
26
+ const scan = async (directory, relative, archived, depth) => {
27
+ let children;
28
+ try {
29
+ children = await readdir(directory, { withFileTypes: true });
30
+ }
31
+ catch (error) {
32
+ if (!isExpectedIoError(error))
33
+ throw error;
34
+ if (depth !== 0 || error.code !== "ENOENT")
35
+ unreadable("session_root_unreadable");
36
+ return;
37
+ }
38
+ for (const child of children) {
39
+ const path = join(directory, child.name);
40
+ const sourceRelativePath = `${relative}/${child.name}`;
41
+ try {
42
+ const linkedInfo = child.isSymbolicLink() ? await stat(path) : undefined;
43
+ const info = linkedInfo ?? child;
44
+ if (info.isDirectory()) {
45
+ if (depth < MAX_DEPTH)
46
+ await scan(path, sourceRelativePath, archived, depth + 1);
47
+ continue;
48
+ }
49
+ if (!info.isFile() || !ROLLOUT_NAME.test(child.name))
50
+ continue;
51
+ const cached = archived ? this.archivedDescriptors.get(path) : undefined;
52
+ if (cached !== undefined) {
53
+ entries.push({ descriptor: cached });
54
+ continue;
55
+ }
56
+ const metadata = linkedInfo ?? (await stat(path));
57
+ if (!metadata.isFile())
58
+ continue;
59
+ entries.push({
60
+ descriptor: {
61
+ id: opaqueIdForPath(path),
62
+ path,
63
+ sourceRelativePath,
64
+ archived,
65
+ size: metadata.size,
66
+ mtimeMs: metadata.mtimeMs,
67
+ },
68
+ });
69
+ }
70
+ catch (error) {
71
+ if (!isExpectedIoError(error))
72
+ throw error;
73
+ unreadable("rollout_unavailable");
74
+ }
75
+ }
76
+ };
77
+ for (const name of ["sessions", "archived_sessions"]) {
78
+ await scan(resolve(this.configDirectory, name), name, name === "archived_sessions", 0);
79
+ }
80
+ entries.sort((a, b) => a.descriptor.path.localeCompare(b.descriptor.path));
81
+ return { entries, diagnostics };
82
+ }
83
+ }
84
+ //# sourceMappingURL=jsonl-catalog-source.js.map
@@ -0,0 +1,2 @@
1
+ export * from "../shared/limits.js";
2
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1,66 @@
1
+ import { truncateText } from "../../domain/session-text.js";
2
+ import { isObject } from "../shared/jsonl-decoder.js";
3
+ import { MAX_MESSAGE_CHARS } from "./limits.js";
4
+ import { nonEmptyString } from "./value-readers.js";
5
+ export function eventMessage(ordinal, timestamp, payload) {
6
+ const type = nonEmptyString(payload.type);
7
+ if (type === "user_message" || type === "agent_message") {
8
+ const markdown = nonEmptyString(payload.message);
9
+ if (markdown === null)
10
+ return null;
11
+ const role = type === "user_message" ? "user" : "assistant";
12
+ const phase = type === "agent_message" ? normalizePhase(payload.phase) : null;
13
+ return messageItem(ordinal, timestamp, role, phase, null, markdown);
14
+ }
15
+ if (type !== "item_completed" || !isObject(payload.item))
16
+ return null;
17
+ const item = payload.item;
18
+ const itemType = nonEmptyString(item.type);
19
+ if (itemType === "UserMessage") {
20
+ const markdown = itemContentText(item.content, "text");
21
+ return markdown === null ? null : messageItem(ordinal, timestamp, "user", null, null, markdown);
22
+ }
23
+ if (itemType === "AgentMessage") {
24
+ const markdown = itemContentText(item.content, "Text");
25
+ return markdown === null
26
+ ? null
27
+ : messageItem(ordinal, timestamp, "assistant", normalizePhase(item.phase), null, markdown);
28
+ }
29
+ if (itemType !== "Plan")
30
+ return null;
31
+ const markdown = nonEmptyString(item.text);
32
+ return markdown === null
33
+ ? null
34
+ : messageItem(ordinal, timestamp, "assistant", "response", "plan", markdown);
35
+ }
36
+ function messageItem(ordinal, timestamp, role, phase, itemType, markdown) {
37
+ return {
38
+ kind: "message",
39
+ id: `message-${ordinal}`,
40
+ ordinal,
41
+ timestamp,
42
+ role,
43
+ phase,
44
+ itemType,
45
+ markdown: truncateText(markdown, MAX_MESSAGE_CHARS).text,
46
+ };
47
+ }
48
+ function itemContentText(value, acceptedType) {
49
+ if (!Array.isArray(value))
50
+ return null;
51
+ const text = value
52
+ .filter(isObject)
53
+ .filter((part) => part.type === acceptedType)
54
+ .map((part) => nonEmptyString(part.text))
55
+ .filter((part) => part !== null)
56
+ .join("\n\n");
57
+ return text.length === 0 ? null : text;
58
+ }
59
+ function normalizePhase(value) {
60
+ if (value === "commentary")
61
+ return "update";
62
+ if (value === "final" || value === "final_answer")
63
+ return "response";
64
+ return null;
65
+ }
66
+ //# sourceMappingURL=message-normalizer.js.map
@@ -0,0 +1,64 @@
1
+ import { isObject } from "../shared/jsonl-decoder.js";
2
+ import { parseUserInputQuestions } from "./user-input-normalizer.js";
3
+ import { nonEmptyString } from "./value-readers.js";
4
+ export function parseResponseItem(ordinal, timestamp, payload) {
5
+ const userInput = userInputRequest(ordinal, timestamp, payload);
6
+ if (userInput !== null)
7
+ return { kind: "user_input_request", value: userInput };
8
+ const output = toolOutput(ordinal, timestamp, payload);
9
+ if (output !== null)
10
+ return { kind: "tool_output", value: output };
11
+ return { kind: "ignored" };
12
+ }
13
+ function userInputRequest(ordinal, timestamp, payload) {
14
+ if (payload.type !== "function_call" || payload.name !== "request_user_input")
15
+ return null;
16
+ const callId = nonEmptyString(payload.call_id);
17
+ if (callId === null)
18
+ return null;
19
+ const questions = parseUserInputQuestions(payload.arguments);
20
+ return {
21
+ callId,
22
+ ordinal,
23
+ timestamp,
24
+ questions: questions ?? [],
25
+ malformed: questions === null,
26
+ };
27
+ }
28
+ function toolOutput(ordinal, timestamp, payload) {
29
+ const type = nonEmptyString(payload.type);
30
+ if (type !== "function_call_output" && type !== "custom_tool_call_output")
31
+ return null;
32
+ const callId = nonEmptyString(payload.call_id);
33
+ if (callId === null)
34
+ return null;
35
+ return {
36
+ callId,
37
+ ordinal,
38
+ timestamp,
39
+ output: toolOutputText(payload.output),
40
+ failed: payload.success === false || payload.status === "failed",
41
+ };
42
+ }
43
+ function serializeText(value) {
44
+ if (typeof value === "string")
45
+ return value;
46
+ if (value === null || value === undefined)
47
+ return null;
48
+ try {
49
+ return JSON.stringify(value);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function toolOutputText(value) {
56
+ if (!Array.isArray(value))
57
+ return serializeText(value);
58
+ const parts = value
59
+ .filter(isObject)
60
+ .map((part) => nonEmptyString(part.text) ?? nonEmptyString(part.output_text) ?? serializeText(part))
61
+ .filter((part) => part !== null);
62
+ return parts.length === 0 ? serializeText(value) : parts.join("\n\n");
63
+ }
64
+ //# sourceMappingURL=response-item-parser.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rollout-descriptor.js.map
@@ -0,0 +1,101 @@
1
+ import { open } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { normalizeSessionTitle } from "../../domain/session-text.js";
4
+ import { MAX_JSONL_LINE_BYTES } from "./limits.js";
5
+ export class SessionIndexReader {
6
+ #path;
7
+ #fingerprint = null;
8
+ #threadNames = new Map();
9
+ constructor(configDirectory) {
10
+ this.#path = join(configDirectory, "session_index.jsonl");
11
+ }
12
+ async refresh() {
13
+ let handle;
14
+ try {
15
+ handle = await open(this.#path, "r");
16
+ const info = await handle.stat();
17
+ const fingerprint = { size: info.size, mtimeMs: info.mtimeMs };
18
+ if (sameFingerprint(this.#fingerprint, fingerprint))
19
+ return;
20
+ const threadNames = info.size === 0
21
+ ? new Map()
22
+ : await readCompleteRecords(handle.createReadStream({ start: 0, end: info.size - 1, autoClose: false }));
23
+ this.#threadNames = threadNames;
24
+ this.#fingerprint = fingerprint;
25
+ }
26
+ catch {
27
+ this.#threadNames = new Map();
28
+ this.#fingerprint = null;
29
+ }
30
+ finally {
31
+ await handle?.close().catch(() => undefined);
32
+ }
33
+ }
34
+ threadName(id) {
35
+ return this.#threadNames.get(id) ?? null;
36
+ }
37
+ }
38
+ async function readCompleteRecords(stream) {
39
+ const threadNames = new Map();
40
+ let parts = [];
41
+ let bytes = 0;
42
+ let droppingOversizedLine = false;
43
+ for await (const rawChunk of stream) {
44
+ const chunk = typeof rawChunk === "string" ? Buffer.from(rawChunk) : rawChunk;
45
+ let cursor = 0;
46
+ while (cursor < chunk.length) {
47
+ const newline = chunk.indexOf(0x0a, cursor);
48
+ const end = newline === -1 ? chunk.length : newline;
49
+ const part = chunk.subarray(cursor, end);
50
+ if (!droppingOversizedLine) {
51
+ if (bytes + part.length <= MAX_JSONL_LINE_BYTES) {
52
+ if (part.length > 0)
53
+ parts.push(part);
54
+ bytes += part.length;
55
+ }
56
+ else {
57
+ parts = [];
58
+ bytes = 0;
59
+ droppingOversizedLine = true;
60
+ }
61
+ }
62
+ if (newline === -1)
63
+ break;
64
+ if (!droppingOversizedLine)
65
+ consumeLine(Buffer.concat(parts, bytes), threadNames);
66
+ parts = [];
67
+ bytes = 0;
68
+ droppingOversizedLine = false;
69
+ cursor = newline + 1;
70
+ }
71
+ }
72
+ // An unterminated tail may still be in flight, so it is deliberately ignored.
73
+ return threadNames;
74
+ }
75
+ function consumeLine(line, threadNames) {
76
+ try {
77
+ const value = JSON.parse(line.toString("utf8"));
78
+ if (!isObject(value))
79
+ return;
80
+ const id = nonEmptyString(value.id);
81
+ const threadName = nonEmptyString(value.thread_name);
82
+ if (id === null || threadName === null)
83
+ return;
84
+ const normalized = normalizeSessionTitle(threadName);
85
+ if (normalized !== null)
86
+ threadNames.set(id, normalized);
87
+ }
88
+ catch {
89
+ // session_index.jsonl is advisory; malformed records do not affect sessions.
90
+ }
91
+ }
92
+ function nonEmptyString(value) {
93
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
94
+ }
95
+ function isObject(value) {
96
+ return typeof value === "object" && value !== null && !Array.isArray(value);
97
+ }
98
+ function sameFingerprint(left, right) {
99
+ return left !== null && left.size === right.size && left.mtimeMs === right.mtimeMs;
100
+ }
101
+ //# sourceMappingURL=session-index-reader.js.map
@@ -0,0 +1,51 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { TmuxLauncher } from "../../interaction/tmux-launcher.js";
4
+ import { exitSteps, resolveDirectory } from "../shared/session-management.js";
5
+ import { codexBindingCommand } from "./interaction-parser.js";
6
+ const execute = promisify(execFile);
7
+ export class CodexSessionManager {
8
+ configDirectory;
9
+ tmux;
10
+ constructor(configDirectory, tmux = new TmuxLauncher()) {
11
+ this.configDirectory = configDirectory;
12
+ this.tmux = tmux;
13
+ }
14
+ resolveDirectory = resolveDirectory;
15
+ isValidNativeId(nativeId) {
16
+ return /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(nativeId);
17
+ }
18
+ launch({ operationId, workingDirectory, worktree, yolo, resumeNativeId, projectDirectory, taskName, }) {
19
+ if (resumeNativeId !== undefined && !this.isValidNativeId(resumeNativeId))
20
+ throw new Error("Invalid resume identity");
21
+ return this.tmux.launch(operationId, workingDirectory, [
22
+ "env",
23
+ `CODEX_HOME=${this.configDirectory}`,
24
+ "codex",
25
+ ...(resumeNativeId ? ["resume", resumeNativeId] : []),
26
+ "--no-daemon",
27
+ "--no-alt-screen",
28
+ ...(worktree ? ["--worktree"] : []),
29
+ ...(yolo ? ["--yolo"] : []),
30
+ codexBindingCommand(operationId, projectDirectory, taskName ?? null),
31
+ ]);
32
+ }
33
+ exitSteps = exitSteps;
34
+ async archive(nativeId) {
35
+ await this.#command(["archive", nativeId]);
36
+ }
37
+ async delete(nativeId) {
38
+ await this.#command(["delete", nativeId, "--force"]);
39
+ }
40
+ async #command(args) {
41
+ if (!this.isValidNativeId(args[1]))
42
+ throw new Error("Session has no valid native UUID");
43
+ await execute("codex", args, {
44
+ env: { ...process.env, CODEX_HOME: this.configDirectory },
45
+ timeout: 25_000,
46
+ maxBuffer: 64 * 1024,
47
+ });
48
+ }
49
+ }
50
+ export const createCodexSessionManager = (configDirectory, socket) => new CodexSessionManager(configDirectory, new TmuxLauncher(socket));
51
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1,175 @@
1
+ import { normalizeSessionTitle, sessionTitleFromMarkdown } from "../../domain/session-text.js";
2
+ import { isObject } from "../shared/jsonl-decoder.js";
3
+ import { codexInteraction } from "./interaction-parser.js";
4
+ import { MAX_SESSION_DIAGNOSTICS } from "./limits.js";
5
+ import { eventMessage } from "./message-normalizer.js";
6
+ import { parseResponseItem } from "./response-item-parser.js";
7
+ import { normalizeUserInputRequest, normalizeUserInputResponse, } from "./user-input-normalizer.js";
8
+ import { nonEmptyString } from "./value-readers.js";
9
+ export class DefaultSessionNormalizer {
10
+ create(descriptor) {
11
+ return {
12
+ descriptor,
13
+ timeline: [],
14
+ pendingUserInputRequests: new Map(),
15
+ decoderDiagnostics: [],
16
+ normalizerDiagnostics: [],
17
+ hasFirstUserMessage: false,
18
+ firstUserTitle: null,
19
+ };
20
+ }
21
+ append(state, records, decoderDiagnostics) {
22
+ const nextDecoderDiagnostics = decoderDiagnostics.slice(0, MAX_SESSION_DIAGNOSTICS);
23
+ const decoderDiagnosticsUnchanged = sameDiagnostics(state.decoderDiagnostics, nextDecoderDiagnostics);
24
+ if (records.length === 0 && decoderDiagnosticsUnchanged) {
25
+ return state;
26
+ }
27
+ const items = [];
28
+ const pendingUserInputRequests = new LazyMap(state.pendingUserInputRequests);
29
+ const normalizerDiagnostics = [...state.normalizerDiagnostics];
30
+ for (const record of records) {
31
+ consumeRecord(record, items, pendingUserInputRequests, normalizerDiagnostics);
32
+ }
33
+ let hasFirstUserMessage = state.hasFirstUserMessage;
34
+ let firstTitle = state.firstUserTitle;
35
+ for (const item of items) {
36
+ if (!hasFirstUserMessage && item.kind === "message" && item.role === "user") {
37
+ hasFirstUserMessage = true;
38
+ firstTitle = sessionTitleFromMarkdown(item.markdown);
39
+ }
40
+ }
41
+ return {
42
+ ...state,
43
+ timeline: items.length === 0 ? state.timeline : [...state.timeline, ...items],
44
+ pendingUserInputRequests: pendingUserInputRequests.result,
45
+ decoderDiagnostics: decoderDiagnosticsUnchanged
46
+ ? state.decoderDiagnostics
47
+ : nextDecoderDiagnostics,
48
+ normalizerDiagnostics,
49
+ hasFirstUserMessage,
50
+ firstUserTitle: firstTitle,
51
+ };
52
+ }
53
+ materialize(state, metadata, origin = DEFAULT_SESSION_ORIGIN) {
54
+ const diagnostics = combinedDiagnostics(state);
55
+ const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity !== "info").length;
56
+ const title = normalizeSessionTitle(metadata.title) ?? state.firstUserTitle ?? "Untitled session";
57
+ const session = {
58
+ id: state.descriptor.id,
59
+ sourceId: metadata.threadId,
60
+ origin,
61
+ title,
62
+ workingDirectory: metadata.workingDirectory,
63
+ projectDirectory: metadata.projectDirectory,
64
+ createdAt: metadata.createdAt,
65
+ updatedAt: metadata.updatedAt,
66
+ archived: metadata.archived,
67
+ parentId: metadata.parentThreadId,
68
+ childIds: [],
69
+ subagent: metadata.subagent ?? null,
70
+ warningCount,
71
+ diagnostics,
72
+ };
73
+ return {
74
+ session,
75
+ timeline: state.timeline,
76
+ interaction: codexInteraction(),
77
+ };
78
+ }
79
+ }
80
+ function combinedDiagnostics(state) {
81
+ const remaining = MAX_SESSION_DIAGNOSTICS - state.decoderDiagnostics.length;
82
+ return [...state.decoderDiagnostics, ...state.normalizerDiagnostics.slice(0, remaining)];
83
+ }
84
+ function sameDiagnostics(left, right) {
85
+ return (left.length === right.length &&
86
+ left.every((item, index) => {
87
+ const candidate = right[index];
88
+ return (candidate !== undefined &&
89
+ item.code === candidate.code &&
90
+ item.severity === candidate.severity &&
91
+ item.message === candidate.message &&
92
+ item.ordinal === candidate.ordinal);
93
+ }));
94
+ }
95
+ class LazyMap {
96
+ original;
97
+ #copy;
98
+ constructor(original) {
99
+ this.original = original;
100
+ }
101
+ get result() {
102
+ return this.#copy ?? this.original;
103
+ }
104
+ set(key, value) {
105
+ if (this.result.has(key) && this.result.get(key) === value)
106
+ return;
107
+ (this.#copy ??= new Map(this.original)).set(key, value);
108
+ }
109
+ delete(key) {
110
+ if (!this.result.has(key))
111
+ return false;
112
+ return (this.#copy ??= new Map(this.original)).delete(key);
113
+ }
114
+ }
115
+ const DEFAULT_SESSION_ORIGIN = {
116
+ sourceType: "test",
117
+ agent: "test",
118
+ runtimeName: "Test",
119
+ runtimeVersion: null,
120
+ };
121
+ function consumeRecord(record, items, pendingUserInputRequests, diagnostics) {
122
+ const timestamp = nonEmptyString(record.value.timestamp);
123
+ const payload = record.value.payload;
124
+ if (!isObject(payload)) {
125
+ if (typeof record.value.type !== "string")
126
+ appendDiagnostic(diagnostics, {
127
+ code: "unknown_record",
128
+ severity: "info",
129
+ message: "A record without a recognized type was reduced to a safe diagnostic.",
130
+ ordinal: record.ordinal,
131
+ });
132
+ return;
133
+ }
134
+ if (record.value.type === "response_item") {
135
+ consumeParsedResponse(parseResponseItem(record.ordinal, timestamp, payload), items, pendingUserInputRequests, diagnostics);
136
+ }
137
+ else if (record.value.type === "event_msg") {
138
+ const message = eventMessage(record.ordinal, timestamp, payload);
139
+ if (message)
140
+ items.push(message);
141
+ }
142
+ }
143
+ function appendDiagnostic(diagnostics, diagnostic) {
144
+ if (diagnostics.length < MAX_SESSION_DIAGNOSTICS) {
145
+ diagnostics.push(diagnostic);
146
+ }
147
+ }
148
+ function consumeParsedResponse(parsed, items, pendingUserInputRequests, diagnostics) {
149
+ switch (parsed.kind) {
150
+ case "tool_output": {
151
+ if (pendingUserInputRequests.delete(parsed.value.callId)) {
152
+ addUserInput(normalizeUserInputResponse(parsed.value), items, diagnostics);
153
+ }
154
+ break;
155
+ }
156
+ case "user_input_request":
157
+ pendingUserInputRequests.set(parsed.value.callId, parsed.value);
158
+ addUserInput(normalizeUserInputRequest(parsed.value), items, diagnostics);
159
+ break;
160
+ case "ignored":
161
+ break;
162
+ }
163
+ }
164
+ function addUserInput(accumulated, items, diagnostics) {
165
+ items.push(accumulated.item);
166
+ if (accumulated.malformed) {
167
+ appendDiagnostic(diagnostics, {
168
+ code: "invalid_user_input",
169
+ severity: "warning",
170
+ message: "A request_user_input call or output had an unsupported payload shape.",
171
+ ordinal: accumulated.item.ordinal,
172
+ });
173
+ }
174
+ }
175
+ //# sourceMappingURL=session-normalizer.js.map
@@ -0,0 +1,9 @@
1
+ export function nonEmptySubagentIdentity(fields) {
2
+ return Object.values(fields).some((field) => field !== null) ? fields : null;
3
+ }
4
+ export function taskNameFromSubagentPath(agentPath) {
5
+ if (agentPath === null)
6
+ return null;
7
+ return agentPath.split("/").filter(Boolean).at(-1) ?? null;
8
+ }
9
+ //# sourceMappingURL=subagent-identity.js.map
@@ -0,0 +1,121 @@
1
+ import { MAX_PREVIEW_CHARS, truncateText } from "../../domain/session-text.js";
2
+ import { isObject } from "../shared/jsonl-decoder.js";
3
+ import { nonEmptyString } from "./value-readers.js";
4
+ export function normalizeUserInputRequest(request) {
5
+ return {
6
+ item: {
7
+ kind: "user_input",
8
+ stage: "request",
9
+ id: `user-input-${request.ordinal}`,
10
+ ordinal: request.ordinal,
11
+ timestamp: request.timestamp,
12
+ callId: request.callId,
13
+ questions: request.questions,
14
+ },
15
+ malformed: request.malformed,
16
+ };
17
+ }
18
+ export function normalizeUserInputResponse(output) {
19
+ const parsed = parseResponse(output.output);
20
+ const base = {
21
+ kind: "user_input",
22
+ stage: "response",
23
+ id: `user-input-${output.ordinal}`,
24
+ ordinal: output.ordinal,
25
+ timestamp: output.timestamp,
26
+ callId: output.callId,
27
+ };
28
+ if (parsed.kind === "answered") {
29
+ return {
30
+ item: { ...base, outcome: "answered", answers: parsed.answers },
31
+ malformed: false,
32
+ };
33
+ }
34
+ if (parsed.kind === "aborted") {
35
+ return {
36
+ item: { ...base, outcome: "aborted" },
37
+ malformed: false,
38
+ };
39
+ }
40
+ return {
41
+ item: {
42
+ ...base,
43
+ outcome: "unavailable",
44
+ summary: truncateText(output.output ?? "User input response was unavailable.", MAX_PREVIEW_CHARS).text,
45
+ },
46
+ malformed: true,
47
+ };
48
+ }
49
+ export function parseUserInputQuestions(value) {
50
+ const object = parseObject(value);
51
+ if (object === null || !Array.isArray(object.questions))
52
+ return null;
53
+ if (object.questions.length < 1 || object.questions.length > 3)
54
+ return null;
55
+ const questions = object.questions.map(parseQuestion);
56
+ if (!questions.every((question) => question !== null))
57
+ return null;
58
+ const parsed = questions;
59
+ return new Set(parsed.map((question) => question.id)).size === parsed.length ? parsed : null;
60
+ }
61
+ function parseQuestion(value) {
62
+ if (!isObject(value) || !Array.isArray(value.options))
63
+ return null;
64
+ if (value.options.length < 2 || value.options.length > 3)
65
+ return null;
66
+ const id = nonEmptyString(value.id);
67
+ const header = nonEmptyString(value.header);
68
+ const question = nonEmptyString(value.question);
69
+ const options = value.options.map((option) => {
70
+ if (!isObject(option))
71
+ return null;
72
+ const label = nonEmptyString(option.label);
73
+ const description = nonEmptyString(option.description);
74
+ return label === null || description === null ? null : { label, description };
75
+ });
76
+ if (id === null ||
77
+ header === null ||
78
+ question === null ||
79
+ !options.every((option) => option !== null))
80
+ return null;
81
+ const parsedOptions = options;
82
+ if (new Set(parsedOptions.map((option) => option.label)).size !== parsedOptions.length) {
83
+ return null;
84
+ }
85
+ return { id, header, question, options: parsedOptions };
86
+ }
87
+ function parseResponse(value) {
88
+ if (value === null)
89
+ return { kind: "unavailable" };
90
+ if (value.trim().toLocaleLowerCase("en-US").startsWith("aborted by user")) {
91
+ return { kind: "aborted" };
92
+ }
93
+ const object = parseObject(value);
94
+ if (object === null || !isObject(object.answers))
95
+ return { kind: "unavailable" };
96
+ const answers = [];
97
+ for (const [questionId, rawAnswer] of Object.entries(object.answers)) {
98
+ if (!isObject(rawAnswer) || !Array.isArray(rawAnswer.answers)) {
99
+ return { kind: "unavailable" };
100
+ }
101
+ const values = rawAnswer.answers.filter((answer) => typeof answer === "string");
102
+ if (values.length !== rawAnswer.answers.length)
103
+ return { kind: "unavailable" };
104
+ answers.push({ questionId, answers: values });
105
+ }
106
+ return { kind: "answered", answers };
107
+ }
108
+ function parseObject(value) {
109
+ if (isObject(value))
110
+ return value;
111
+ if (typeof value !== "string")
112
+ return null;
113
+ try {
114
+ const parsed = JSON.parse(value);
115
+ return isObject(parsed) ? parsed : null;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ //# sourceMappingURL=user-input-normalizer.js.map