@kingcrab/pi-imessage 0.0.43 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +93 -127
  2. package/dist/agent/behavior.js +79 -0
  3. package/dist/agent/chats.js +88 -0
  4. package/dist/agent/commands.js +191 -0
  5. package/dist/agent/compaction.js +51 -0
  6. package/dist/agent/deliver.js +139 -0
  7. package/dist/agent/direct-send.js +69 -0
  8. package/dist/agent/harness.js +79 -0
  9. package/dist/agent/health.js +33 -0
  10. package/dist/agent/models.js +69 -0
  11. package/dist/agent/progress-format.js +87 -0
  12. package/dist/agent/progress.js +173 -0
  13. package/dist/agent/prompt.js +79 -0
  14. package/dist/agent/replies.js +10 -0
  15. package/dist/agent/run.js +160 -0
  16. package/dist/agent/scheduling.js +291 -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/restart.js +100 -0
  26. package/dist/extensions/schedules.js +42 -0
  27. package/dist/extensions/subagent.js +54 -0
  28. package/dist/extensions/web.js +100 -0
  29. package/dist/extensions/workspace.js +98 -0
  30. package/dist/main.js +578 -88
  31. package/dist/transport/attachments.js +37 -0
  32. package/dist/transport/http.js +110 -0
  33. package/dist/{rich-text.js → transport/rich-text.js} +25 -13
  34. package/dist/{send.js → transport/send.js} +13 -13
  35. package/dist/transport/watch.js +124 -0
  36. package/dist/web/app.js +826 -0
  37. package/dist/web/history.js +87 -0
  38. package/dist/web/memory.js +81 -0
  39. package/dist/web/page.js +29 -0
  40. package/dist/web/schedules.js +63 -0
  41. package/dist/web/server.js +382 -0
  42. package/dist/web/style.css +577 -0
  43. package/dist/web/tasks.js +97 -0
  44. package/docs/adr/0001-durable-owns-state.md +11 -0
  45. package/docs/adr/0002-module-layering.md +11 -0
  46. package/docs/adr/0003-replies-at-most-once.md +11 -0
  47. package/docs/adr/0004-native-extensions.md +11 -0
  48. package/docs/adr/0005-history-migration.md +11 -0
  49. package/docs/adr/0006-jsonl-storage.md +11 -0
  50. package/docs/adr/0007-images-by-path.md +11 -0
  51. package/docs/adr/0008-compaction-and-run.md +11 -0
  52. package/docs/adr/0009-one-shot-cutover.md +11 -0
  53. package/docs/adr/0010-test-budget.md +11 -0
  54. package/docs/adr/0011-reconcile-unsent-answers.md +24 -0
  55. package/docs/adr/0012-run-in-owned-conversation.md +22 -0
  56. package/docs/adr/0013-thin-adapters-around-durable.md +18 -0
  57. package/docs/adr/0014-ephemeral-private-storage.md +13 -0
  58. package/docs/adr/0015-ordinary-stop-keeps-native-inbox.md +21 -0
  59. package/docs/adr/0016-process-http-transport.md +21 -0
  60. package/docs/adr/0017-browser-cli-owned-scope.md +22 -0
  61. package/docs/adr/0018-tasks-without-prompt-api.md +19 -0
  62. package/docs/adr/0019-remove-scheduled-services.md +41 -0
  63. package/docs/adr/0020-chat-task-progress.md +41 -0
  64. package/docs/adr/0021-readable-progress-without-link-previews.md +33 -0
  65. package/docs/adr/0022-progress-admission-and-settlement.md +35 -0
  66. package/docs/adr/0023-retain-progress-type-prefixes.md +18 -0
  67. package/docs/adr/0024-status-marker-before-type-prefix.md +13 -0
  68. package/docs/adr/0025-reusable-progress-and-command-preview.md +49 -0
  69. package/docs/adr/0026-inline-command-only-requests.md +16 -0
  70. package/docs/adr/0027-command-preview-width.md +10 -0
  71. package/docs/adr/0028-command-preview-120.md +8 -0
  72. package/docs/adr/0030-shared-web-read-cache.md +33 -0
  73. package/docs/adr/0031-quiet-compaction-progress.md +32 -0
  74. package/docs/adr/0032-native-background-schedules.md +58 -0
  75. package/docs/adr/0033-operator-scheduled-run.md +23 -0
  76. package/docs/adr/0034-machine-local-schedule-time.md +36 -0
  77. package/docs/adr/0035-external-business-task-extensions.md +56 -0
  78. package/docs/adr/0036-workspace-policy-and-neutral-service-id.md +37 -0
  79. package/docs/adr/0037-restart-during-in-flight-work.md +30 -0
  80. package/docs/adr/0038-restart-any-time.md +25 -0
  81. package/docs/adr/0039-rerun-interrupted-tools.md +24 -0
  82. package/docs/adr/0040-chat-progress-notices.md +44 -0
  83. package/docs/adr/0041-restart-and-recovery.md +28 -0
  84. package/docs/adr/0042-background-schedules-and-workspace-extensions.md +44 -0
  85. package/docs/adr/0043-remove-legacy-migration.md +23 -0
  86. package/docs/adr/0044-message-channels.md +32 -0
  87. package/docs/adr/0045-chat-scheduling-tools.md +51 -0
  88. package/docs/adr/0046-selective-command-redaction.md +31 -0
  89. package/docs/adr/0047-one-scheduling-path.md +158 -0
  90. package/docs/adr/0048-quiet-attachment-reads.md +11 -0
  91. package/docs/adr/0049-per-chat-progress-notices.md +21 -0
  92. package/docs/adr/0052-host-owned-self-restart.md +53 -0
  93. package/docs/adr/README.md +54 -0
  94. package/docs/api.md +104 -0
  95. package/docs/architecture.md +164 -0
  96. package/docs/browser.md +93 -0
  97. package/docs/memory.md +27 -0
  98. package/docs/screenshot.png +0 -0
  99. package/docs/workspace-extensions.md +133 -0
  100. package/examples/workspace-extensions/system-context/index.ts +69 -0
  101. package/examples/workspace-extensions/system-context/package.json +3 -0
  102. package/ops/README.md +250 -0
  103. package/ops/preflight.mjs +28 -0
  104. package/package.json +36 -37
  105. package/dist/agent.d.ts +0 -84
  106. package/dist/agent.d.ts.map +0 -1
  107. package/dist/agent.js +0 -780
  108. package/dist/agent.js.map +0 -1
  109. package/dist/cli.d.ts +0 -15
  110. package/dist/cli.d.ts.map +0 -1
  111. package/dist/cli.js.map +0 -1
  112. package/dist/imessage.d.ts +0 -33
  113. package/dist/imessage.d.ts.map +0 -1
  114. package/dist/imessage.js +0 -76
  115. package/dist/imessage.js.map +0 -1
  116. package/dist/logger.d.ts +0 -33
  117. package/dist/logger.d.ts.map +0 -1
  118. package/dist/logger.js +0 -104
  119. package/dist/logger.js.map +0 -1
  120. package/dist/main.d.ts +0 -5
  121. package/dist/main.d.ts.map +0 -1
  122. package/dist/main.js.map +0 -1
  123. package/dist/memory.d.ts +0 -53
  124. package/dist/memory.d.ts.map +0 -1
  125. package/dist/memory.js +0 -196
  126. package/dist/memory.js.map +0 -1
  127. package/dist/model-health.d.ts +0 -11
  128. package/dist/model-health.d.ts.map +0 -1
  129. package/dist/model-health.js +0 -69
  130. package/dist/model-health.js.map +0 -1
  131. package/dist/pipeline.d.ts +0 -27
  132. package/dist/pipeline.d.ts.map +0 -1
  133. package/dist/pipeline.js +0 -59
  134. package/dist/pipeline.js.map +0 -1
  135. package/dist/queue.d.ts +0 -23
  136. package/dist/queue.d.ts.map +0 -1
  137. package/dist/queue.js +0 -89
  138. package/dist/queue.js.map +0 -1
  139. package/dist/reminders.d.ts +0 -42
  140. package/dist/reminders.d.ts.map +0 -1
  141. package/dist/reminders.js +0 -207
  142. package/dist/reminders.js.map +0 -1
  143. package/dist/rich-text.d.ts +0 -26
  144. package/dist/rich-text.d.ts.map +0 -1
  145. package/dist/rich-text.js.map +0 -1
  146. package/dist/self-echo.d.ts +0 -19
  147. package/dist/self-echo.d.ts.map +0 -1
  148. package/dist/self-echo.js +0 -54
  149. package/dist/self-echo.js.map +0 -1
  150. package/dist/send.d.ts +0 -24
  151. package/dist/send.d.ts.map +0 -1
  152. package/dist/send.js.map +0 -1
  153. package/dist/settings.d.ts +0 -44
  154. package/dist/settings.d.ts.map +0 -1
  155. package/dist/settings.js +0 -80
  156. package/dist/settings.js.map +0 -1
  157. package/dist/store.d.ts +0 -57
  158. package/dist/store.d.ts.map +0 -1
  159. package/dist/store.js +0 -124
  160. package/dist/store.js.map +0 -1
  161. package/dist/tasks.d.ts +0 -95
  162. package/dist/tasks.d.ts.map +0 -1
  163. package/dist/tasks.js +0 -384
  164. package/dist/tasks.js.map +0 -1
  165. package/dist/types.d.ts +0 -93
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -38
  168. package/dist/types.js.map +0 -1
  169. package/dist/watch.d.ts +0 -21
  170. package/dist/watch.d.ts.map +0 -1
  171. package/dist/watch.js +0 -219
  172. package/dist/watch.js.map +0 -1
  173. package/dist/web/data.d.ts +0 -11
  174. package/dist/web/data.d.ts.map +0 -1
  175. package/dist/web/data.js +0 -56
  176. package/dist/web/data.js.map +0 -1
  177. package/dist/web/html.d.ts +0 -5
  178. package/dist/web/html.d.ts.map +0 -1
  179. package/dist/web/html.js +0 -16
  180. package/dist/web/html.js.map +0 -1
  181. package/dist/web/index.d.ts +0 -25
  182. package/dist/web/index.d.ts.map +0 -1
  183. package/dist/web/index.js +0 -335
  184. package/dist/web/index.js.map +0 -1
  185. package/dist/web/render.d.ts +0 -11
  186. package/dist/web/render.d.ts.map +0 -1
  187. package/dist/web/render.js +0 -56
  188. package/dist/web/render.js.map +0 -1
  189. package/dist/web/templates/logs.eta +0 -75
  190. package/dist/web/templates/memory.eta +0 -91
  191. package/dist/web/templates/page.eta +0 -98
