@kingcrab/pi-imessage 0.0.42 → 1.0.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 (183) hide show
  1. package/README.md +101 -108
  2. package/dist/agent/behavior.js +79 -0
  3. package/dist/agent/chats.js +67 -0
  4. package/dist/agent/commands.js +163 -0
  5. package/dist/agent/compaction.js +51 -0
  6. package/dist/agent/deliver.js +139 -0
  7. package/dist/agent/direct-send.js +68 -0
  8. package/dist/agent/harness.js +79 -0
  9. package/dist/agent/health.js +32 -0
  10. package/dist/agent/models.js +69 -0
  11. package/dist/agent/progress-format.js +87 -0
  12. package/dist/agent/progress.js +135 -0
  13. package/dist/agent/prompt.js +79 -0
  14. package/dist/agent/replies.js +9 -0
  15. package/dist/agent/run.js +159 -0
  16. package/dist/agent/scheduling.js +290 -0
  17. package/dist/agent/time.js +18 -0
  18. package/dist/cli.js +88 -191
  19. package/dist/config/settings.js +60 -0
  20. package/dist/extensions/browser.js +326 -0
  21. package/dist/extensions/final-text.js +23 -0
  22. package/dist/extensions/memory.js +109 -0
  23. package/dist/extensions/message.js +33 -0
  24. package/dist/extensions/read-image.js +72 -0
  25. package/dist/extensions/schedules.js +41 -0
  26. package/dist/extensions/subagent.js +54 -0
  27. package/dist/extensions/web.js +99 -0
  28. package/dist/extensions/workspace.js +98 -0
  29. package/dist/main.js +571 -74
  30. package/dist/transport/attachments.js +37 -0
  31. package/dist/transport/http.js +110 -0
  32. package/dist/{rich-text.js → transport/rich-text.js} +25 -13
  33. package/dist/{send.js → transport/send.js} +13 -13
  34. package/dist/transport/watch.js +124 -0
  35. package/dist/web/app.js +748 -0
  36. package/dist/web/history.js +87 -0
  37. package/dist/web/memory.js +81 -0
  38. package/dist/web/page.js +28 -0
  39. package/dist/web/schedules.js +74 -0
  40. package/dist/web/server.js +348 -0
  41. package/dist/web/style.css +543 -0
  42. package/dist/web/tasks.js +63 -0
  43. package/docs/adr/0001-durable-owns-state.md +11 -0
  44. package/docs/adr/0002-module-layering.md +11 -0
  45. package/docs/adr/0003-replies-at-most-once.md +11 -0
  46. package/docs/adr/0004-native-extensions.md +11 -0
  47. package/docs/adr/0005-history-migration.md +11 -0
  48. package/docs/adr/0006-jsonl-storage.md +11 -0
  49. package/docs/adr/0007-images-by-path.md +11 -0
  50. package/docs/adr/0008-compaction-and-run.md +11 -0
  51. package/docs/adr/0009-one-shot-cutover.md +11 -0
  52. package/docs/adr/0010-test-budget.md +11 -0
  53. package/docs/adr/0011-reconcile-unsent-answers.md +24 -0
  54. package/docs/adr/0012-run-in-owned-conversation.md +22 -0
  55. package/docs/adr/0013-thin-adapters-around-durable.md +18 -0
  56. package/docs/adr/0014-ephemeral-private-storage.md +13 -0
  57. package/docs/adr/0015-ordinary-stop-keeps-native-inbox.md +21 -0
  58. package/docs/adr/0016-process-http-transport.md +21 -0
  59. package/docs/adr/0017-browser-cli-owned-scope.md +22 -0
  60. package/docs/adr/0018-tasks-without-prompt-api.md +19 -0
  61. package/docs/adr/0019-remove-scheduled-services.md +41 -0
  62. package/docs/adr/0020-chat-task-progress.md +41 -0
  63. package/docs/adr/0021-readable-progress-without-link-previews.md +33 -0
  64. package/docs/adr/0022-progress-admission-and-settlement.md +35 -0
  65. package/docs/adr/0023-retain-progress-type-prefixes.md +18 -0
  66. package/docs/adr/0024-status-marker-before-type-prefix.md +13 -0
  67. package/docs/adr/0025-reusable-progress-and-command-preview.md +49 -0
  68. package/docs/adr/0026-inline-command-only-requests.md +16 -0
  69. package/docs/adr/0027-command-preview-width.md +10 -0
  70. package/docs/adr/0028-command-preview-120.md +8 -0
  71. package/docs/adr/0030-shared-web-read-cache.md +33 -0
  72. package/docs/adr/0031-quiet-compaction-progress.md +32 -0
  73. package/docs/adr/0032-native-background-schedules.md +58 -0
  74. package/docs/adr/0033-operator-scheduled-run.md +23 -0
  75. package/docs/adr/0034-machine-local-schedule-time.md +36 -0
  76. package/docs/adr/0035-external-business-task-extensions.md +56 -0
  77. package/docs/adr/0036-workspace-policy-and-neutral-service-id.md +37 -0
  78. package/docs/adr/0037-restart-during-in-flight-work.md +30 -0
  79. package/docs/adr/0038-restart-any-time.md +25 -0
  80. package/docs/adr/0039-rerun-interrupted-tools.md +24 -0
  81. package/docs/adr/0040-chat-progress-notices.md +44 -0
  82. package/docs/adr/0041-restart-and-recovery.md +28 -0
  83. package/docs/adr/0042-background-schedules-and-workspace-extensions.md +44 -0
  84. package/docs/adr/0043-remove-legacy-migration.md +23 -0
  85. package/docs/adr/0044-message-channels.md +32 -0
  86. package/docs/adr/0045-chat-scheduling-tools.md +51 -0
  87. package/docs/adr/0046-selective-command-redaction.md +31 -0
  88. package/docs/adr/0047-one-scheduling-path.md +158 -0
  89. package/docs/adr/README.md +50 -0
  90. package/docs/api.md +92 -0
  91. package/docs/architecture.md +164 -0
  92. package/docs/browser.md +93 -0
  93. package/docs/memory.md +27 -0
  94. package/docs/screenshot.png +0 -0
  95. package/docs/workspace-extensions.md +133 -0
  96. package/examples/workspace-extensions/system-context/index.ts +69 -0
  97. package/examples/workspace-extensions/system-context/package.json +3 -0
  98. package/ops/README.md +267 -0
  99. package/ops/preflight.mjs +28 -0
  100. package/package.json +33 -45
  101. package/dist/agent.d.ts +0 -60
  102. package/dist/agent.d.ts.map +0 -1
  103. package/dist/agent.js +0 -715
  104. package/dist/agent.js.map +0 -1
  105. package/dist/cli.d.ts +0 -15
  106. package/dist/cli.d.ts.map +0 -1
  107. package/dist/cli.js.map +0 -1
  108. package/dist/imessage.d.ts +0 -33
  109. package/dist/imessage.d.ts.map +0 -1
  110. package/dist/imessage.js +0 -76
  111. package/dist/imessage.js.map +0 -1
  112. package/dist/logger.d.ts +0 -33
  113. package/dist/logger.d.ts.map +0 -1
  114. package/dist/logger.js +0 -104
  115. package/dist/logger.js.map +0 -1
  116. package/dist/main.d.ts +0 -5
  117. package/dist/main.d.ts.map +0 -1
  118. package/dist/main.js.map +0 -1
  119. package/dist/memory.d.ts +0 -53
  120. package/dist/memory.d.ts.map +0 -1
  121. package/dist/memory.js +0 -196
  122. package/dist/memory.js.map +0 -1
  123. package/dist/model-health.d.ts +0 -11
  124. package/dist/model-health.d.ts.map +0 -1
  125. package/dist/model-health.js +0 -67
  126. package/dist/model-health.js.map +0 -1
  127. package/dist/pipeline.d.ts +0 -27
  128. package/dist/pipeline.d.ts.map +0 -1
  129. package/dist/pipeline.js +0 -59
  130. package/dist/pipeline.js.map +0 -1
  131. package/dist/queue.d.ts +0 -23
  132. package/dist/queue.d.ts.map +0 -1
  133. package/dist/queue.js +0 -89
  134. package/dist/queue.js.map +0 -1
  135. package/dist/rich-text.d.ts +0 -26
  136. package/dist/rich-text.d.ts.map +0 -1
  137. package/dist/rich-text.js.map +0 -1
  138. package/dist/self-echo.d.ts +0 -19
  139. package/dist/self-echo.d.ts.map +0 -1
  140. package/dist/self-echo.js +0 -54
  141. package/dist/self-echo.js.map +0 -1
  142. package/dist/send.d.ts +0 -24
  143. package/dist/send.d.ts.map +0 -1
  144. package/dist/send.js.map +0 -1
  145. package/dist/settings.d.ts +0 -44
  146. package/dist/settings.d.ts.map +0 -1
  147. package/dist/settings.js +0 -80
  148. package/dist/settings.js.map +0 -1
  149. package/dist/store.d.ts +0 -57
  150. package/dist/store.d.ts.map +0 -1
  151. package/dist/store.js +0 -124
  152. package/dist/store.js.map +0 -1
  153. package/dist/tasks.d.ts +0 -95
  154. package/dist/tasks.d.ts.map +0 -1
  155. package/dist/tasks.js +0 -384
  156. package/dist/tasks.js.map +0 -1
  157. package/dist/types.d.ts +0 -93
  158. package/dist/types.d.ts.map +0 -1
  159. package/dist/types.js +0 -38
  160. package/dist/types.js.map +0 -1
  161. package/dist/watch.d.ts +0 -21
  162. package/dist/watch.d.ts.map +0 -1
  163. package/dist/watch.js +0 -219
  164. package/dist/watch.js.map +0 -1
  165. package/dist/web/data.d.ts +0 -11
  166. package/dist/web/data.d.ts.map +0 -1
  167. package/dist/web/data.js +0 -56
  168. package/dist/web/data.js.map +0 -1
  169. package/dist/web/html.d.ts +0 -5
  170. package/dist/web/html.d.ts.map +0 -1
  171. package/dist/web/html.js +0 -16
  172. package/dist/web/html.js.map +0 -1
  173. package/dist/web/index.d.ts +0 -23
  174. package/dist/web/index.d.ts.map +0 -1
  175. package/dist/web/index.js +0 -279
  176. package/dist/web/index.js.map +0 -1
  177. package/dist/web/render.d.ts +0 -11
  178. package/dist/web/render.d.ts.map +0 -1
  179. package/dist/web/render.js +0 -56
  180. package/dist/web/render.js.map +0 -1
  181. package/dist/web/templates/logs.eta +0 -75
  182. package/dist/web/templates/memory.eta +0 -91
  183. package/dist/web/templates/page.eta +0 -98
