@sumicom/quicksave 0.8.5 → 0.8.7

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 (193) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +31 -93
  3. package/dist/ai/asyncQueue.d.ts.map +1 -1
  4. package/dist/ai/asyncQueue.js +2 -0
  5. package/dist/ai/asyncQueue.js.map +1 -1
  6. package/dist/ai/attachmentStaging.d.ts +71 -0
  7. package/dist/ai/attachmentStaging.d.ts.map +1 -0
  8. package/dist/ai/attachmentStaging.js +243 -0
  9. package/dist/ai/attachmentStaging.js.map +1 -0
  10. package/dist/ai/attachmentStore.d.ts +40 -0
  11. package/dist/ai/attachmentStore.d.ts.map +1 -0
  12. package/dist/ai/attachmentStore.js +173 -0
  13. package/dist/ai/attachmentStore.js.map +1 -0
  14. package/dist/ai/cardBuilder.d.ts +6 -6
  15. package/dist/ai/cardBuilder.d.ts.map +1 -1
  16. package/dist/ai/cardBuilder.js +164 -16
  17. package/dist/ai/cardBuilder.js.map +1 -1
  18. package/dist/ai/claudeCliProvider.d.ts +34 -15
  19. package/dist/ai/claudeCliProvider.d.ts.map +1 -1
  20. package/dist/ai/claudeCliProvider.js +108 -64
  21. package/dist/ai/claudeCliProvider.js.map +1 -1
  22. package/dist/ai/claudeCodeProvider.d.ts.map +1 -1
  23. package/dist/ai/claudeCodeProvider.js +2 -0
  24. package/dist/ai/claudeCodeProvider.js.map +1 -1
  25. package/dist/ai/claudeSdkProvider.d.ts +6 -3
  26. package/dist/ai/claudeSdkProvider.d.ts.map +1 -1
  27. package/dist/ai/claudeSdkProvider.js +97 -29
  28. package/dist/ai/claudeSdkProvider.js.map +1 -1
  29. package/dist/ai/codexAppServer/approvalMapping.d.ts.map +1 -1
  30. package/dist/ai/codexAppServer/approvalMapping.js.map +1 -1
  31. package/dist/ai/codexAppServer/cardAdapter.d.ts +0 -3
  32. package/dist/ai/codexAppServer/cardAdapter.d.ts.map +1 -1
  33. package/dist/ai/codexAppServer/cardAdapter.js +40 -5
  34. package/dist/ai/codexAppServer/cardAdapter.js.map +1 -1
  35. package/dist/ai/codexAppServer/index.d.ts.map +1 -1
  36. package/dist/ai/codexAppServer/index.js +2 -0
  37. package/dist/ai/codexAppServer/index.js.map +1 -1
  38. package/dist/ai/codexAppServer/overrideStore.d.ts.map +1 -1
  39. package/dist/ai/codexAppServer/overrideStore.js.map +1 -1
  40. package/dist/ai/codexAppServer/permissionMapping.d.ts.map +1 -1
  41. package/dist/ai/codexAppServer/permissionMapping.js.map +1 -1
  42. package/dist/ai/codexAppServer/processManager.d.ts.map +1 -1
  43. package/dist/ai/codexAppServer/processManager.js +2 -0
  44. package/dist/ai/codexAppServer/processManager.js.map +1 -1
  45. package/dist/ai/codexAppServer/provider.d.ts.map +1 -1
  46. package/dist/ai/codexAppServer/provider.js +9 -24
  47. package/dist/ai/codexAppServer/provider.js.map +1 -1
  48. package/dist/ai/codexAppServer/rpcClient.d.ts.map +1 -1
  49. package/dist/ai/codexAppServer/rpcClient.js.map +1 -1
  50. package/dist/ai/codexAppServer/schema/index.d.ts.map +1 -1
  51. package/dist/ai/codexAppServer/schema/index.js +2 -0
  52. package/dist/ai/codexAppServer/schema/index.js.map +1 -1
  53. package/dist/ai/codexAppServer/stdioTransport.d.ts.map +1 -1
  54. package/dist/ai/codexAppServer/stdioTransport.js.map +1 -1
  55. package/dist/ai/codexAppServer/tokenAccounting.d.ts.map +1 -1
  56. package/dist/ai/codexAppServer/tokenAccounting.js.map +1 -1
  57. package/dist/ai/codexAppServer/version.d.ts.map +1 -1
  58. package/dist/ai/codexAppServer/version.js +2 -0
  59. package/dist/ai/codexAppServer/version.js.map +1 -1
  60. package/dist/ai/codexLogin.d.ts.map +1 -1
  61. package/dist/ai/codexLogin.js +2 -0
  62. package/dist/ai/codexLogin.js.map +1 -1
  63. package/dist/ai/codexMcpProvider.d.ts.map +1 -1
  64. package/dist/ai/codexMcpProvider.js +7 -10
  65. package/dist/ai/codexMcpProvider.js.map +1 -1
  66. package/dist/ai/commitSummary.d.ts.map +1 -1
  67. package/dist/ai/commitSummary.js +2 -0
  68. package/dist/ai/commitSummary.js.map +1 -1
  69. package/dist/ai/commitSummaryCli.d.ts.map +1 -1
  70. package/dist/ai/commitSummaryCli.js +2 -0
  71. package/dist/ai/commitSummaryCli.js.map +1 -1
  72. package/dist/ai/commitSummaryStore.d.ts.map +1 -1
  73. package/dist/ai/commitSummaryStore.js +2 -0
  74. package/dist/ai/commitSummaryStore.js.map +1 -1
  75. package/dist/ai/contentBlocks.d.ts +9 -0
  76. package/dist/ai/contentBlocks.d.ts.map +1 -0
  77. package/dist/ai/contentBlocks.js +86 -0
  78. package/dist/ai/contentBlocks.js.map +1 -0
  79. package/dist/ai/debugLogger.d.ts.map +1 -1
  80. package/dist/ai/debugLogger.js +2 -0
  81. package/dist/ai/debugLogger.js.map +1 -1
  82. package/dist/ai/enrichEntry.d.ts.map +1 -1
  83. package/dist/ai/enrichEntry.js.map +1 -1
  84. package/dist/ai/pdfMeta.d.ts +8 -0
  85. package/dist/ai/pdfMeta.d.ts.map +1 -0
  86. package/dist/ai/pdfMeta.js +51 -0
  87. package/dist/ai/pdfMeta.js.map +1 -0
  88. package/dist/ai/provider.d.ts +21 -8
  89. package/dist/ai/provider.d.ts.map +1 -1
  90. package/dist/ai/provider.js.map +1 -1
  91. package/dist/ai/sandboxMcp.d.ts.map +1 -1
  92. package/dist/ai/sandboxMcp.js +2 -0
  93. package/dist/ai/sandboxMcp.js.map +1 -1
  94. package/dist/ai/sandboxMcpStdio.d.ts.map +1 -1
  95. package/dist/ai/sandboxMcpStdio.js +2 -0
  96. package/dist/ai/sandboxMcpStdio.js.map +1 -1
  97. package/dist/ai/sessionManager.d.ts +19 -7
  98. package/dist/ai/sessionManager.d.ts.map +1 -1
  99. package/dist/ai/sessionManager.js +81 -23
  100. package/dist/ai/sessionManager.js.map +1 -1
  101. package/dist/ai/sessionRegistry.d.ts.map +1 -1
  102. package/dist/ai/sessionRegistry.js +2 -0
  103. package/dist/ai/sessionRegistry.js.map +1 -1
  104. package/dist/ai/sessionStore.d.ts.map +1 -1
  105. package/dist/ai/sessionStore.js +2 -0
  106. package/dist/ai/sessionStore.js.map +1 -1
  107. package/dist/ai/systemPrompt.d.ts.map +1 -1
  108. package/dist/ai/systemPrompt.js +3 -0
  109. package/dist/ai/systemPrompt.js.map +1 -1
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +2 -0
  112. package/dist/config.js.map +1 -1
  113. package/dist/connection/connection.d.ts.map +1 -1
  114. package/dist/connection/connection.js +2 -0
  115. package/dist/connection/connection.js.map +1 -1
  116. package/dist/connection/e2eHarness.d.ts +1 -1
  117. package/dist/connection/e2eHarness.d.ts.map +1 -1
  118. package/dist/connection/e2eHarness.js +26 -8
  119. package/dist/connection/e2eHarness.js.map +1 -1
  120. package/dist/connection/fakeRelay.d.ts.map +1 -1
  121. package/dist/connection/fakeRelay.js +2 -0
  122. package/dist/connection/fakeRelay.js.map +1 -1
  123. package/dist/connection/pubsub.d.ts.map +1 -1
  124. package/dist/connection/pubsub.js +2 -0
  125. package/dist/connection/pubsub.js.map +1 -1
  126. package/dist/connection/relay.d.ts.map +1 -1
  127. package/dist/connection/relay.js +2 -0
  128. package/dist/connection/relay.js.map +1 -1
  129. package/dist/files/fileBrowser.d.ts.map +1 -1
  130. package/dist/files/fileBrowser.js +44 -2
  131. package/dist/files/fileBrowser.js.map +1 -1
  132. package/dist/git/operations.d.ts.map +1 -1
  133. package/dist/git/operations.js +2 -0
  134. package/dist/git/operations.js.map +1 -1
  135. package/dist/handlers/legacyBusAdapter.d.ts +1 -1
  136. package/dist/handlers/legacyBusAdapter.d.ts.map +1 -1
  137. package/dist/handlers/legacyBusAdapter.js +13 -1
  138. package/dist/handlers/legacyBusAdapter.js.map +1 -1
  139. package/dist/handlers/messageHandler.d.ts +23 -0
  140. package/dist/handlers/messageHandler.d.ts.map +1 -1
  141. package/dist/handlers/messageHandler.js +226 -14
  142. package/dist/handlers/messageHandler.js.map +1 -1
  143. package/dist/index.js +42 -2
  144. package/dist/index.js.map +1 -1
  145. package/dist/messageBus/busServerTransport.d.ts.map +1 -1
  146. package/dist/messageBus/busServerTransport.js +2 -0
  147. package/dist/messageBus/busServerTransport.js.map +1 -1
  148. package/dist/service/debugHttpServer.d.ts.map +1 -1
  149. package/dist/service/debugHttpServer.js +2 -0
  150. package/dist/service/debugHttpServer.js.map +1 -1
  151. package/dist/service/ensureDaemon.d.ts.map +1 -1
  152. package/dist/service/ensureDaemon.js +27 -2
  153. package/dist/service/ensureDaemon.js.map +1 -1
  154. package/dist/service/index.d.ts.map +1 -1
  155. package/dist/service/index.js +2 -0
  156. package/dist/service/index.js.map +1 -1
  157. package/dist/service/ipcClient.d.ts.map +1 -1
  158. package/dist/service/ipcClient.js +3 -1
  159. package/dist/service/ipcClient.js.map +1 -1
  160. package/dist/service/ipcServer.d.ts.map +1 -1
  161. package/dist/service/ipcServer.js +2 -0
  162. package/dist/service/ipcServer.js.map +1 -1
  163. package/dist/service/pushClient.d.ts.map +1 -1
  164. package/dist/service/pushClient.js +2 -0
  165. package/dist/service/pushClient.js.map +1 -1
  166. package/dist/service/run.d.ts.map +1 -1
  167. package/dist/service/run.js +14 -1
  168. package/dist/service/run.js.map +1 -1
  169. package/dist/service/singleton.d.ts +1 -0
  170. package/dist/service/singleton.d.ts.map +1 -1
  171. package/dist/service/singleton.js +5 -0
  172. package/dist/service/singleton.js.map +1 -1
  173. package/dist/service/stateStore.d.ts.map +1 -1
  174. package/dist/service/stateStore.js +2 -0
  175. package/dist/service/stateStore.js.map +1 -1
  176. package/dist/service/systemdUnit.d.ts +149 -0
  177. package/dist/service/systemdUnit.d.ts.map +1 -0
  178. package/dist/service/systemdUnit.js +322 -0
  179. package/dist/service/systemdUnit.js.map +1 -0
  180. package/dist/service/types.d.ts +6 -0
  181. package/dist/service/types.d.ts.map +1 -1
  182. package/dist/service/types.js +3 -1
  183. package/dist/service/types.js.map +1 -1
  184. package/dist/storage/eventStore.d.ts.map +1 -1
  185. package/dist/storage/eventStore.js +2 -0
  186. package/dist/storage/eventStore.js.map +1 -1
  187. package/dist/terminal/terminalManager.d.ts.map +1 -1
  188. package/dist/terminal/terminalManager.js +2 -0
  189. package/dist/terminal/terminalManager.js.map +1 -1
  190. package/dist/tombstoneCheck.d.ts.map +1 -1
  191. package/dist/tombstoneCheck.js +2 -0
  192. package/dist/tombstoneCheck.js.map +1 -1
  193. package/package.json +3 -3
