@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,173 @@
1
+ import { normalizeSessionTitle, truncateText } from "../../domain/session-text.js";
2
+ import { mergeBindingMetadata } from "../shared/binding-protocol.js";
3
+ import { MAX_MESSAGE_CHARS, MAX_SESSION_DIAGNOSTICS } from "../shared/limits.js";
4
+ import { appendClaudeIdentity } from "./identity.js";
5
+ import { claudeRecordBindings } from "./interaction.js";
6
+ import { normalizeQuestionResponse, normalizeQuestions } from "./user-input-normalizer.js";
7
+ export const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
8
+ const string = (value) => (typeof value === "string" ? value : null);
9
+ /** Decoder codes are renamed to adapter-neutral session diagnostics. */
10
+ export function claudeDecoderDiagnostics(diagnostics) {
11
+ return diagnostics.map((d) => d.code === "malformed_json" ? { ...d, code: "invalid_jsonl" } : d);
12
+ }
13
+ export function createClaudeState(nativeId) {
14
+ return {
15
+ timeline: [],
16
+ diagnostics: [],
17
+ questions: new Map(),
18
+ decoderDiagnostics: [],
19
+ normalizerDiagnostics: [],
20
+ line: 0,
21
+ nativeId,
22
+ firstCwd: null,
23
+ cwd: null,
24
+ project: null,
25
+ taskName: null,
26
+ customTitle: null,
27
+ aiTitle: null,
28
+ firstUser: null,
29
+ version: null,
30
+ createdAt: null,
31
+ updatedAt: null,
32
+ identityConflict: false,
33
+ };
34
+ }
35
+ export function appendClaudeRecords(previous, records, diagnostics = [], bindings = records.flatMap(claudeRecordBindings)) {
36
+ if (records.length === 0 &&
37
+ JSON.stringify(previous.decoderDiagnostics) === JSON.stringify(diagnostics))
38
+ return previous;
39
+ const state = {
40
+ ...previous,
41
+ diagnostics: [...previous.normalizerDiagnostics],
42
+ };
43
+ const newItems = [];
44
+ let pendingQuestions = previous.questions;
45
+ const mutableQuestions = () => {
46
+ if (pendingQuestions === previous.questions)
47
+ pendingQuestions = new Map(previous.questions);
48
+ return pendingQuestions;
49
+ };
50
+ const emit = (item) => {
51
+ const ordinal = previous.timeline.length + newItems.length + 1;
52
+ if (item.kind === "message")
53
+ item = { ...item, markdown: truncateText(item.markdown, MAX_MESSAGE_CHARS).text };
54
+ newItems.push({ ...item, id: `${state.nativeId}:${ordinal}`, ordinal });
55
+ };
56
+ const retainDiagnostic = (diagnostic) => {
57
+ const index = state.diagnostics.findIndex((existing) => (existing.ordinal ?? Infinity) > (diagnostic.ordinal ?? Infinity));
58
+ if (index >= 0)
59
+ state.diagnostics.splice(index, 0, diagnostic);
60
+ else if (state.diagnostics.length < MAX_SESSION_DIAGNOSTICS)
61
+ state.diagnostics.push(diagnostic);
62
+ state.diagnostics.length = Math.min(state.diagnostics.length, MAX_SESSION_DIAGNOSTICS);
63
+ };
64
+ for (const { ordinal, value: row } of records) {
65
+ state.line = ordinal;
66
+ if (!object(row) || row.isSidechain === true || row.parent_tool_use_id != null)
67
+ continue;
68
+ // API copies and progress/subagent notifications never supply conversation or bindings.
69
+ if (!["user", "assistant", "custom-title", "ai-title", "system"].includes(String(row.type)))
70
+ continue;
71
+ if (!appendClaudeIdentity(state, row, ordinal, retainDiagnostic))
72
+ continue;
73
+ const timestamp = string(row.timestamp);
74
+ if (row.type === "custom-title") {
75
+ state.customTitle = normalizeSessionTitle(string(row.customTitle)) || state.customTitle;
76
+ continue;
77
+ }
78
+ if (row.type === "ai-title") {
79
+ state.aiTitle = normalizeSessionTitle(string(row.aiTitle)) || state.aiTitle;
80
+ continue;
81
+ }
82
+ if (row.type !== "user" && row.type !== "assistant")
83
+ continue;
84
+ if (row.isMeta === true || row.isCompactSummary === true || row.subtype === "subagent")
85
+ continue;
86
+ const message = row.message;
87
+ if (!object(message) || message.role !== row.type)
88
+ continue;
89
+ const role = row.type;
90
+ let phase = null;
91
+ if (role === "assistant") {
92
+ if (message.stop_reason === "tool_use")
93
+ phase = "update";
94
+ else if (message.stop_reason === "end_turn")
95
+ phase = "response";
96
+ }
97
+ let blocks = [];
98
+ if (typeof message.content === "string")
99
+ blocks = [{ type: "text", text: message.content }];
100
+ else if (Array.isArray(message.content))
101
+ blocks = message.content;
102
+ let text = [];
103
+ const flush = () => {
104
+ const markdown = truncateText(text.join("\n\n"), MAX_MESSAGE_CHARS).text;
105
+ text = [];
106
+ if (!markdown.trim())
107
+ return;
108
+ if (role === "user")
109
+ state.firstUser ??= markdown;
110
+ emit({ kind: "message", role, phase, itemType: null, markdown, timestamp });
111
+ };
112
+ for (const block of blocks) {
113
+ if (!object(block)) {
114
+ flush();
115
+ continue;
116
+ }
117
+ if (block.type === "text" && typeof block.text === "string") {
118
+ const content = block.text;
119
+ if (role === "user" &&
120
+ /^\s*(?:<bash-(?:input|stdout|stderr)>|<local-command-|<command-name>|<command-message>|<task-notification>|<subagent|<teammate-message|<system-reminder>|<system-context>|\[Request interrupted by user)/.test(content)) {
121
+ flush();
122
+ continue;
123
+ }
124
+ text.push(content);
125
+ continue;
126
+ }
127
+ flush();
128
+ if (role === "assistant" && block.type === "tool_use" && object(block.input)) {
129
+ const callId = string(block.id);
130
+ if (block.name === "ExitPlanMode" &&
131
+ typeof block.input.plan === "string" &&
132
+ block.input.plan.trim()) {
133
+ emit({
134
+ kind: "message",
135
+ role,
136
+ phase: "response",
137
+ itemType: "plan",
138
+ markdown: block.input.plan,
139
+ timestamp,
140
+ });
141
+ }
142
+ if (block.name === "AskUserQuestion" && callId && Array.isArray(block.input.questions)) {
143
+ const questions = normalizeQuestions(callId, block.input.questions);
144
+ mutableQuestions().set(callId, questions);
145
+ emit({ kind: "user_input", stage: "request", callId, questions, timestamp });
146
+ }
147
+ }
148
+ if (role === "user" &&
149
+ block.type === "tool_result" &&
150
+ typeof block.tool_use_id === "string") {
151
+ const callId = block.tool_use_id;
152
+ const questions = pendingQuestions.get(callId);
153
+ if (!questions)
154
+ continue;
155
+ emit(normalizeQuestionResponse(callId, questions, block.content, row.toolUseResult, timestamp));
156
+ mutableQuestions().delete(callId);
157
+ }
158
+ }
159
+ flush();
160
+ }
161
+ const metadata = mergeBindingMetadata({ projectDirectory: state.project, taskName: state.taskName }, bindings.map((event) => event.attempt));
162
+ return {
163
+ ...state,
164
+ timeline: newItems.length ? [...previous.timeline, ...newItems] : previous.timeline,
165
+ questions: pendingQuestions,
166
+ project: metadata.projectDirectory,
167
+ taskName: metadata.taskName,
168
+ decoderDiagnostics: diagnostics,
169
+ normalizerDiagnostics: state.diagnostics,
170
+ diagnostics: [...diagnostics, ...state.diagnostics].slice(0, MAX_SESSION_DIAGNOSTICS),
171
+ };
172
+ }
173
+ //# sourceMappingURL=session-normalizer.js.map
@@ -0,0 +1,71 @@
1
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2
+ const string = (value) => (typeof value === "string" ? value : null);
3
+ export function normalizeQuestions(callId, values) {
4
+ return values.flatMap((value, index) => {
5
+ if (!object(value) || typeof value.question !== "string")
6
+ return [];
7
+ const options = [];
8
+ if (Array.isArray(value.options)) {
9
+ for (const option of value.options) {
10
+ if (!object(option) || typeof option.label !== "string")
11
+ continue;
12
+ options.push({ label: option.label, description: string(option.description) ?? "" });
13
+ }
14
+ }
15
+ return [
16
+ {
17
+ id: `${callId}:${index}`,
18
+ header: string(value.header) ?? "",
19
+ question: value.question,
20
+ options,
21
+ },
22
+ ];
23
+ });
24
+ }
25
+ export function normalizeQuestionResponse(callId, questions, content, toolUseResult, timestamp) {
26
+ let result = content;
27
+ if (typeof result === "string") {
28
+ try {
29
+ result = JSON.parse(result);
30
+ }
31
+ catch {
32
+ /* Unstructured results are unavailable unless explicitly rejected. */
33
+ }
34
+ }
35
+ let structured = null;
36
+ if (object(toolUseResult))
37
+ structured = toolUseResult;
38
+ else if (object(result))
39
+ structured = result;
40
+ const values = structured && object(structured.answers) ? structured.answers : null;
41
+ const answers = questions.map((q) => {
42
+ const answer = values?.[q.question] ?? values?.[q.id];
43
+ let answerValues = [];
44
+ if (typeof answer === "string")
45
+ answerValues = [answer];
46
+ else if (Array.isArray(answer) && answer.every((v) => typeof v === "string"))
47
+ answerValues = [...answer];
48
+ return { questionId: q.id, answers: answerValues };
49
+ });
50
+ const rejected = structured?.denied === true ||
51
+ structured?.rejected === true ||
52
+ (typeof result === "string" &&
53
+ /^(?:The user (?:doesn't want to proceed|declined|rejected)|User (?:declined|rejected)|Request interrupted by user)/i.test(result));
54
+ const response = {
55
+ kind: "user_input",
56
+ stage: "response",
57
+ callId,
58
+ timestamp,
59
+ };
60
+ if (rejected)
61
+ return { ...response, outcome: "aborted" };
62
+ else if (answers.length > 0 && answers.every((a) => a.answers.length > 0))
63
+ return { ...response, outcome: "answered", answers };
64
+ else
65
+ return {
66
+ ...response,
67
+ outcome: "unavailable",
68
+ summary: "Answer could not be reliably interpreted",
69
+ };
70
+ }
71
+ //# sourceMappingURL=user-input-normalizer.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=catalog-source.js.map
@@ -0,0 +1,250 @@
1
+ import { resolve } from "node:path";
2
+ import { performance } from "node:perf_hooks";
3
+ import { mergeBindingMetadata } from "../shared/binding-protocol.js";
4
+ import { CheckpointedJsonlDecoder, } from "../shared/jsonl-decoder.js";
5
+ import { MAX_SESSION_DIAGNOSTICS } from "../shared/limits.js";
6
+ import { acceptedPathOrder, fingerprintOf, mutableTelemetry, sameFingerprint, } from "../shared/source-cache.js";
7
+ import { IdentityResolver } from "./identity-resolver.js";
8
+ import { codexBindingAttemptsFrom, codexInteraction } from "./interaction-parser.js";
9
+ import { isExpectedIoError } from "./io-errors.js";
10
+ import { JsonlCatalogSource } from "./jsonl-catalog-source.js";
11
+ import { SessionIndexReader } from "./session-index-reader.js";
12
+ import { DefaultSessionNormalizer, } from "./session-normalizer.js";
13
+ export class CodexSessionSource {
14
+ configDirectory;
15
+ descriptor;
16
+ #cache = new Map();
17
+ #duplicates = new Map();
18
+ #snapshot = null;
19
+ #lastRefreshTelemetry = mutableTelemetry();
20
+ decoder;
21
+ identity;
22
+ normalizer;
23
+ sessionIndex;
24
+ constructor(configDirectory, instanceKey, { decoder = new CheckpointedJsonlDecoder(), identity = new IdentityResolver(), normalizer = new DefaultSessionNormalizer(), sessionIndex = new SessionIndexReader(configDirectory), agent = "default", } = {}) {
25
+ this.configDirectory = configDirectory;
26
+ this.decoder = decoder;
27
+ this.identity = identity;
28
+ this.normalizer = normalizer;
29
+ this.sessionIndex = sessionIndex;
30
+ this.descriptor = { sourceType: "codex-jsonl", instanceKey, agent, displayName: "Codex" };
31
+ }
32
+ async refresh() {
33
+ await this.sessionIndex.refresh();
34
+ const archivedDescriptors = new Map([...this.#cache.values()]
35
+ .filter((entry) => entry.descriptor.archived)
36
+ .map((entry) => [entry.descriptor.path, entry.descriptor]));
37
+ const discovery = await new JsonlCatalogSource(this.configDirectory, archivedDescriptors).discover();
38
+ const discoverySignature = JSON.stringify({
39
+ diagnostics: discovery.diagnostics,
40
+ entries: discovery.entries.map(({ descriptor }) => fingerprintOf(descriptor)),
41
+ });
42
+ const nextCache = new Map();
43
+ const nextDuplicates = new Map();
44
+ const binds = new Map();
45
+ const telemetry = mutableTelemetry();
46
+ const unavailableRollouts = [];
47
+ const loadDiagnostics = [];
48
+ const acceptedThreads = new Set();
49
+ const duplicateThreads = new Set();
50
+ // Previously accepted paths take precedence; discovery order breaks first-scan ties.
51
+ const candidates = [...discovery.entries].sort((left, right) => acceptedPathOrder(this.#cache, left.descriptor.sourceRelativePath, right.descriptor.sourceRelativePath));
52
+ for (const { descriptor } of candidates) {
53
+ const fingerprint = fingerprintOf(descriptor);
54
+ const duplicate = this.#duplicates.get(descriptor.sourceRelativePath);
55
+ if (duplicate &&
56
+ sameFingerprint(duplicate.fingerprint, fingerprint) &&
57
+ acceptedThreads.has(duplicate.threadId)) {
58
+ nextDuplicates.set(descriptor.sourceRelativePath, duplicate);
59
+ duplicateThreads.add(duplicate.threadId);
60
+ continue;
61
+ }
62
+ const cached = this.#cache.get(descriptor.sourceRelativePath);
63
+ let loaded;
64
+ if (cached !== undefined && sameFingerprint(cached.fingerprint, fingerprint)) {
65
+ loaded = cached;
66
+ }
67
+ else {
68
+ loaded = await this.#load(descriptor, fingerprint, cached, telemetry, binds);
69
+ }
70
+ if (loaded === null) {
71
+ unavailableRollouts.push(descriptor.sourceRelativePath);
72
+ loadDiagnostics.push(rolloutUnavailableDiagnostic());
73
+ continue;
74
+ }
75
+ if (loaded.threadId !== null) {
76
+ if (acceptedThreads.has(loaded.threadId)) {
77
+ nextDuplicates.set(descriptor.sourceRelativePath, {
78
+ fingerprint,
79
+ threadId: loaded.threadId,
80
+ });
81
+ duplicateThreads.add(loaded.threadId);
82
+ binds.delete(descriptor.id);
83
+ continue;
84
+ }
85
+ acceptedThreads.add(loaded.threadId);
86
+ }
87
+ nextCache.set(descriptor.sourceRelativePath, loaded);
88
+ }
89
+ const sessions = sourceEntries(nextCache);
90
+ const diagnostics = [
91
+ ...discovery.diagnostics.map((item) => ({ ...item })),
92
+ ...loadDiagnostics,
93
+ ...[...duplicateThreads].map(() => ({
94
+ code: "duplicate_native_session_id",
95
+ severity: "warning",
96
+ message: "Multiple Codex rollouts declared the same native session ID; duplicate rollouts were ignored.",
97
+ ordinal: null,
98
+ })),
99
+ ].slice(0, MAX_SESSION_DIAGNOSTICS);
100
+ const bindings = sessions.flatMap((entry) => (binds.get(entry.normalized.session.id) ?? []).map((event) => ({
101
+ ...event,
102
+ localId: entry.localId,
103
+ })));
104
+ const signature = JSON.stringify({
105
+ discoverySignature,
106
+ unavailableRollouts,
107
+ projects: sessions.map((entry) => [
108
+ entry.localId,
109
+ entry.normalized.session.projectDirectory,
110
+ entry.normalized.session.taskName ?? null,
111
+ ]),
112
+ });
113
+ const snapshot = { signature, sessions, diagnostics, bindings };
114
+ this.#cache = nextCache;
115
+ this.#duplicates = nextDuplicates;
116
+ this.#lastRefreshTelemetry = telemetry;
117
+ if (this.#snapshot?.signature === signature)
118
+ return this.#snapshot;
119
+ this.#snapshot = snapshot;
120
+ return snapshot;
121
+ }
122
+ lastRefreshTelemetry() {
123
+ return { ...this.#lastRefreshTelemetry };
124
+ }
125
+ displayTitleFor(session) {
126
+ if (session.origin.agent !== this.descriptor.agent || session.sourceId === null)
127
+ return null;
128
+ return this.sessionIndex.threadName(session.sourceId);
129
+ }
130
+ async #load(descriptor, fingerprint, cached, telemetry, binds) {
131
+ try {
132
+ const decodeStartedAt = performance.now();
133
+ const decoded = await this.decoder.decode(descriptor, cached?.checkpoint);
134
+ telemetry.decodeMs += performance.now() - decodeStartedAt;
135
+ telemetry.probeBytes += decoded.telemetry.probeBytes;
136
+ telemetry.decodeBytes += decoded.telemetry.decodeBytes;
137
+ telemetry[decoded.mode === "append" ? "appendFiles" : "fullFiles"] += 1;
138
+ const normalizeStartedAt = performance.now();
139
+ const identityBase = decoded.mode === "append" && cached !== undefined
140
+ ? cached.identityState
141
+ : this.identity.create(descriptor);
142
+ const normalizerBase = decoded.mode === "append" && cached !== undefined
143
+ ? cached.normalizerState
144
+ : this.normalizer.create(descriptor);
145
+ const identityState = this.identity.append(identityBase, decoded.records);
146
+ const normalizerState = this.normalizer.append(normalizerBase, decoded.records, decoded.diagnostics);
147
+ const bindingEvents = decoded.records.flatMap((record) => {
148
+ const timestamp = typeof record.value.timestamp === "string" ? Date.parse(record.value.timestamp) : NaN;
149
+ return codexBindingAttemptsFrom(record).map((attempt, position) => ({
150
+ time: Number.isFinite(timestamp) ? timestamp : 0,
151
+ ordinal: record.ordinal,
152
+ position,
153
+ attempt,
154
+ }));
155
+ });
156
+ binds.set(descriptor.id, bindingEvents);
157
+ const metadata = this.identity.metadata(identityState);
158
+ const origin = this.#origin(metadata.runtimeVersion);
159
+ const baseNormalized = decoded.mode === "append" &&
160
+ cached !== undefined &&
161
+ identityState === cached.identityState &&
162
+ normalizerState === cached.normalizerState &&
163
+ sameOrigin(origin, cached.origin)
164
+ ? cached.normalized
165
+ : this.normalizer.materialize(normalizerState, metadata, origin);
166
+ const { projectDirectory: bindProjectDirectory, taskName } = mergeBindingMetadata({
167
+ projectDirectory: decoded.mode === "append" ? (cached?.bindProjectDirectory ?? null) : null,
168
+ taskName: decoded.mode === "append" ? (cached?.normalized.session.taskName ?? null) : null,
169
+ }, bindingEvents.map((event) => event.attempt));
170
+ const projectDirectory = bindProjectDirectory ?? baseNormalized.session.workingDirectory;
171
+ const normalized = baseNormalized === cached?.normalized &&
172
+ projectDirectory === cached.normalized.session.projectDirectory &&
173
+ (taskName ?? undefined) === cached.normalized.session.taskName
174
+ ? cached.normalized
175
+ : {
176
+ ...baseNormalized,
177
+ session: {
178
+ ...baseNormalized.session,
179
+ projectDirectory,
180
+ taskName: taskName ?? undefined,
181
+ },
182
+ interaction: codexInteraction(projectDirectory, taskName ?? null),
183
+ };
184
+ telemetry.normalizeMs += performance.now() - normalizeStartedAt;
185
+ return {
186
+ descriptor,
187
+ fingerprint,
188
+ normalized,
189
+ threadId: metadata.threadId,
190
+ parentThreadId: metadata.parentThreadId,
191
+ origin,
192
+ checkpoint: decoded.checkpoint,
193
+ identityState,
194
+ normalizerState,
195
+ bindProjectDirectory,
196
+ };
197
+ }
198
+ catch (error) {
199
+ if (!isExpectedIoError(error))
200
+ throw error;
201
+ return null;
202
+ }
203
+ }
204
+ #origin(runtimeVersion) {
205
+ return {
206
+ sourceType: this.descriptor.sourceType,
207
+ agent: this.descriptor.agent,
208
+ runtimeName: this.descriptor.displayName,
209
+ runtimeVersion,
210
+ };
211
+ }
212
+ }
213
+ export async function createCodexSessionSource(configDirectory, options = {}) {
214
+ const configured = resolve(configDirectory);
215
+ return new CodexSessionSource(configured, `codex-jsonl\0${configured}`, options);
216
+ }
217
+ function sourceEntries(cache) {
218
+ const sessions = [];
219
+ for (const entry of cache.values()) {
220
+ const { threadId } = entry;
221
+ sessions.push({
222
+ localId: sourceLocalId(threadId, entry.fingerprint.sourceRelativePath),
223
+ nativeSessionId: threadId,
224
+ parentNativeSessionId: entry.parentThreadId,
225
+ origin: entry.origin,
226
+ normalized: entry.normalized,
227
+ });
228
+ }
229
+ return sessions;
230
+ }
231
+ function sourceLocalId(threadId, sourceRelativePath) {
232
+ if (threadId === null)
233
+ return `resource:${sourceRelativePath}`;
234
+ return `thread:${threadId}`;
235
+ }
236
+ function rolloutUnavailableDiagnostic() {
237
+ return {
238
+ code: "rollout_unavailable",
239
+ severity: "warning",
240
+ message: "A registered rollout could not be read.",
241
+ ordinal: null,
242
+ };
243
+ }
244
+ function sameOrigin(left, right) {
245
+ return (left.sourceType === right.sourceType &&
246
+ left.agent === right.agent &&
247
+ left.runtimeName === right.runtimeName &&
248
+ left.runtimeVersion === right.runtimeVersion);
249
+ }
250
+ //# sourceMappingURL=codex-session-source.js.map
@@ -0,0 +1,104 @@
1
+ import { basename } from "node:path";
2
+ import { isObject } from "../shared/jsonl-decoder.js";
3
+ import { nonEmptySubagentIdentity, taskNameFromSubagentPath } from "./subagent-identity.js";
4
+ import { nonEmptyString } from "./value-readers.js";
5
+ export class IdentityResolver {
6
+ create(descriptor) {
7
+ return {
8
+ fileName: basename(descriptor.path),
9
+ archived: descriptor.archived,
10
+ firstMetadata: null,
11
+ firstMatchingMetadata: null,
12
+ hasFirstRecord: false,
13
+ firstRecordTimestamp: null,
14
+ lastTimestamp: null,
15
+ };
16
+ }
17
+ append(state, records) {
18
+ if (records.length === 0)
19
+ return state;
20
+ let firstMetadata = state.firstMetadata;
21
+ let firstMatchingMetadata = state.firstMatchingMetadata;
22
+ let hasFirstRecord = state.hasFirstRecord;
23
+ let firstRecordTimestamp = state.firstRecordTimestamp;
24
+ let lastTimestamp = state.lastTimestamp;
25
+ for (const record of records) {
26
+ const timestamp = timestampOf(record.value);
27
+ if (!hasFirstRecord) {
28
+ hasFirstRecord = true;
29
+ firstRecordTimestamp = timestamp;
30
+ }
31
+ if (timestamp !== null)
32
+ lastTimestamp = timestamp;
33
+ if (record.value.type !== "session_meta")
34
+ continue;
35
+ const metadata = rawMetadata(record.value.payload);
36
+ if (metadata === null)
37
+ continue;
38
+ firstMetadata ??= metadata;
39
+ if (firstMatchingMetadata === null &&
40
+ metadata.id !== null &&
41
+ state.fileName.includes(metadata.id))
42
+ firstMatchingMetadata = metadata;
43
+ }
44
+ return {
45
+ ...state,
46
+ firstMetadata,
47
+ firstMatchingMetadata,
48
+ hasFirstRecord,
49
+ firstRecordTimestamp,
50
+ lastTimestamp,
51
+ };
52
+ }
53
+ metadata(state) {
54
+ const raw = state.firstMatchingMetadata ?? state.firstMetadata;
55
+ return {
56
+ threadId: raw?.id ?? null,
57
+ runtimeVersion: raw?.runtimeVersion ?? null,
58
+ title: raw?.title ?? null,
59
+ workingDirectory: raw?.cwd ?? null,
60
+ projectDirectory: raw?.cwd ?? null,
61
+ createdAt: raw?.timestamp ?? state.firstRecordTimestamp,
62
+ updatedAt: state.lastTimestamp,
63
+ parentThreadId: raw?.parentThreadId ?? null,
64
+ archived: state.archived,
65
+ subagent: raw?.subagent ?? null,
66
+ };
67
+ }
68
+ }
69
+ function rawMetadata(value) {
70
+ if (!isObject(value))
71
+ return null;
72
+ const spawn = threadSpawn(value);
73
+ return {
74
+ id: nonEmptyString(value.id),
75
+ runtimeVersion: nonEmptyString(value.cli_version ?? value.agent_version),
76
+ title: nonEmptyString(value.title),
77
+ cwd: nonEmptyString(value.cwd),
78
+ timestamp: nonEmptyString(value.timestamp),
79
+ parentThreadId: nonEmptyString(value.parent_thread_id) ??
80
+ nonEmptyString(value.parent_id) ??
81
+ nonEmptyString(spawn?.parent_thread_id),
82
+ subagent: rawSubagent(value, spawn),
83
+ };
84
+ }
85
+ function threadSpawn(value) {
86
+ const source = isObject(value.source) ? value.source : null;
87
+ const subagent = source?.subagent;
88
+ return isObject(subagent) && isObject(subagent.thread_spawn) ? subagent.thread_spawn : null;
89
+ }
90
+ function rawSubagent(value, spawn) {
91
+ const source = isObject(value.source) ? value.source : null;
92
+ const subagent = source?.subagent;
93
+ return nonEmptySubagentIdentity({
94
+ taskName: taskNameFromSubagentPath(nonEmptyString(value.agent_path) ?? nonEmptyString(spawn?.agent_path)),
95
+ nickname: nonEmptyString(value.agent_nickname) ?? nonEmptyString(spawn?.agent_nickname),
96
+ role: nonEmptyString(value.agent_role) ??
97
+ nonEmptyString(spawn?.agent_role) ??
98
+ (typeof subagent === "string" ? subagent : null),
99
+ });
100
+ }
101
+ function timestampOf(record) {
102
+ return record === undefined ? null : nonEmptyString(record.timestamp);
103
+ }
104
+ //# sourceMappingURL=identity-resolver.js.map
@@ -0,0 +1,70 @@
1
+ import { TASK_NAME_ERROR, validTaskName } from "../../../shared/task-name.js";
2
+ import { validProjectDirectory } from "../../domain/project-directory.js";
3
+ import { bindingAttempts } from "../shared/binding-protocol.js";
4
+ import { isObject } from "../shared/jsonl-decoder.js";
5
+ import { DEFAULT_KEY_BINDINGS, interruptSteps } from "../shared/session-management.js";
6
+ const BINDING_MARKER = "AHUB_TMUX_BIND_V3";
7
+ export function codexInteraction(projectDirectory, taskName) {
8
+ return {
9
+ transport: "tmux",
10
+ bindingCommand: codexBindingCommand(undefined, projectDirectory, taskName),
11
+ keyBindings: DEFAULT_KEY_BINDINGS,
12
+ interruptSteps,
13
+ };
14
+ }
15
+ export function codexBindingAttemptsFrom(record) {
16
+ if (record.value.type !== "response_item")
17
+ return [];
18
+ const metadata = record.value.metadata;
19
+ if (isObject(metadata) && metadata.inherited_user_message === true)
20
+ return [];
21
+ const payload = record.value.payload;
22
+ if (!isObject(payload) || payload.type !== "message" || payload.role !== "user") {
23
+ return [];
24
+ }
25
+ const text = contentText(payload.content);
26
+ if (text === null || !/^\s*<user_shell_command>/.test(text))
27
+ return [];
28
+ const shellBlocks = [...text.matchAll(/<user_shell_command>([\s\S]*?)<\/user_shell_command>/g)];
29
+ const found = [];
30
+ for (const block of shellBlocks) {
31
+ const lines = (block[1] ?? "").split("\n").map((line) => line.replace(/\r$/, ""));
32
+ found.push(...bindingAttempts(lines, record.ordinal, validOperationId));
33
+ }
34
+ const lastOpen = text.lastIndexOf("<user_shell_command>");
35
+ const lastClose = text.lastIndexOf("</user_shell_command>");
36
+ if (lastOpen > lastClose) {
37
+ const incomplete = text.slice(lastOpen + "<user_shell_command>".length);
38
+ const incompleteLines = incomplete.replace(/\r\n?/g, "\n").split("\n");
39
+ for (const line of incompleteLines) {
40
+ if (line === BINDING_MARKER)
41
+ found.push({ ordinal: record.ordinal, valid: false });
42
+ }
43
+ }
44
+ return found;
45
+ }
46
+ function contentText(value) {
47
+ if (!Array.isArray(value))
48
+ return null;
49
+ const parts = [];
50
+ for (const part of value) {
51
+ if (!isObject(part) || typeof part.text !== "string")
52
+ continue;
53
+ if (part.type === "input_text" || part.type === "text")
54
+ parts.push(part.text);
55
+ }
56
+ return parts.length === 0 ? null : parts.join("\n\n");
57
+ }
58
+ export function codexBindingCommand(operationId, projectDirectory, taskName) {
59
+ if (taskName !== undefined && taskName !== null && !validTaskName(taskName))
60
+ throw new Error(TASK_NAME_ERROR);
61
+ if (operationId !== undefined && !validOperationId(operationId))
62
+ throw new Error("Invalid operation ID");
63
+ const quote = (value) => "'" + value.replaceAll("'", "'\"'\"'") + "'";
64
+ const project = validProjectDirectory(projectDirectory) ? projectDirectory : "";
65
+ return `! printf '${BINDING_MARKER}\\n%s\\n%s\\n%s\\n%s\\n%s\\n' "\${TMUX%%,*}" "$AHUB_TMUX_SESSION_NAME" ${quote(operationId ?? "")} ${quote(project)} ${quote(JSON.stringify(taskName ?? null))}`;
66
+ }
67
+ function validOperationId(id) {
68
+ return /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(id);
69
+ }
70
+ //# sourceMappingURL=interaction-parser.js.map
@@ -0,0 +1,2 @@
1
+ export { isExpectedIoError } from "../shared/io-errors.js";
2
+ //# sourceMappingURL=io-errors.js.map