@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
@@ -0,0 +1,543 @@
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
+ }
@@ -0,0 +1,63 @@
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { displayHistory, historyActivity } from "./history.js";
3
+ /** Read one consistent tree, including terminal receipts and task-owned conversations. */
4
+ export function readTaskTree(harness) {
5
+ return harness.commit(async (tx) => {
6
+ const conversations = [];
7
+ let cursor;
8
+ do {
9
+ const page = await tx.scanConversations({}, 100, cursor);
10
+ for (const conversation of page.items) {
11
+ const history = await tx.scanEntries({ conversationId: conversation.id }, 100);
12
+ const { groupName } = displayHistory(history, 1);
13
+ conversations.push({
14
+ ...conversation,
15
+ updatedAt: historyActivity(history),
16
+ groupName: typeof groupName === "string" ? groupName : undefined,
17
+ });
18
+ }
19
+ cursor = page.next;
20
+ } while (cursor);
21
+ const tasks = [];
22
+ const assistants = new Map();
23
+ do {
24
+ const page = await tx.scanTasks({}, 100, cursor);
25
+ for (const task of page.items) {
26
+ let name;
27
+ const input = task.input;
28
+ if (task.kind === "pi.tool" && input && typeof input === "object" && !Array.isArray(input)) {
29
+ if (typeof input.assistant === "number" && typeof input.callId === "string") {
30
+ const id = input.assistant;
31
+ if (!assistants.has(id))
32
+ assistants.set(id, await tx.entry(id));
33
+ for (const message of assistants.get(id)?.model ?? []) {
34
+ if (message.role !== "assistant")
35
+ continue;
36
+ const call = message.content.find((block) => block.type === "toolCall" && block.id === input.callId);
37
+ if (call?.type === "toolCall")
38
+ name = call.name;
39
+ }
40
+ }
41
+ }
42
+ const state = task.state;
43
+ tasks.push({
44
+ id: task.id,
45
+ conversationId: task.conversationId,
46
+ owner: task.owner,
47
+ kind: task.kind,
48
+ name,
49
+ background: task.background,
50
+ abortRequested: task.abortRequested,
51
+ status: state.status,
52
+ phase: state.checkpoint && typeof state.checkpoint === "object" && !Array.isArray(state.checkpoint)
53
+ ? state.checkpoint.phase
54
+ : undefined,
55
+ on: state.status === "waiting" ? state.on : undefined,
56
+ outcome: state.outcome?.status,
57
+ });
58
+ }
59
+ cursor = page.next;
60
+ } while (cursor);
61
+ return { conversations, tasks };
62
+ }, BACKGROUND_CONTEXT);
63
+ }
@@ -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.
@@ -0,0 +1,11 @@
1
+ # 0005. Import old history once; start with an empty context
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** Old data per chat: `WORKING_DIR/<chat>/log.jsonl` (about 28,000 lines across 12 chats on claw), plus SDK sessions, goals, subagents and several GB of images.
6
+
7
+ **Decision.** `migrate/migrate.ts` runs once at cutover. Each `log.jsonl` becomes user and assistant entries in that chat's conversation, followed by one reset, so the model starts with an empty context. Attachments move to `WORKING_DIR/attachments/<chat>/`, and entries keep the path. The agent searches old messages by grepping `WORKING_DIR/durable/`; the system prompt says where. SDK sessions, goals, subagents and scratch are archived once, then deleted.
8
+
9
+ **Consequences.** The model doesn't remember pre-cutover talk until it searches. The web UI shows old and new history the same way.
10
+
11
+ **Rejected.** Importing the SDK context (different format). A summary per chat at reset (one model call per chat). A separate archive folder.
@@ -0,0 +1,11 @@
1
+ # 0006. One JSONL storage, backed up by hand
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** Durable can store to memory, SQLite or JSONL. The owner backs up `WORKING_DIR` with git in their dotfiles.
6
+
7
+ **Decision.** One JSONL storage at `WORKING_DIR/durable/` for all chats, with `fsync` on. A lock file stops a second process from opening it, since Durable has no cross-process lock. The bot runs no backup job.
8
+
9
+ **Consequences.** Readable text diffs in git. Not every change is an append: Durable rewrites its side files when it reclaims them. A copy taken mid-write still opens, because Durable drops an unfinished tail.
10
+
11
+ **Rejected.** SQLite (a binary file in git). One storage per chat (more files and locks, and no cross-chat tasks).
@@ -0,0 +1,11 @@
1
+ # 0007. Store images as file paths
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** Durable stores images inside entries as base64. Old chats hold several GB of images; inlining them would bloat `durable/` and the dotfiles backup. Durable's `read` tool cannot read images yet.
6
+
7
+ **Decision.** Incoming images are saved under `attachments/<chat>/` (HEIC converted to JPEG), and the user entry carries the path. `extensions/read-image.ts` extends `read`: for an image path it returns the image, resized for the model.
8
+
9
+ **Consequences.** Looking at an image costs the model one extra tool call. `durable/` stays text.
10
+
11
+ **Rejected.** Inline base64 (Durable's default).
@@ -0,0 +1,11 @@
1
+ # 0008. Compact every 6 hours; `/run` replaces `/goal`
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** The old bot reset every chat nightly, and used `pi-goal-x` for long tasks, which brought task trees, contracts and reviewers we don't need.
6
+
7
+ **Decision.** Every 6 hours, each chat with new entries since its last compaction is compacted in the background; no chat notifications. Durable's automatic compaction near the context limit stays on. `/run <duration> [task]` keeps the run going through an `onYield` hook until the task is done, the user is needed, or the deadline passes. One run per conversation; `/run-stop`, `/stop` and `/new` end it; it does not survive a restart.
8
+
9
+ **Consequences.** Context is never wiped on a schedule. `/run` spends tokens until it stops.
10
+
11
+ **Rejected.** Nightly reset. Migrating `pi-goal-x`.
@@ -0,0 +1,11 @@
1
+ # 0009. Switch over in one step
2
+
3
+ Status: accepted (2026-10-04; owner accepted 2026-10-05)
4
+
5
+ **Context.** Running old and new pipelines side by side (a per-chat canary) needs guards against processing a message twice. That was much of #31's complexity.
6
+
7
+ **Decision.** Build in a new repository, `pi-imessage-next`. At cutover: stop the old service, run the migration (which also saves the current `chat.db` row as the watch cursor), start the new service, then push the new code to `daya0576/pi-imessage` as one commit. Rollback: stop the new service, restore the `WORKING_DIR` backup, start the old release.
8
+
9
+ **Consequences.** Minutes of downtime; messages that arrive meanwhile are picked up from the cursor, and losing a few is acceptable. No code for running two pipelines.
10
+
11
+ **Rejected.** Per-chat canary.