@@ -1,3 +1,5 @@
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
1
3
  import { createMessage, } from '@sumicom/quicksave-shared';
2
4
  /**
3
5
  * Verbs from the legacy `MessageHandler` switch that are exposed as bus
@@ -11,7 +13,7 @@ import { createMessage, } from '@sumicom/quicksave-shared';
11
13
  * touching:
12
14
  * 1. `messageHandler.ts` switch (the handler itself)
13
15
  * 2. This array (the bus exposure)
14
- * 3. The relevant docs (`docs/references/quicksave-architecture.md`)
16
+ * 3. The relevant docs (`docs/references/quicksave-architecture.en.md`)
15
17
  */
16
18
  export const LEGACY_BUS_VERBS = [
17
19
  'ping',
@@ -52,6 +54,10 @@ export const LEGACY_BUS_VERBS = [
52
54
  'agent:check-update',
53
55
  'agent:update',
54
56
  'agent:restart',
57
+ // systemd user-unit (Linux only — agent returns available:false elsewhere)
58
+ 'systemd:status',
59
+ 'systemd:install',
60
+ 'systemd:uninstall',
55
61
  // codex
56
62
  'codex:list-models',
57
63
  'codex:login-start',
@@ -73,6 +79,7 @@ export const LEGACY_BUS_VERBS = [
73
79
  'session:update-history',
74
80
  'session:delete-history',
75
81
  'session:list-archived',
82
+ 'session:mark-read',
76
83
  // project
77
84
  'project:list-summaries',
78
85
  'project:list-repos',
@@ -88,6 +95,10 @@ export const LEGACY_BUS_VERBS = [
88
95
  // files
89
96
  'files:list',
90
97
  'files:read',
98
+ // attachments
99
+ 'attachment:upload',
100
+ 'attachment:cancel',
101
+ 'attachment:fetch',
91
102
  ];
92
103
  /**
93
104
  * Bridge legacy `MessageHandler` request/response verbs onto a
@@ -105,6 +116,7 @@ export const LEGACY_BUS_VERBS = [
105
116
  * callers can `String#startsWith` to detect specific codes.
106
117
  */
107
118
  export function wireLegacyBusVerbs(bus, handler, verbs = LEGACY_BUS_VERBS) {
119
+ console.log(`[legacy-bus] wiring ${verbs.length} verbs; attachment* present: ${verbs.filter(v => v.startsWith('attachment:')).join(',') || 'NONE'}`);
108
120
  for (const verb of verbs) {
109
121
  bus.onCommand(verb, async (rawPayload, { peer }) => {
110
122
  let repoPath;
@@ -1 +1 @@
1
- {"version":3,"file":"legacyBusAdapter.js","sourceRoot":"","sources":["../../src/handlers/legacyBusAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"legacyBusAdapter.js","sourceRoot":"","sources":["../../src/handlers/legacyBusAdapter.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { Message, License, Repository, SessionRegistryEntry, AgentId, CodexModel
2
2
  import type { PushClient } from '../service/pushClient.js';
3
3
  import { CommitSummaryStateStore } from '../ai/commitSummaryStore.js';
4
4
  import { SessionManager } from '../ai/sessionManager.js';
5
+ import { AttachmentStaging } from '../ai/attachmentStaging.js';
5
6
  import { CodexLoginManager } from '../ai/codexLogin.js';
6
7
  export declare class MessageHandler {
7
8
  private repos;
@@ -17,6 +18,8 @@ export declare class MessageHandler {
17
18
  * `state-updated` event to `connection.broadcast` (see service/run.ts). */
18
19
  private commitSummaryStore;
19
20
  private claudeService;
21
+ private attachmentStaging;
22
+ private attachmentGcTimer;
20
23
  private pushClient;
21
24
  private latestVersionCache;
22
25
  private versionCheckInFlight;
@@ -172,6 +175,9 @@ export declare class MessageHandler {
172
175
  private handleAgentCheckUpdate;
173
176
  private handleAgentUpdate;
174
177
  private handleAgentRestart;
178
+ private handleSystemdStatus;
179
+ private handleSystemdInstall;
180
+ private handleSystemdUninstall;
175
181
  private handleCodexListModels;
176
182
  /**
177
183
  * Kick off the Codex device-auth OAuth flow on the daemon. Returns the
@@ -208,6 +214,18 @@ export declare class MessageHandler {
208
214
  private handleUpdateHistory;
209
215
  private handleDeleteHistory;
210
216
  private handleListArchived;
217
+ /**
218
+ * Stamp `lastReadAt` on a registry entry and broadcast the update so every
219
+ * connected PWA client converges on the same read state. The "unread" mark
220
+ * lives server-side under the user's group identity (see
221
+ * `docs/guidelines/sync-security.en.md`) — there's no per-device scoping,
222
+ * so reading on the phone clears the badge on the desktop too.
223
+ *
224
+ * Idempotent: late-arriving / out-of-order viewedAt values that are older
225
+ * than the persisted timestamp are dropped (max-wins) so a slow tab can't
226
+ * undo a fresh read from a faster one.
227
+ */
228
+ private handleMarkRead;
211
229
  private handleListProjectSummaries;
212
230
  private handleListProjectRepos;
213
231
  /**
@@ -236,6 +254,11 @@ export declare class MessageHandler {
236
254
  private handleTerminalRename;
237
255
  private handleFilesList;
238
256
  private handleFilesRead;
257
+ /** Exposed so other agent paths (sessionManager) can resolve staged ids. */
258
+ getAttachmentStaging(): AttachmentStaging;
259
+ private handleAttachmentUpload;
260
+ private handleAttachmentCancel;
261
+ private handleAttachmentFetch;
239
262
  private createErrorResponse;
240
263
  }
241
264
  //# sourceMappingURL=messageHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"messageHandler.d.ts","sourceRoot":"","sources":["../../src/handlers/messageHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"messageHandler.d.ts","sourceRoot":"","sources":["../../src/handlers/messageHandler.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,7 @@
1
- import { createMessage, generateMessageId, } from '@sumicom/quicksave-shared';
1
+ // SPDX-FileCopyrightText: 2026 King Young Technology
2
+ // SPDX-License-Identifier: MIT
3
+ import { createMessage, } from '@sumicom/quicksave-shared';
4
+ import { getSystemdStatus, installUserUnit, uninstallUserUnit, systemctlAvailable, } from '../service/systemdUnit.js';
2
5
  import { GitOperations } from '../git/operations.js';
3
6
  import { getAnthropicApiKey, setAnthropicApiKey, hasAnthropicApiKey, addManagedRepo, removeManagedRepo, addManagedCodingPath, removeManagedCodingPath } from '../config.js';
4
7
  import { CommitSummaryService } from '../ai/commitSummary.js';
@@ -6,6 +9,8 @@ import { CommitSummaryCliService, CommitSummaryCliError } from '../ai/commitSumm
6
9
  import { CommitSummaryStateStore } from '../ai/commitSummaryStore.js';
7
10
  import { SessionManager } from '../ai/sessionManager.js';
8
11
  import { ClaudeCodeProvider } from '../ai/claudeCodeProvider.js';
12
+ import { AttachmentStaging } from '../ai/attachmentStaging.js';
13
+ import { loadAttachment, removeSessionAttachments } from '../ai/attachmentStore.js';
9
14
  import { CodexAppServerProvider } from '../ai/codexAppServer/index.js';
10
15
  import { CodexLoginManager } from '../ai/codexLogin.js';
11
16
  import { getTerminalManager } from '../terminal/terminalManager.js';
@@ -16,7 +21,7 @@ import { getEventStore } from '../storage/eventStore.js';
16
21
  import { readdir, stat, readFile } from 'fs/promises';
17
22
  import { existsSync } from 'fs';
18
23
  import { join, dirname, basename } from 'path';
19
- import { homedir } from 'os';
24
+ import { homedir, platform as osPlatform } from 'os';
20
25
  import { spawnAppServer } from '../ai/codexAppServer/index.js';
21
26
  const VERSION_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000; // 12 hours
22
27
  // model/list reflects the current authenticated account's available models.
@@ -24,6 +29,14 @@ const VERSION_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000; // 12 hours
24
29
  // every PWA reload; short enough that plan upgrades / model rollouts surface
25
30
  // within the same session day. Force-refreshes still bypass the TTL.
26
31
  const CODEX_MODELS_TTL_MS = 30 * 60 * 1000;
32
+ /** Coerce `os.platform()` into the narrow union the PWA expects. Anything
33
+ * outside the three first-class platforms gets `'other'` so the field is
34
+ * always present and the PWA doesn't have to handle `undefined`. */
35
+ function normalizePlatform(p) {
36
+ if (p === 'linux' || p === 'darwin' || p === 'win32')
37
+ return p;
38
+ return 'other';
39
+ }
27
40
  /** Shallow equality on a model list. Used to suppress no-op
28
41
  * `/codex/models` broadcasts after a TTL refresh that returned the
29
42
  * same list. */
@@ -74,7 +87,7 @@ function projectAppServerModel(m) {
74
87
  }
75
88
  export class MessageHandler {
76
89
  repos;
77
- agentVersion = '0.8.5';
90
+ agentVersion = '0.8.7';
78
91
  defaultRepoPath;
79
92
  clientRepos = new Map(); // peerAddress -> repoPath
80
93
  repoLocks = new Map(); // repoPath -> peerAddress holding lock
@@ -86,6 +99,8 @@ export class MessageHandler {
86
99
  * `state-updated` event to `connection.broadcast` (see service/run.ts). */
87
100
  commitSummaryStore = new CommitSummaryStateStore();
88
101
  claudeService;
102
+ attachmentStaging = new AttachmentStaging();
103
+ attachmentGcTimer = null;
89
104
  pushClient = null;
90
105
  latestVersionCache = null;
91
106
  versionCheckInFlight = null;
@@ -117,6 +132,10 @@ export class MessageHandler {
117
132
  this.codingPaths.set(p, { path: p, name: basename(p) });
118
133
  }
119
134
  }
135
+ this.attachmentGcTimer = setInterval(() => this.attachmentStaging.gc(), 60_000);
136
+ if (typeof this.attachmentGcTimer.unref === 'function') {
137
+ this.attachmentGcTimer.unref();
138
+ }
120
139
  }
121
140
  /**
122
141
  * Check npm registry for latest version. Deduped: returns cached value
@@ -359,10 +378,17 @@ export class MessageHandler {
359
378
  this.repoLocks.delete(repoPath);
360
379
  }
361
380
  }
381
+ // Drop any in-flight uploads from this peer; on reconnect the PWA will
382
+ // re-stage them via the upload manager's resume path.
383
+ this.attachmentStaging.removePeer(peerAddress);
362
384
  // Pending user input requests are NOT auto-approved on disconnect.
363
385
  // They persist until the user reconnects and explicitly responds.
364
386
  }
365
387
  cleanup() {
388
+ if (this.attachmentGcTimer) {
389
+ clearInterval(this.attachmentGcTimer);
390
+ this.attachmentGcTimer = null;
391
+ }
366
392
  this.claudeService.cleanup();
367
393
  }
368
394
  getActiveSessionCount() {
@@ -397,7 +423,8 @@ export class MessageHandler {
397
423
  async handleMessage(message, peerAddress = 'default') {
398
424
  const isVerbose = message.type.startsWith('claude:') ||
399
425
  message.type.startsWith('agent:add') ||
400
- message.type.startsWith('project:');
426
+ message.type.startsWith('project:') ||
427
+ message.type.startsWith('attachment:');
401
428
  if (isVerbose) {
402
429
  console.log(`[msg] ${message.type} from ${peerAddress.slice(0, 12)}`);
403
430
  }
@@ -498,6 +525,12 @@ export class MessageHandler {
498
525
  return this.handleAgentUpdate(message);
499
526
  case 'agent:restart':
500
527
  return this.handleAgentRestart(message);
528
+ case 'systemd:status':
529
+ return this.handleSystemdStatus(message);
530
+ case 'systemd:install':
531
+ return this.handleSystemdInstall(message);
532
+ case 'systemd:uninstall':
533
+ return this.handleSystemdUninstall(message);
501
534
  case 'codex:list-models':
502
535
  return this.handleCodexListModels(message);
503
536
  case 'codex:login-start':
@@ -535,6 +568,8 @@ export class MessageHandler {
535
568
  return this.handleDeleteHistory(message);
536
569
  case 'session:list-archived':
537
570
  return this.handleListArchived(message);
571
+ case 'session:mark-read':
572
+ return this.handleMarkRead(message);
538
573
  case 'project:list-summaries':
539
574
  return this.handleListProjectSummaries(message);
540
575
  case 'project:list-repos':
@@ -557,6 +592,12 @@ export class MessageHandler {
557
592
  return this.handleFilesList(message);
558
593
  case 'files:read':
559
594
  return this.handleFilesRead(message);
595
+ case 'attachment:upload':
596
+ return this.handleAttachmentUpload(message, peerAddress);
597
+ case 'attachment:cancel':
598
+ return this.handleAttachmentCancel(message, peerAddress);
599
+ case 'attachment:fetch':
600
+ return this.handleAttachmentFetch(message);
560
601
  default:
561
602
  return this.createErrorResponse(message.id, 'UNKNOWN_MESSAGE_TYPE', `Unknown message type: ${message.type}`);
562
603
  }
@@ -575,6 +616,7 @@ export class MessageHandler {
575
616
  latestVersion: this.latestVersionCache?.version,
576
617
  devBuild: !this.productionBuild || undefined,
577
618
  codexModels: this.codexModelsCache?.models,
619
+ platform: normalizePlatform(osPlatform()),
578
620
  });
579
621
  response.id = message.id;
580
622
  return response;
@@ -1506,6 +1548,77 @@ export class MessageHandler {
1506
1548
  return response;
1507
1549
  }
1508
1550
  }
1551
+ // ==========================================================================
1552
+ // systemd user-unit (Linux auto-start at login)
1553
+ //
1554
+ // The PWA toggles install/uninstall from the per-machine settings page.
1555
+ // These handlers are no-ops on non-Linux hosts (the status payload's
1556
+ // `available: false` field is the gate the PWA uses to hide the section,
1557
+ // so non-Linux still gets a well-formed response instead of an error).
1558
+ // ==========================================================================
1559
+ handleSystemdStatus(message) {
1560
+ const status = getSystemdStatus();
1561
+ const response = createMessage('systemd:status:response', status);
1562
+ response.id = message.id;
1563
+ return response;
1564
+ }
1565
+ handleSystemdInstall(message) {
1566
+ if (!systemctlAvailable()) {
1567
+ const response = createMessage('systemd:install:response', {
1568
+ success: false,
1569
+ error: 'systemctl --user is not available on this host',
1570
+ status: getSystemdStatus(),
1571
+ });
1572
+ response.id = message.id;
1573
+ return response;
1574
+ }
1575
+ const result = installUserUnit();
1576
+ const response = createMessage('systemd:install:response', result);
1577
+ response.id = message.id;
1578
+ return response;
1579
+ }
1580
+ handleSystemdUninstall(message) {
1581
+ // Uninstalling can stop the systemd-managed daemon as a side effect — if
1582
+ // the running process IS that daemon, `disable --now` would kill our IPC
1583
+ // server before the response reaches the PWA. So we compute the response
1584
+ // up front (which captures the pre-uninstall status) but defer the
1585
+ // actual mutation until next tick, giving the bus transport a chance to
1586
+ // flush. The next CLI invocation will hit `ensureDaemon`, which falls
1587
+ // back to detached spawn now that the unit is gone.
1588
+ if (!systemctlAvailable()) {
1589
+ const response = createMessage('systemd:uninstall:response', {
1590
+ success: false,
1591
+ error: 'systemctl --user is not available on this host',
1592
+ status: getSystemdStatus(),
1593
+ });
1594
+ response.id = message.id;
1595
+ return response;
1596
+ }
1597
+ setImmediate(() => {
1598
+ try {
1599
+ uninstallUserUnit();
1600
+ }
1601
+ catch (err) {
1602
+ console.error('[systemd] deferred uninstall failed:', err);
1603
+ }
1604
+ });
1605
+ // Optimistic response: report the post-uninstall shape (unitInstalled,
1606
+ // unitEnabled, isActive all expected to flip false). We can't actually
1607
+ // observe the post state because we haven't run the disable yet.
1608
+ const optimistic = getSystemdStatus();
1609
+ const response = createMessage('systemd:uninstall:response', {
1610
+ success: true,
1611
+ status: {
1612
+ ...optimistic,
1613
+ unitInstalled: false,
1614
+ unitEnabled: false,
1615
+ isActive: false,
1616
+ currentExecStart: undefined,
1617
+ },
1618
+ });
1619
+ response.id = message.id;
1620
+ return response;
1621
+ }
1509
1622
  async handleCodexListModels(message) {
1510
1623
  try {
1511
1624
  const [models, auth] = await Promise.all([
@@ -1558,10 +1671,9 @@ export class MessageHandler {
1558
1671
  // Claude Code SDK Handlers
1559
1672
  // ============================================================================
1560
1673
  async handleClaudeStart(message, peerAddress) {
1561
- const { prompt, cwd: payloadCwd, agent, allowedTools, systemPrompt, model, permissionMode, sandboxed, reasoningEffort, contextWindow } = message.payload;
1674
+ const { prompt, cwd: payloadCwd, agent, allowedTools, systemPrompt, model, permissionMode, sandboxed, reasoningEffort, contextWindow, attachmentIds } = message.payload;
1562
1675
  const legacyProvider = message.payload.provider;
1563
1676
  const cwd = payloadCwd || this.getClientRepoPath(peerAddress);
1564
- const streamId = generateMessageId();
1565
1677
  const resolvedAgent = agent ?? (legacyProvider === 'codex-mcp' ? 'codex' : legacyProvider ? 'claude-code' : undefined);
1566
1678
  // Coerce a Codex model the user's account doesn't actually support
1567
1679
  // (e.g. they kept `claude-opus-4-7` selected before switching to a
@@ -1569,12 +1681,24 @@ export class MessageHandler {
1569
1681
  // turn/start with `BadRequest`. Pass-through for non-codex agents and
1570
1682
  // when the cache hasn't loaded yet.
1571
1683
  const validatedModel = this.validateCodexModel(model, resolvedAgent ?? 'claude-code');
1572
- console.log(`[agent:start] agent=${resolvedAgent ?? 'default'} model=${validatedModel ?? 'default'}${validatedModel !== model ? ` (coerced from ${model})` : ''} cwd=${cwd} prompt=${prompt.slice(0, 80)}${sandboxed ? ' [sandboxed]' : ''}`);
1684
+ console.log(`[agent:start] agent=${resolvedAgent ?? 'default'} model=${validatedModel ?? 'default'}${validatedModel !== model ? ` (coerced from ${model})` : ''} cwd=${cwd} prompt=${prompt.slice(0, 80)}${sandboxed ? ' [sandboxed]' : ''}${attachmentIds && attachmentIds.length > 0 ? ` [+${attachmentIds.length} attachments]` : ''}`);
1685
+ let attachments;
1686
+ if (attachmentIds && attachmentIds.length > 0) {
1687
+ try {
1688
+ attachments = this.attachmentStaging.consume(peerAddress, attachmentIds);
1689
+ }
1690
+ catch (error) {
1691
+ const code = error.code ?? 'attachment_error';
1692
+ const text = error instanceof Error ? error.message : 'attachment resolve failed';
1693
+ const response = createMessage('claude:start:response', { success: false, error: `${code}: ${text}` });
1694
+ response.id = message.id;
1695
+ return response;
1696
+ }
1697
+ }
1573
1698
  try {
1574
1699
  const sessionId = await this.claudeService.startSession({
1575
1700
  prompt,
1576
1701
  cwd,
1577
- streamId,
1578
1702
  agent: resolvedAgent,
1579
1703
  allowedTools,
1580
1704
  systemPrompt,
@@ -1583,6 +1707,7 @@ export class MessageHandler {
1583
1707
  sandboxed,
1584
1708
  reasoningEffort,
1585
1709
  contextWindow,
1710
+ attachments,
1586
1711
  });
1587
1712
  console.log(`[agent:start] session created: ${sessionId} agent=${resolvedAgent ?? 'default'}`);
1588
1713
  // Register in session history
@@ -1612,7 +1737,7 @@ export class MessageHandler {
1612
1737
  contextWindow,
1613
1738
  });
1614
1739
  this.onHistoryUpdated?.(cwd, registry.getEntry(cwd, sessionId), 'upsert');
1615
- const response = createMessage('claude:start:response', { success: true, sessionId, streamId });
1740
+ const response = createMessage('claude:start:response', { success: true, sessionId });
1616
1741
  response.id = message.id;
1617
1742
  return response;
1618
1743
  }
@@ -1624,20 +1749,32 @@ export class MessageHandler {
1624
1749
  }
1625
1750
  }
1626
1751
  async handleClaudeResume(message, peerAddress) {
1627
- const { sessionId: requestedId, prompt, cwd: payloadCwd, agent } = message.payload;
1752
+ const { sessionId: requestedId, prompt, cwd: payloadCwd, agent, attachmentIds } = message.payload;
1628
1753
  const legacyProvider = message.payload.provider;
1629
1754
  const cwd = payloadCwd || this.getClientRepoPath(peerAddress);
1630
- const streamId = generateMessageId();
1631
1755
  const resolvedAgent = agent ?? (legacyProvider === 'codex-mcp' ? 'codex' : legacyProvider ? 'claude-code' : undefined);
1632
1756
  const activeCfg = this.claudeService.getSessionConfig(requestedId);
1633
- console.log(`[agent:resume] session=${requestedId} agent=${resolvedAgent ?? 'stored'} model=${activeCfg.model ?? 'default'} cwd=${cwd} prompt=${prompt.slice(0, 80)}`);
1757
+ console.log(`[agent:resume] session=${requestedId} agent=${resolvedAgent ?? 'stored'} model=${activeCfg.model ?? 'default'} cwd=${cwd} prompt=${prompt.slice(0, 80)}${attachmentIds && attachmentIds.length > 0 ? ` [+${attachmentIds.length} attachments]` : ''}`);
1758
+ let attachments;
1759
+ if (attachmentIds && attachmentIds.length > 0) {
1760
+ try {
1761
+ attachments = this.attachmentStaging.consume(peerAddress, attachmentIds);
1762
+ }
1763
+ catch (error) {
1764
+ const code = error.code ?? 'attachment_error';
1765
+ const text = error instanceof Error ? error.message : 'attachment resolve failed';
1766
+ const response = createMessage('claude:resume:response', { success: false, error: `${code}: ${text}` });
1767
+ response.id = message.id;
1768
+ return response;
1769
+ }
1770
+ }
1634
1771
  try {
1635
1772
  const actualSessionId = await this.claudeService.resumeSession({
1636
1773
  sessionId: requestedId,
1637
1774
  prompt,
1638
1775
  cwd,
1639
- streamId,
1640
1776
  agent: resolvedAgent,
1777
+ attachments,
1641
1778
  });
1642
1779
  console.log(`[agent:resume] session resumed: ${actualSessionId}`);
1643
1780
  getEventStore().record({
@@ -1659,7 +1796,7 @@ export class MessageHandler {
1659
1796
  gitBranch,
1660
1797
  });
1661
1798
  this.onHistoryUpdated?.(cwd, registry.getEntry(cwd, actualSessionId), 'upsert');
1662
- const response = createMessage('claude:resume:response', { success: true, sessionId: actualSessionId, streamId });
1799
+ const response = createMessage('claude:resume:response', { success: true, sessionId: actualSessionId });
1663
1800
  response.id = message.id;
1664
1801
  return response;
1665
1802
  }
@@ -1712,6 +1849,11 @@ export class MessageHandler {
1712
1849
  }
1713
1850
  }
1714
1851
  const closed = this.claudeService.closeSession(sessionId);
1852
+ // Drop persisted attachment bytes for this session — fire-and-forget;
1853
+ // a stuck rm shouldn't block the response.
1854
+ removeSessionAttachments(sessionId).catch((err) => {
1855
+ console.error(`[agent:end-task] removeSessionAttachments failed session=${sessionId}:`, err);
1856
+ });
1715
1857
  const success = closed || archived;
1716
1858
  console.log(`[agent:end-task] session=${sessionId} closed=${closed} archived=${archived} cwd=${cwd ?? 'unknown'}`);
1717
1859
  const response = createMessage('claude:end-task:response', { success, error: success ? undefined : 'Session not found' });
@@ -1846,6 +1988,42 @@ export class MessageHandler {
1846
1988
  response.id = message.id;
1847
1989
  return response;
1848
1990
  }
1991
+ /**
1992
+ * Stamp `lastReadAt` on a registry entry and broadcast the update so every
1993
+ * connected PWA client converges on the same read state. The "unread" mark
1994
+ * lives server-side under the user's group identity (see
1995
+ * `docs/guidelines/sync-security.en.md`) — there's no per-device scoping,
1996
+ * so reading on the phone clears the badge on the desktop too.
1997
+ *
1998
+ * Idempotent: late-arriving / out-of-order viewedAt values that are older
1999
+ * than the persisted timestamp are dropped (max-wins) so a slow tab can't
2000
+ * undo a fresh read from a faster one.
2001
+ */
2002
+ handleMarkRead(message) {
2003
+ const { sessionId, cwd, viewedAt } = message.payload;
2004
+ const stamp = typeof viewedAt === 'number' ? viewedAt : Date.now();
2005
+ const existing = getSessionRegistry().getEntry(cwd, sessionId);
2006
+ if (!existing) {
2007
+ const failed = createMessage('session:mark-read:response', { success: false, error: 'Entry not found' });
2008
+ failed.id = message.id;
2009
+ return failed;
2010
+ }
2011
+ const nextLastReadAt = Math.max(existing.lastReadAt ?? 0, stamp);
2012
+ let entry = existing;
2013
+ if (nextLastReadAt !== existing.lastReadAt) {
2014
+ const updated = getSessionRegistry().updateEntry(cwd, sessionId, { lastReadAt: nextLastReadAt });
2015
+ if (updated)
2016
+ entry = updated;
2017
+ // Broadcast on /sessions/history so other PWA clients see the new
2018
+ // read state immediately. /sessions/active is also re-emitted via
2019
+ // sessionManager so the live-stats path stays consistent.
2020
+ this.onHistoryUpdated?.(cwd, entry, 'upsert');
2021
+ this.claudeService.emitSessionUpdate(sessionId);
2022
+ }
2023
+ const response = createMessage('session:mark-read:response', { success: true, lastReadAt: entry.lastReadAt });
2024
+ response.id = message.id;
2025
+ return response;
2026
+ }
1849
2027
  handleListProjectSummaries(message) {
1850
2028
  const allEntries = getSessionRegistry().getEntriesForProject();
1851
2029
  // Group by cwd
@@ -2166,6 +2344,40 @@ export class MessageHandler {
2166
2344
  response.id = message.id;
2167
2345
  return response;
2168
2346
  }
2347
+ // ── Attachment staging handlers ──────────────────────────────────────────
2348
+ /** Exposed so other agent paths (sessionManager) can resolve staged ids. */
2349
+ getAttachmentStaging() {
2350
+ return this.attachmentStaging;
2351
+ }
2352
+ handleAttachmentUpload(message, peerAddress) {
2353
+ try {
2354
+ const result = this.attachmentStaging.acceptChunk(peerAddress, message.payload);
2355
+ const response = createMessage('attachment:upload:response', result);
2356
+ response.id = message.id;
2357
+ return response;
2358
+ }
2359
+ catch (error) {
2360
+ const code = error.code ?? 'attachment_error';
2361
+ const text = error instanceof Error ? error.message : 'attachment upload failed';
2362
+ return this.createErrorResponse(message.id, code, text);
2363
+ }
2364
+ }
2365
+ handleAttachmentCancel(message, peerAddress) {
2366
+ const removed = this.attachmentStaging.cancel(peerAddress, message.payload.attachmentId);
2367
+ const response = createMessage('attachment:cancel:response', { removed });
2368
+ response.id = message.id;
2369
+ return response;
2370
+ }
2371
+ async handleAttachmentFetch(message) {
2372
+ const { sessionId, attachmentId } = message.payload;
2373
+ const attachment = await loadAttachment(sessionId, attachmentId);
2374
+ if (!attachment) {
2375
+ return this.createErrorResponse(message.id, 'attachment_not_found', `attachment ${attachmentId} not on disk for session ${sessionId}`);
2376
+ }
2377
+ const response = createMessage('attachment:fetch:response', { attachment });
2378
+ response.id = message.id;
2379
+ return response;
2380
+ }
2169
2381
  createErrorResponse(id, code, message) {
2170
2382
  const response = createMessage('error', { code, message });
2171
2383
  response.id = id;