@@ -0,0 +1,577 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ --nav-width: 160px;
4
+ --page-space: 32px;
5
+ --bg: #111;
6
+ --surface: #1a1a1a;
7
+ --border: #292929;
8
+ --text: #ccc;
9
+ --muted: #888;
10
+ }
11
+
12
+ *,
13
+ *::before,
14
+ *::after {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ margin: 0;
20
+ background: var(--bg);
21
+ color: var(--text);
22
+ font: 13px / 1.6 monospace;
23
+ display: flex;
24
+ height: 100dvh;
25
+ overflow: hidden;
26
+ }
27
+
28
+ * {
29
+ scrollbar-width: thin;
30
+ scrollbar-color: #333 transparent;
31
+ }
32
+
33
+ a {
34
+ color: inherit;
35
+ text-underline-offset: 3px;
36
+ }
37
+
38
+ .app-nav {
39
+ width: var(--nav-width);
40
+ flex: 0 0 var(--nav-width);
41
+ padding: var(--page-space) 12px;
42
+ border-right: 1px solid var(--border);
43
+ overflow-y: auto;
44
+ }
45
+
46
+ .app-nav a {
47
+ display: block;
48
+ margin: 0 0 4px;
49
+ padding: 8px 12px;
50
+ color: var(--muted);
51
+ line-height: 24px;
52
+ white-space: nowrap;
53
+ text-decoration: none;
54
+ border-radius: 4px;
55
+ }
56
+
57
+ .app-nav a:hover {
58
+ color: #eee;
59
+ background: #191919;
60
+ }
61
+
62
+ .app-nav a.active {
63
+ color: #eee;
64
+ background: #222;
65
+ }
66
+
67
+ :focus-visible {
68
+ outline: 2px solid #aaa;
69
+ outline-offset: 2px;
70
+ }
71
+
72
+ .content {
73
+ flex: 1;
74
+ min-width: 0;
75
+ min-height: 0;
76
+ padding: var(--page-space);
77
+ overflow: auto;
78
+ }
79
+
80
+ .chat-list {
81
+ width: 220px;
82
+ flex: 0 0 220px;
83
+ padding: var(--page-space) 16px;
84
+ border-left: 1px solid var(--border);
85
+ overflow-y: auto;
86
+ }
87
+
88
+ .chat-list a {
89
+ display: block;
90
+ color: inherit;
91
+ text-decoration: none;
92
+ line-height: 2;
93
+ overflow: hidden;
94
+ text-overflow: ellipsis;
95
+ white-space: nowrap;
96
+ opacity: 0.55;
97
+ }
98
+
99
+ .chat-list a:hover,
100
+ .chat-list a.active {
101
+ opacity: 1;
102
+ }
103
+
104
+ h1,
105
+ h2,
106
+ h3,
107
+ strong {
108
+ font: inherit;
109
+ }
110
+
111
+ h1,
112
+ h2,
113
+ h3 {
114
+ margin: 0;
115
+ }
116
+
117
+ button,
118
+ input,
119
+ select {
120
+ font: inherit;
121
+ color: inherit;
122
+ background: transparent;
123
+ border: 1px solid #444;
124
+ border-radius: 3px;
125
+ padding: 5px 8px;
126
+ min-width: 0;
127
+ max-width: 100%;
128
+ }
129
+
130
+ select {
131
+ background: var(--bg);
132
+ }
133
+
134
+ button,
135
+ summary {
136
+ cursor: pointer;
137
+ }
138
+
139
+ button:hover {
140
+ color: #eee;
141
+ border-color: #777;
142
+ }
143
+
144
+ .page-header {
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: space-between;
148
+ gap: 16px;
149
+ margin-bottom: 20px;
150
+ }
151
+
152
+ .connection {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 12px;
156
+ color: var(--muted);
157
+ font-size: 11px;
158
+ }
159
+
160
+ .toolbar {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ gap: 10px;
164
+ align-items: end;
165
+ margin-bottom: 20px;
166
+ }
167
+
168
+ .toolbar label {
169
+ display: grid;
170
+ gap: 4px;
171
+ min-width: 0;
172
+ }
173
+
174
+ .toolbar .search {
175
+ flex: 1 1 220px;
176
+ }
177
+
178
+ .toolbar select {
179
+ max-width: 200px;
180
+ }
181
+
182
+ .card {
183
+ max-height: min(560px, 70dvh);
184
+ overflow: auto;
185
+ background: var(--surface);
186
+ margin-bottom: 24px;
187
+ scroll-margin-top: 20px;
188
+ }
189
+
190
+ .card-header {
191
+ padding: 12px 16px;
192
+ border-bottom: 1px solid var(--border);
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: space-between;
196
+ gap: 12px;
197
+ }
198
+
199
+ .card-header a {
200
+ text-decoration: none;
201
+ }
202
+
203
+ .meta {
204
+ font-size: 11px;
205
+ opacity: 0.5;
206
+ }
207
+
208
+ .reply-state {
209
+ white-space: nowrap;
210
+ opacity: 0.55;
211
+ }
212
+
213
+ .card-messages,
214
+ .card-content {
215
+ padding: 14px 16px;
216
+ }
217
+
218
+ .card-messages {
219
+ max-width: 100%;
220
+ overflow-x: auto;
221
+ }
222
+
223
+ body.page-chat:not(.conversation-detail) .card-messages {
224
+ max-height: 260px;
225
+ overflow: auto;
226
+ }
227
+
228
+ table {
229
+ border-collapse: collapse;
230
+ width: 100%;
231
+ }
232
+
233
+ td,
234
+ th {
235
+ vertical-align: top;
236
+ padding: 2px 0;
237
+ text-align: left;
238
+ font-weight: normal;
239
+ }
240
+
241
+ .c-fix {
242
+ white-space: nowrap;
243
+ padding-right: 8px;
244
+ width: 1%;
245
+ }
246
+
247
+ .c-sender {
248
+ text-align: right;
249
+ }
250
+
251
+ .c-time,
252
+ .c-source,
253
+ .c-channel {
254
+ opacity: 0.55;
255
+ }
256
+
257
+ .c-msg {
258
+ min-width: 180px;
259
+ white-space: pre-wrap;
260
+ overflow-wrap: anywhere;
261
+ }
262
+
263
+ .message-detail summary {
264
+ list-style: none;
265
+ }
266
+
267
+ .message-detail summary:hover {
268
+ text-decoration: underline;
269
+ text-underline-offset: 3px;
270
+ }
271
+
272
+ .message-detail[open] summary {
273
+ opacity: 0.5;
274
+ margin-bottom: 8px;
275
+ }
276
+
277
+ pre {
278
+ font: inherit;
279
+ white-space: pre-wrap;
280
+ overflow-wrap: anywhere;
281
+ margin: 0;
282
+ }
283
+
284
+ .muted,
285
+ .empty {
286
+ opacity: 0.55;
287
+ }
288
+
289
+ .empty {
290
+ padding: 12px 0;
291
+ }
292
+
293
+ #notice {
294
+ border-left: 1px solid #888;
295
+ padding-left: 12px;
296
+ }
297
+
298
+ .native-state {
299
+ margin-top: 24px;
300
+ opacity: 0.65;
301
+ }
302
+
303
+ .native-state > pre,
304
+ .record-details > pre {
305
+ max-height: min(420px, 50dvh);
306
+ overflow: auto;
307
+ margin-top: 12px;
308
+ }
309
+
310
+ .pagination {
311
+ display: flex;
312
+ gap: 16px;
313
+ margin: 16px 0;
314
+ }
315
+
316
+ .memory-record {
317
+ max-height: min(560px, 70dvh);
318
+ overflow: auto;
319
+ background: var(--surface);
320
+ padding: 12px 16px;
321
+ margin-bottom: 6px;
322
+ overflow-wrap: anywhere;
323
+ }
324
+
325
+ .row-head {
326
+ display: flex;
327
+ flex-wrap: wrap;
328
+ justify-content: space-between;
329
+ gap: 8px;
330
+ margin-bottom: 8px;
331
+ }
332
+
333
+ .memory-text {
334
+ white-space: pre-wrap;
335
+ margin: 8px 0;
336
+ }
337
+
338
+ .record-details {
339
+ margin-top: 8px;
340
+ }
341
+
342
+ .record-details-icon {
343
+ width: max-content;
344
+ margin-left: auto;
345
+ padding: 0 4px;
346
+ list-style: none;
347
+ opacity: 0.55;
348
+ }
349
+
350
+ .record-details-icon::-webkit-details-marker {
351
+ display: none;
352
+ }
353
+
354
+ .record-details-icon:hover {
355
+ opacity: 1;
356
+ }
357
+
358
+ .counts {
359
+ margin-bottom: 16px;
360
+ opacity: 0.55;
361
+ }
362
+
363
+ .setting-table th,
364
+ .setting-table td {
365
+ padding: 8px 16px;
366
+ border-bottom: 1px solid var(--border);
367
+ }
368
+
369
+ .setting-table th {
370
+ width: 180px;
371
+ opacity: 0.55;
372
+ }
373
+
374
+ .setting-table ul {
375
+ padding-left: 16px;
376
+ margin: 0;
377
+ }
378
+
379
+ .schedule-row > .card-content {
380
+ overflow-x: auto;
381
+ }
382
+
383
+ body.page-logs #view {
384
+ display: flex;
385
+ flex-direction: column;
386
+ flex: 1;
387
+ min-height: 0;
388
+ gap: 16px;
389
+ }
390
+
391
+ body.page-logs .content {
392
+ display: flex;
393
+ flex-direction: column;
394
+ overflow: hidden;
395
+ }
396
+
397
+ body.page-logs .card {
398
+ flex: 1;
399
+ display: flex;
400
+ flex-direction: column;
401
+ min-height: 0;
402
+ margin: 0;
403
+ overflow: hidden;
404
+ }
405
+
406
+ body.page-logs .card-content {
407
+ overflow: auto;
408
+ flex: 1;
409
+ }
410
+
411
+ body.page-logs .native-state {
412
+ flex: 0 0 auto;
413
+ max-height: 30%;
414
+ overflow: auto;
415
+ }
416
+
417
+ .task-table td,
418
+ .task-table th {
419
+ padding: 4px 12px 4px 0;
420
+ }
421
+
422
+ .task-tree {
423
+ list-style: none;
424
+ margin: 4px 0;
425
+ padding-left: 24px;
426
+ border-left: 1px solid var(--border);
427
+ }
428
+
429
+ .task-root {
430
+ padding: 14px 16px;
431
+ border-left: 0;
432
+ }
433
+
434
+ .task-tree li {
435
+ margin: 4px 0;
436
+ }
437
+
438
+ .task-tree details > pre {
439
+ padding: 8px 0 8px 16px;
440
+ }
441
+
442
+ .schedule-row {
443
+ background: var(--surface);
444
+ margin-bottom: 6px;
445
+ overflow-x: auto;
446
+ }
447
+
448
+ .schedule-summary {
449
+ display: grid;
450
+ grid-template-columns: 16px minmax(160px, 1fr) minmax(400px, 2fr) minmax(240px, 1.2fr) minmax(100px, 0.6fr);
451
+ min-width: 980px;
452
+ align-items: center;
453
+ gap: 10px;
454
+ padding: 12px 16px;
455
+ list-style: none;
456
+ }
457
+
458
+ .schedule-summary::-webkit-details-marker {
459
+ display: none;
460
+ }
461
+
462
+ .schedule-summary::before {
463
+ content: "+";
464
+ opacity: 0.55;
465
+ }
466
+
467
+ .schedule-row[open] > .schedule-summary::before {
468
+ content: "-";
469
+ }
470
+
471
+ .schedule-name,
472
+ .schedule-next,
473
+ .schedule-last,
474
+ .schedule-status {
475
+ min-width: 0;
476
+ overflow: hidden;
477
+ text-overflow: ellipsis;
478
+ white-space: nowrap;
479
+ }
480
+
481
+ .schedule-next,
482
+ .schedule-last,
483
+ .schedule-status {
484
+ opacity: 0.55;
485
+ white-space: nowrap;
486
+ }
487
+
488
+ [hidden] {
489
+ display: none;
490
+ }
491
+
492
+ @media (max-width: 900px) {
493
+ .chat-list {
494
+ display: none;
495
+ }
496
+ }
497
+
498
+ @media (max-width: 600px) {
499
+ :root {
500
+ --page-space: 16px;
501
+ }
502
+
503
+ body {
504
+ flex-direction: column;
505
+ }
506
+
507
+ .app-nav {
508
+ width: 100%;
509
+ flex: 0 0 57px;
510
+ display: flex;
511
+ align-items: center;
512
+ gap: 4px;
513
+ padding: 8px 12px;
514
+ border-right: 0;
515
+ border-bottom: 1px solid var(--border);
516
+ overflow-x: auto;
517
+ overflow-y: hidden;
518
+ }
519
+
520
+ .app-nav a {
521
+ flex: 0 0 auto;
522
+ margin: 0;
523
+ padding: 8px 12px;
524
+ }
525
+
526
+ .card-header {
527
+ gap: 8px;
528
+ overflow-wrap: anywhere;
529
+ }
530
+
531
+ .c-time,
532
+ .c-source {
533
+ display: none;
534
+ }
535
+
536
+ .page-header {
537
+ gap: 8px;
538
+ }
539
+
540
+ .setting-table th {
541
+ width: 100px;
542
+ }
543
+ }
544
+
545
+ .chat-table th,
546
+ .chat-table td {
547
+ padding: 8px 16px;
548
+ border-bottom: 1px solid var(--border);
549
+ }
550
+
551
+ .chat-table th {
552
+ opacity: 0.55;
553
+ white-space: nowrap;
554
+ }
555
+
556
+ .chat-table td:first-child {
557
+ width: 100%;
558
+ word-break: break-all;
559
+ }
560
+
561
+ .chat-table tbody tr:last-child td {
562
+ border-bottom: 0;
563
+ }
564
+
565
+ .chat-silent {
566
+ opacity: 0.55;
567
+ }
568
+
569
+ .chat-empty {
570
+ padding: 12px 16px;
571
+ }
572
+
573
+ /* The Chats tables grow with their rows; the page itself scrolls. */
574
+ body.page-chats .card {
575
+ max-height: none;
576
+ overflow: visible;
577
+ }
@@ -0,0 +1,97 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { displayHistory, historyActivity } from "./history.js";
3
+ /** Newest tasks listed per conversation; older finished ones are only flagged. */
4
+ export const RECENT_TASKS_PER_CONVERSATION = 20;
5
+ /**
6
+ * Read one consistent tree, newest first: all conversations, every unfinished task, every task that owns a
7
+ * conversation, their parent chains and the newest tasks of each conversation. Descending scans stop at the
8
+ * window instead of reading the whole task history (earendil-works/pi#10546).
9
+ */
10
+ export function readTaskTree(harness) {
11
+ return harness.commit(async (tx) => {
12
+ const conversations = [];
13
+ const kept = new Map();
14
+ const more = {};
15
+ let cursor;
16
+ do {
17
+ const page = await tx.scanConversations({}, 100, cursor);
18
+ for (const conversation of page.items) {
19
+ const history = await tx.scanEntries({ conversationId: conversation.id }, 100);
20
+ const { groupName } = displayHistory(history, 1);
21
+ conversations.push({
22
+ ...conversation,
23
+ updatedAt: historyActivity(history),
24
+ groupName: typeof groupName === "string" ? groupName : undefined,
25
+ });
26
+ const newest = await tx.scanTasks({ conversationId: conversation.id, order: "descending" }, RECENT_TASKS_PER_CONVERSATION + 1);
27
+ for (const task of newest.items.slice(0, RECENT_TASKS_PER_CONVERSATION))
28
+ kept.set(task.id, task);
29
+ if (newest.items.length > RECENT_TASKS_PER_CONVERSATION)
30
+ more[conversation.id] = true;
31
+ if (conversation.owner && !kept.has(conversation.owner.taskId)) {
32
+ const owner = await tx.task(conversation.owner.taskId);
33
+ if (owner)
34
+ kept.set(owner.id, owner);
35
+ }
36
+ }
37
+ cursor = page.next;
38
+ } while (cursor);
39
+ // Unfinished tasks can be old (sleeping schedulers); status scans are small.
40
+ for (const status of ["pending", "running", "waiting", "sleeping"]) {
41
+ cursor = undefined;
42
+ do {
43
+ const page = await tx.scanTasks({ status, order: "descending" }, 100, cursor);
44
+ for (const task of page.items)
45
+ kept.set(task.id, task);
46
+ cursor = page.next;
47
+ } while (cursor);
48
+ }
49
+ // A kept child needs its parent chain so the tree can place it.
50
+ for (const task of [...kept.values()]) {
51
+ for (let owner = task.owner; owner !== undefined && !kept.has(owner); owner = kept.get(owner)?.owner) {
52
+ const parent = await tx.task(owner);
53
+ if (!parent)
54
+ break;
55
+ kept.set(parent.id, parent);
56
+ }
57
+ }
58
+ const assistants = new Map();
59
+ const tasks = [];
60
+ for (const task of [...kept.values()].sort((a, b) => Number(b.id) - Number(a.id))) {
61
+ let name;
62
+ const input = task.input;
63
+ if (task.kind === "pi.tool" && input && typeof input === "object" && !Array.isArray(input)) {
64
+ const call = input;
65
+ if (typeof call.assistant === "number" && typeof call.callId === "string") {
66
+ const id = call.assistant;
67
+ if (!assistants.has(id))
68
+ assistants.set(id, await tx.entry(id));
69
+ for (const message of assistants.get(id)?.model ?? []) {
70
+ if (message.role !== "assistant")
71
+ continue;
72
+ const block = message.content.find((item) => item.type === "toolCall" && item.id === call.callId);
73
+ if (block?.type === "toolCall")
74
+ name = block.name;
75
+ }
76
+ }
77
+ }
78
+ const state = task.state;
79
+ tasks.push({
80
+ id: task.id,
81
+ conversationId: task.conversationId,
82
+ owner: task.owner,
83
+ kind: task.kind,
84
+ name,
85
+ background: task.background,
86
+ abortRequested: task.abortRequested,
87
+ status: state.status,
88
+ phase: state.checkpoint && typeof state.checkpoint === "object" && !Array.isArray(state.checkpoint)
89
+ ? state.checkpoint.phase
90
+ : undefined,
91
+ on: state.status === "waiting" ? state.on : undefined,
92
+ outcome: state.outcome?.status,
93
+ });
94
+ }
95
+ return { conversations, tasks, more };
96
+ }, BACKGROUND_CONTEXT);
97
+ }
@@ -0,0 +1,11 @@
1
+ # 0001. Pi Durable owns all chat state
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** The old bot wrapped the Pi SDK in its own queue, retries, timeouts and recovery fences. The first Durable attempt (#31) then added controllers, epochs, outboxes and a supervisor around Durable, and reached 54 files and 404 tests without shipping.
6
+
7
+ **Decision.** Pi Durable owns all chat state: inbox, deduplication, steering, runs, tool replay and compaction. Our code adds only what Durable cannot see: reading `chat.db` and sending through Messages.app. Use Durable as documented; don't re-check what it already guarantees.
8
+
9
+ **Consequences.** The host stays small. Durable's limits become ours: one writer per storage, no power-loss guarantee. When its API changes, we update our code instead of adding shims.
10
+
11
+ **Rejected.** Host-side controller, epoch and outbox layers (the `issue-31-durable` branch).
@@ -0,0 +1,11 @@
1
+ # 0002. Modules in strict layers
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** The old `src/` was flat; `agent.ts` alone was 1,536 lines, and any file could import any other.
6
+
7
+ **Decision.** Folders sit in rows ([architecture.md](../architecture.md)). A row imports only rows below it. `test/architecture.test.ts` enforces this. Functions over classes; no DI.
8
+
9
+ **Consequences.** Transport can be tested without an agent. Changing the agent never touches Messages code.
10
+
11
+ **Rejected.** A flat `src/` (the old layout). One package per layer (too much ceremony for one app).
@@ -0,0 +1,11 @@
1
+ # 0003. Replies are sent at most once
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** Sending through Messages.app and recording it in Durable cannot be one atomic step. A crash in between either loses or repeats a reply.
6
+
7
+ **Decision.** For each final answer: record `sending` in the conversation, send, then record `sent`. On restart, `sending` becomes `unknown`: it is logged and shown in the web UI, and never resent automatically; to resend, use `/send`. This covers agent replies; `/send`, cron and reminders report their result to the caller or run log.
8
+
9
+ **Consequences.** A crash in that window of a few milliseconds can drop one reply. Nothing is ever sent twice.
10
+
11
+ **Rejected.** Send, then record (can send twice). Confirming delivery through `chat.db` GUIDs (complex and unreliable).
@@ -0,0 +1,11 @@
1
+ # 0004. Native Durable extensions, not an SDK adapter
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** Chats need web search, page fetch and a browser. Durable has no loader for Pi SDK extensions, and the official durable coding agent loads none; its own way to add tools is `defineTool` / `section` / `defineDoc`.
6
+
7
+ **Decision.** Write the tools we use as native Durable extensions. `extensions/web.ts` provides search and readable-page fetch, with the search-result cache in a per-conversation `defineDoc` (this replaces the old `appendEntry("web-search-results")` hack and rewinds with forks and compaction). Browser work uses the `pi-browser` CLI through `bash` (the `browser` skill); no extension. We do not adapt or fork `pi-web-access` or `pi-browser-actions`. Also not loaded: `openai-codex-fast` (10 lines in `agent/harness.ts`), `claw-remote`, `tool_search` (all tools are offered directly), `pi-subagents` and `pi-goal-x` (replaced by `subagent.ts` and `/run`).
8
+
9
+ **Consequences.** `web.ts` owns its search provider and fetch dependencies (e.g. `undici`, `defuddle`, `turndown`, `unpdf`), and implements only the features the bot uses, not `pi-web-access`'s full set. No fragile `ExtensionAPI` shim to break on an upstream upgrade.
10
+
11
+ **Rejected.** A home-grown `ExtensionAPI` shim running `pi-web-access` unchanged (the old `headless-extensions.ts`: a fake context plus 30k bundled lines at runtime). Forking the extensions.