package/dist/watch.js DELETED
@@ -1,219 +0,0 @@
1
- /**
2
- * Watch for new iMessages by polling ~/Library/Messages/chat.db.
3
- *
4
- * ┌──────────┐ poll ┌──────────┐ push ┌───────┐
5
- * │ chat.db │ ──────> │ watch.ts │ ──────> │ queue │
6
- * └──────────┘ └──────────┘ └───────┘
7
- *
8
- * Replaces BlueBubbles webhook monitor — no server, no external dependency.
9
- * Requires Full Disk Access for the terminal / Node process.
10
- */
11
- import { homedir } from "node:os";
12
- import { join } from "node:path";
13
- import Database from "better-sqlite3";
14
- // ── Config ────────────────────────────────────────────────────────────────────
15
- const DEFAULT_POLL_INTERVAL_MS = 2_000;
16
- /** macOS Core Data epoch: 2001-01-01T00:00:00Z in milliseconds. */
17
- const MAC_EPOCH_MS = new Date("2001-01-01T00:00:00Z").getTime();
18
- // ── SQL ───────────────────────────────────────────────────────────────────────
19
- const MESSAGES_QUERY = `
20
- SELECT
21
- message.ROWID AS rowid,
22
- message.text AS text,
23
- message.attributedBody AS attributedBody,
24
- message.is_from_me AS is_from_me,
25
- message.service AS service,
26
- message.associated_message_type AS reaction_type,
27
- message.thread_originator_guid AS thread_originator_guid,
28
- handle.id AS sender,
29
- chat.guid AS chat_guid,
30
- chat.display_name AS group_name,
31
- (SELECT COUNT(*) FROM chat_handle_join WHERE chat_handle_join.chat_id = chat.ROWID) > 1 AS is_group
32
- FROM message
33
- LEFT JOIN handle ON message.handle_id = handle.ROWID
34
- LEFT JOIN chat_message_join ON message.ROWID = chat_message_join.message_id
35
- LEFT JOIN chat ON chat_message_join.chat_id = chat.ROWID
36
- WHERE message.date > ?
37
- AND message.is_from_me = 0
38
- AND (message.associated_message_type IS NULL OR message.associated_message_type = 0)
39
- ORDER BY message.date ASC
40
- `;
41
- const REPLY_TO_QUERY = `
42
- SELECT
43
- message.text AS text,
44
- message.attributedBody AS attributedBody
45
- FROM message
46
- WHERE message.guid = ?
47
- LIMIT 1
48
- `;
49
- const ATTACHMENTS_QUERY = `
50
- SELECT
51
- attachment.filename AS filename,
52
- attachment.mime_type AS mime_type
53
- FROM attachment
54
- INNER JOIN message_attachment_join ON attachment.ROWID = message_attachment_join.attachment_id
55
- WHERE message_attachment_join.message_id = ?
56
- `;
57
- export function createWatcher(config) {
58
- const { queue, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS } = config;
59
- const dbPath = join(homedir(), "Library", "Messages", "chat.db");
60
- let db;
61
- let intervalId = null;
62
- let lastTimestamp = (Date.now() - MAC_EPOCH_MS) * 1_000_000; // now, in macOS nanoseconds
63
- const seenRowIds = new Set();
64
- function deriveMessageType(service, isGroup) {
65
- if (service?.toLowerCase().includes("sms"))
66
- return "sms";
67
- return isGroup ? "group" : "imessage";
68
- }
69
- /**
70
- * Extract plain text from an attributedBody blob (NSKeyedArchiver typedstream).
71
- *
72
- * Binary layout after "NSString" class declaration:
73
- * \x01\x94\x84\x01\x2b — fixed header ('+' type marker)
74
- * length — 1 byte if < 0x81, otherwise \x81 + 2-byte big-endian
75
- * UTF-8 text bytes
76
- * \x86 — terminator
77
- *
78
- * Returns null if extraction fails — caller should treat as no text.
79
- */
80
- function extractTextFromAttributedBody(blob) {
81
- try {
82
- const marker = Buffer.from("NSString");
83
- const markerIdx = blob.indexOf(marker);
84
- if (markerIdx === -1)
85
- return null;
86
- // Skip marker + fixed header bytes, find the '+' (0x2b) type indicator
87
- let pos = markerIdx + marker.length;
88
- while (pos < blob.length && blob[pos] !== 0x2b)
89
- pos++;
90
- if (pos >= blob.length)
91
- return null;
92
- pos++; // skip 0x2b
93
- // Read length
94
- let textLength;
95
- if (blob[pos] === 0x81) {
96
- // 2-byte little-endian length follows
97
- pos++;
98
- if (pos + 2 > blob.length)
99
- return null;
100
- textLength = blob[pos] | (blob[pos + 1] << 8);
101
- pos += 2;
102
- }
103
- else {
104
- textLength = blob[pos];
105
- pos++;
106
- }
107
- if (textLength <= 0 || pos + textLength > blob.length)
108
- return null;
109
- const text = blob
110
- .subarray(pos, pos + textLength)
111
- .toString("utf-8")
112
- .trim();
113
- return text || null;
114
- }
115
- catch {
116
- return null;
117
- }
118
- }
119
- /** Resolve message text: prefer message.text, fall back to attributedBody. */
120
- function resolveText(row) {
121
- const text = row.text?.trim();
122
- if (text)
123
- return text;
124
- if (row.attributedBody)
125
- return extractTextFromAttributedBody(row.attributedBody);
126
- return null;
127
- }
128
- function expandPath(rawPath) {
129
- if (rawPath.startsWith("~"))
130
- return rawPath.replace(/^~/, homedir());
131
- return rawPath;
132
- }
133
- function getAttachments(rowid) {
134
- const rows = db.prepare(ATTACHMENTS_QUERY).all(rowid);
135
- return rows
136
- .filter((r) => r.filename)
137
- .map((r) => ({
138
- path: expandPath(r.filename ?? ""),
139
- mimeType: r.mime_type ?? null,
140
- }));
141
- }
142
- /** Look up the text of a replied-to message by its guid. Returns null on miss. */
143
- function getReplyToText(guid) {
144
- try {
145
- const row = db.prepare(REPLY_TO_QUERY).get(guid);
146
- return row ? resolveText(row) : null;
147
- }
148
- catch {
149
- return null;
150
- }
151
- }
152
- function poll() {
153
- try {
154
- const rows = db.prepare(MESSAGES_QUERY).all(lastTimestamp);
155
- for (const row of rows) {
156
- if (seenRowIds.has(row.rowid))
157
- continue;
158
- if (!row.chat_guid)
159
- continue;
160
- const resolvedText = resolveText(row);
161
- const hasText = Boolean(resolvedText);
162
- const attachments = getAttachments(row.rowid);
163
- if (!hasText && attachments.length === 0)
164
- continue;
165
- seenRowIds.add(row.rowid);
166
- const replyToText = row.thread_originator_guid ? getReplyToText(row.thread_originator_guid) : null;
167
- const msg = {
168
- chatGuid: row.chat_guid,
169
- text: resolvedText,
170
- sender: row.sender ?? "unknown",
171
- messageType: deriveMessageType(row.service, row.is_group),
172
- groupName: row.group_name ?? "",
173
- replyToText,
174
- attachments,
175
- images: [],
176
- };
177
- queue.push(msg);
178
- }
179
- // Advance timestamp to latest seen row
180
- if (rows.length > 0) {
181
- // Re-query max date to advance cursor
182
- const maxDate = db
183
- .prepare(`SELECT MAX(date) AS max_date FROM message WHERE ROWID IN (${rows.map(() => "?").join(",")})`)
184
- .get(...rows.map((r) => r.rowid));
185
- if (maxDate?.max_date) {
186
- lastTimestamp = maxDate.max_date;
187
- }
188
- }
189
- // Prune seenRowIds — keep last 10000
190
- if (seenRowIds.size > 10_000) {
191
- const entries = [...seenRowIds];
192
- for (let i = 0; i < entries.length - 5_000; i++) {
193
- seenRowIds.delete(entries[i]);
194
- }
195
- }
196
- }
197
- catch (error) {
198
- console.error("[watch] poll error:", error);
199
- }
200
- }
201
- return {
202
- start() {
203
- db = new Database(dbPath, { readonly: true });
204
- console.log(`[watch] polling chat.db every ${pollIntervalMs}ms`);
205
- poll(); // initial poll to set cursor (catches nothing on first run)
206
- intervalId = setInterval(poll, pollIntervalMs);
207
- },
208
- stop() {
209
- if (intervalId) {
210
- clearInterval(intervalId);
211
- intervalId = null;
212
- }
213
- db?.close();
214
- queue.close();
215
- console.log("[watch] stopped");
216
- },
217
- };
218
- }
219
- //# sourceMappingURL=watch.js.map
package/dist/watch.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"watch.js","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"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"}
@@ -1,11 +0,0 @@
1
- /** Read chat logs from disk and produce ChatBlock summaries. */
2
- import type { Message } from "../store.js";
3
- export interface ChatBlock {
4
- guid: string;
5
- displayName: string;
6
- messages: Message[];
7
- lastTime: number;
8
- }
9
- /** Return chat blocks from the last 7 days, sorted by most-recent message. */
10
- export declare function getChatBlocks(workingDir: string): ChatBlock[];
11
- //# sourceMappingURL=data.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/web/data.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAIhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAmBD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,CAiC7D"}
package/dist/web/data.js DELETED
@@ -1,56 +0,0 @@
1
- /** Read chat logs from disk and produce ChatBlock summaries. */
2
- import { existsSync, readFileSync, readdirSync } from "node:fs";
3
- import { join } from "node:path";
4
- const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
5
- function readMessages(workingDir, chatGuid) {
6
- const logFile = join(workingDir, chatGuid, "log.jsonl");
7
- if (!existsSync(logFile))
8
- return [];
9
- const lines = readFileSync(logFile, "utf-8").trim().split("\n").filter(Boolean);
10
- const messages = [];
11
- for (const line of lines) {
12
- try {
13
- messages.push(JSON.parse(line));
14
- }
15
- catch {
16
- // skip malformed lines
17
- }
18
- }
19
- return messages;
20
- }
21
- /** Return chat blocks from the last 7 days, sorted by most-recent message. */
22
- export function getChatBlocks(workingDir) {
23
- if (!existsSync(workingDir))
24
- return [];
25
- const cutoff = Date.now() - SEVEN_DAYS_MS;
26
- const blocks = [];
27
- let entries;
28
- try {
29
- entries = readdirSync(workingDir, { withFileTypes: true })
30
- .filter((entry) => entry.isDirectory())
31
- .map((entry) => entry.name);
32
- }
33
- catch {
34
- return [];
35
- }
36
- for (const guid of entries) {
37
- const messages = readMessages(workingDir, guid);
38
- if (messages.length === 0)
39
- continue;
40
- const lastMessage = messages[messages.length - 1];
41
- if (!lastMessage)
42
- continue;
43
- const lastTime = new Date(lastMessage.date).getTime();
44
- if (lastTime < cutoff)
45
- continue;
46
- const parts = guid.split(";");
47
- let displayName = parts[parts.length - 1] ?? guid;
48
- if (lastMessage.messageType === "group" && lastMessage.groupName) {
49
- displayName = lastMessage.groupName;
50
- }
51
- blocks.push({ guid, displayName, messages, lastTime });
52
- }
53
- blocks.sort((a, b) => b.lastTime - a.lastTime);
54
- return blocks;
55
- }
56
- //# sourceMappingURL=data.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/web/data.ts"],"names":[],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- /** HTML utility functions. */
2
- /** Format as "[YYYY-MM-DD HH:MM:SS]" in local time. */
3
- export declare function formatTime(iso: string): string;
4
- export declare function anchorId(guid: string): string;
5
- //# sourceMappingURL=html.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,uDAAuD;AACvD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS9C;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C"}
package/dist/web/html.js DELETED
@@ -1,16 +0,0 @@
1
- /** HTML utility functions. */
2
- /** Format as "[YYYY-MM-DD HH:MM:SS]" in local time. */
3
- export function formatTime(iso) {
4
- const date = new Date(iso);
5
- const year = date.getFullYear();
6
- const month = String(date.getMonth() + 1).padStart(2, "0");
7
- const day = String(date.getDate()).padStart(2, "0");
8
- const hour = String(date.getHours()).padStart(2, "0");
9
- const minute = String(date.getMinutes()).padStart(2, "0");
10
- const second = String(date.getSeconds()).padStart(2, "0");
11
- return `[${year}-${month}-${day} ${hour}:${minute}:${second}]`;
12
- }
13
- export function anchorId(guid) {
14
- return guid.replace(/[^a-zA-Z0-9]/g, "_");
15
- }
16
- //# sourceMappingURL=html.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,uDAAuD;AACvD,MAAM,UAAU,UAAU,CAAC,GAAW;IACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,OAAO,IAAI,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC"}
@@ -1,23 +0,0 @@
1
- /** Web server: serves the chat log UI, logs page, and API endpoints. */
2
- import type { AgentManager } from "../agent.js";
3
- import type { ModelHealthChecker } from "../model-health.js";
4
- import type { SelfEchoFilter } from "../self-echo.js";
5
- import type { MessageSender } from "../send.js";
6
- import type { Settings } from "../settings.js";
7
- export interface WebServerConfig {
8
- workingDir: string;
9
- host: string;
10
- port: number;
11
- getSettings: () => Settings;
12
- setSettings: (settings: Settings) => void;
13
- sender: MessageSender;
14
- echoFilter: SelfEchoFilter;
15
- agent: AgentManager;
16
- checkModelHealth: ModelHealthChecker;
17
- }
18
- export interface WebServer {
19
- start(): void;
20
- stop(): Promise<void>;
21
- }
22
- export declare function createWebServer(config: WebServerConfig): WebServer;
23
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAKxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK/C,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,QAAQ,CAAC;IAC5B,WAAW,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,cAAc,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,gBAAgB,EAAE,kBAAkB,CAAC;CACrC;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAgDD,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA4OlE"}
package/dist/web/index.js DELETED
@@ -1,279 +0,0 @@
1
- /** Web server: serves the chat log UI, logs page, and API endpoints. */
2
- import { existsSync, readFileSync, readdirSync, watch } from "node:fs";
3
- import { createServer } from "node:http";
4
- import { join } from "node:path";
5
- import { getChatBlocks } from "./data.js";
6
- import { renderLogsPage, renderMemoryPage, renderPage } from "./render.js";
7
- /** Read the tail of a log file. */
8
- function readLogTail(path, maxLines) {
9
- if (!existsSync(path))
10
- return "";
11
- try {
12
- const content = readFileSync(path, "utf-8");
13
- const lines = content.split("\n");
14
- return lines.slice(-maxLines).join("\n");
15
- }
16
- catch {
17
- return "";
18
- }
19
- }
20
- /** Parse JSON body from request. */
21
- function parseJsonBody(request) {
22
- return new Promise((resolve, reject) => {
23
- const chunks = [];
24
- request.on("data", (chunk) => chunks.push(chunk));
25
- request.on("end", () => {
26
- try {
27
- resolve(JSON.parse(Buffer.concat(chunks).toString()));
28
- }
29
- catch (error) {
30
- reject(error);
31
- }
32
- });
33
- request.on("error", reject);
34
- });
35
- }
36
- /** Read global and per-chat MEMORY.md files. */
37
- function readMemories(workingDir) {
38
- const globalMemoryPath = join(workingDir, "MEMORY.md");
39
- const globalMemory = existsSync(globalMemoryPath) ? readFileSync(globalMemoryPath, "utf-8").trim() : "";
40
- const chatMemories = [];
41
- if (existsSync(workingDir)) {
42
- for (const entry of readdirSync(workingDir, { withFileTypes: true })) {
43
- if (!entry.isDirectory())
44
- continue;
45
- const memPath = join(workingDir, entry.name, "MEMORY.md");
46
- if (existsSync(memPath)) {
47
- const content = readFileSync(memPath, "utf-8").trim();
48
- if (content)
49
- chatMemories.push({ name: entry.name, content });
50
- }
51
- }
52
- }
53
- return { globalMemory, chatMemories };
54
- }
55
- export function createWebServer(config) {
56
- const { workingDir, host, port, getSettings, setSettings, sender, echoFilter, agent, checkModelHealth } = config;
57
- const sseClients = new Set();
58
- let fsWatcher = null;
59
- let debounceTimer = null;
60
- function broadcast() {
61
- if (debounceTimer)
62
- return;
63
- debounceTimer = setTimeout(() => {
64
- debounceTimer = null;
65
- for (const client of sseClients) {
66
- client.write("event: update\ndata: {}\n\n");
67
- }
68
- }, 300);
69
- }
70
- function startWatcher() {
71
- if (!existsSync(workingDir))
72
- return;
73
- try {
74
- fsWatcher = watch(workingDir, { recursive: true }, (_event, filename) => {
75
- if (filename?.endsWith("log.jsonl") || filename?.endsWith(".log") || filename?.endsWith("MEMORY.md"))
76
- broadcast();
77
- });
78
- fsWatcher.on("error", () => { });
79
- }
80
- catch {
81
- // workingDir may not exist yet
82
- }
83
- }
84
- /** Toggle reply for a chatGuid by updating whitelist/blacklist. */
85
- function toggleChatReply(chatGuid, enabled) {
86
- const { chatAllowlist, ...rest } = getSettings();
87
- const whitelist = chatAllowlist.whitelist.filter((id) => id !== chatGuid);
88
- const blacklist = chatAllowlist.blacklist.filter((id) => id !== chatGuid);
89
- if (enabled) {
90
- whitelist.push(chatGuid);
91
- }
92
- else {
93
- blacklist.push(chatGuid);
94
- }
95
- setSettings({ ...rest, chatAllowlist: { whitelist, blacklist } });
96
- }
97
- function jsonResponse(response, status, data) {
98
- response.writeHead(status, { "Content-Type": "application/json" });
99
- response.end(JSON.stringify(data));
100
- }
101
- async function handleRequest(request, response) {
102
- const url = new URL(request.url ?? "/", `http://localhost:${port}`);
103
- // SSE
104
- if (url.pathname === "/events") {
105
- response.writeHead(200, {
106
- "Content-Type": "text/event-stream",
107
- "Cache-Control": "no-cache",
108
- Connection: "keep-alive",
109
- });
110
- response.write("retry: 5000\n\n");
111
- sseClients.add(response);
112
- request.on("close", () => sseClients.delete(response));
113
- return;
114
- }
115
- // Memory page
116
- if (url.pathname === "/memory" && request.method === "GET") {
117
- const { globalMemory, chatMemories } = readMemories(workingDir);
118
- const html = renderMemoryPage(globalMemory, chatMemories);
119
- response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
120
- response.end(html);
121
- return;
122
- }
123
- // Memory data API (JSON)
124
- if (url.pathname === "/memory/data" && request.method === "GET") {
125
- jsonResponse(response, 200, readMemories(workingDir));
126
- return;
127
- }
128
- // Logs page
129
- if (url.pathname === "/logs" && request.method === "GET") {
130
- const appLog = readLogTail(join(workingDir, "app.log"), 200);
131
- const digestLog = readLogTail(join(workingDir, "digest.log"), 200);
132
- const html = renderLogsPage(appLog, digestLog);
133
- response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
134
- response.end(html);
135
- return;
136
- }
137
- // Logs data API (JSON)
138
- if (url.pathname === "/logs/data" && request.method === "GET") {
139
- const appLog = readLogTail(join(workingDir, "app.log"), 200);
140
- const digestLog = readLogTail(join(workingDir, "digest.log"), 200);
141
- jsonResponse(response, 200, { appLog, digestLog });
142
- return;
143
- }
144
- // GET /health/model — make a live request to the configured default AI model
145
- if (request.method === "GET" && url.pathname === "/health/model") {
146
- const result = await checkModelHealth();
147
- jsonResponse(response, result.ok ? 200 : 503, result);
148
- return;
149
- }
150
- // POST /send — send a message and/or local file attachment directly to a chat
151
- if (request.method === "POST" && url.pathname === "/send") {
152
- try {
153
- const body = await parseJsonBody(request);
154
- const chatGuid = body.chatGuid;
155
- const text = body.text;
156
- const filePath = (body.filePath ?? body.attachmentPath);
157
- if (!chatGuid || (!text && !filePath)) {
158
- jsonResponse(response, 400, { error: "chatGuid and text or filePath required" });
159
- return;
160
- }
161
- if (text) {
162
- echoFilter.remember(chatGuid, text);
163
- await sender.sendMessage(chatGuid, text);
164
- }
165
- if (filePath) {
166
- await sender.sendAttachment(chatGuid, filePath);
167
- }
168
- console.log(`[web] /send: ${chatGuid} text=${text ? `"${text.substring(0, 60)}"` : "none"} file=${filePath ?? "none"}`);
169
- jsonResponse(response, 200, { ok: true });
170
- }
171
- catch (error) {
172
- console.error("[web] /send error:", error);
173
- jsonResponse(response, 500, { error: String(error) });
174
- }
175
- return;
176
- }
177
- // POST /prompt — send a prompt to the agent, reply to the chat
178
- if (request.method === "POST" && url.pathname === "/prompt") {
179
- try {
180
- const body = await parseJsonBody(request);
181
- const chatGuid = body.chatGuid;
182
- const prompt = body.prompt;
183
- if (!chatGuid || !prompt) {
184
- jsonResponse(response, 400, { error: "chatGuid and prompt required" });
185
- return;
186
- }
187
- console.log(`[web] /prompt: ${chatGuid} "${prompt.substring(0, 60)}"`);
188
- jsonResponse(response, 200, { ok: true });
189
- // Process asynchronously — agent replies are sent to the chat when ready
190
- agent
191
- .processMessage({
192
- chatGuid,
193
- sender: "cron",
194
- text: prompt,
195
- messageType: "imessage",
196
- groupName: "",
197
- replyToText: null,
198
- attachments: [],
199
- images: [],
200
- }, async (agentReply) => {
201
- if (agentReply.kind === "assistant") {
202
- echoFilter.remember(chatGuid, agentReply.text);
203
- await sender.sendMessage(chatGuid, agentReply.text);
204
- }
205
- }, { streamingBehavior: "followUp" })
206
- .then(() => {
207
- console.log(`[web] /prompt done: ${chatGuid}`);
208
- })
209
- .catch((error) => {
210
- console.error(`[web] /prompt error: ${chatGuid}`, error);
211
- });
212
- }
213
- catch (error) {
214
- console.error("[web] /prompt error:", error);
215
- jsonResponse(response, 500, { error: String(error) });
216
- }
217
- return;
218
- }
219
- // POST /toggle
220
- if (request.method === "POST" && url.pathname === "/toggle") {
221
- try {
222
- const body = await parseJsonBody(request);
223
- toggleChatReply(body.chatGuid, body.enabled);
224
- console.log(`[web] toggled reply for ${body.chatGuid}: enabled=${body.enabled}`);
225
- jsonResponse(response, 200, { ok: true });
226
- }
227
- catch (error) {
228
- console.error("[web] toggle error:", error);
229
- response.writeHead(400);
230
- response.end("bad request");
231
- }
232
- return;
233
- }
234
- // Default: chat page
235
- const blocks = getChatBlocks(workingDir);
236
- const settings = getSettings();
237
- const html = renderPage(blocks, settings);
238
- response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
239
- response.end(html);
240
- }
241
- const server = createServer((req, res) => {
242
- handleRequest(req, res).catch((error) => {
243
- console.error("[web] unhandled request error:", error);
244
- if (!res.headersSent) {
245
- res.writeHead(500);
246
- res.end("internal error");
247
- }
248
- });
249
- });
250
- return {
251
- start() {
252
- startWatcher();
253
- server.listen(port, host, () => {
254
- console.log(`[web] UI available at http://${host}:${port}`);
255
- });
256
- },
257
- stop() {
258
- if (debounceTimer)
259
- clearTimeout(debounceTimer);
260
- fsWatcher?.close();
261
- for (const client of sseClients)
262
- client.end();
263
- sseClients.clear();
264
- return new Promise((resolve, reject) => {
265
- server.close((error) => {
266
- if (error) {
267
- console.error("[web] server close error:", error);
268
- reject(error);
269
- }
270
- else {
271
- console.log("[web] server closed");
272
- resolve();
273
- }
274
- });
275
- });
276
- },
277
- };
278
- }
279
- //# sourceMappingURL=index.js.map