@agimon-ai/po 0.0.0-alpha.114 → 0.0.0-alpha.116

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.
@@ -2,7 +2,7 @@
2
2
  "version": 1,
3
3
  "packages": {
4
4
  "@agimon-ai/po": {
5
- "version": "0.0.0-alpha.114",
5
+ "version": "0.0.0-alpha.116",
6
6
  "dependencies": [
7
7
  "@agimon-ai/po-approval",
8
8
  "@agimon-ai/po-artifact",
@@ -28,14 +28,14 @@
28
28
  ]
29
29
  },
30
30
  "@agimon-ai/po-approval": {
31
- "version": "0.0.0-alpha.113",
31
+ "version": "0.0.0-alpha.115",
32
32
  "dependencies": [
33
33
  "@agimon-ai/po-core",
34
34
  "@agimon-ai/po-web-components"
35
35
  ]
36
36
  },
37
37
  "@agimon-ai/po-artifact": {
38
- "version": "0.0.0-alpha.112",
38
+ "version": "0.0.0-alpha.114",
39
39
  "dependencies": [
40
40
  "@agimon-ai/po-core",
41
41
  "@agimon-ai/po-web-components",
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "@agimon-ai/po-author": {
46
- "version": "0.0.0-alpha.113",
46
+ "version": "0.0.0-alpha.115",
47
47
  "dependencies": [
48
48
  "@agimon-ai/po-artifact",
49
49
  "@agimon-ai/po-core",
@@ -53,7 +53,7 @@
53
53
  ]
54
54
  },
55
55
  "@agimon-ai/po-autocompact": {
56
- "version": "0.0.0-alpha.113",
56
+ "version": "0.0.0-alpha.115",
57
57
  "dependencies": [
58
58
  "@agimon-ai/po-artifact",
59
59
  "@agimon-ai/po-config",
@@ -62,24 +62,25 @@
62
62
  ]
63
63
  },
64
64
  "@agimon-ai/po-autostop": {
65
- "version": "0.0.0-alpha.113",
65
+ "version": "0.0.0-alpha.115",
66
66
  "dependencies": [
67
67
  "@agimon-ai/po-core"
68
68
  ]
69
69
  },
70
70
  "@agimon-ai/po-browse": {
71
- "version": "0.0.1-alpha.111",
71
+ "version": "0.0.1-alpha.113",
72
72
  "dependencies": [
73
+ "@agimon-ai/po-config",
73
74
  "@agimon-ai/po-core",
74
75
  "@agimon-ai/po-web-components"
75
76
  ]
76
77
  },
77
78
  "@agimon-ai/po-build": {
78
- "version": "0.0.0-alpha.113",
79
+ "version": "0.0.0-alpha.115",
79
80
  "dependencies": []
80
81
  },
81
82
  "@agimon-ai/po-cache": {
82
- "version": "0.0.0-alpha.113",
83
+ "version": "0.0.0-alpha.115",
83
84
  "dependencies": [
84
85
  "@agimon-ai/po-config",
85
86
  "@agimon-ai/po-core",
@@ -87,7 +88,7 @@
87
88
  ]
88
89
  },
89
90
  "@agimon-ai/po-calendar": {
90
- "version": "0.0.1-alpha.72",
91
+ "version": "0.0.1-alpha.74",
91
92
  "dependencies": [
92
93
  "@agimon-ai/po-config",
93
94
  "@agimon-ai/po-core",
@@ -99,11 +100,11 @@
99
100
  ]
100
101
  },
101
102
  "@agimon-ai/po-claude-bridge": {
102
- "version": "0.1.1-alpha.93",
103
+ "version": "0.1.1-alpha.95",
103
104
  "dependencies": []
104
105
  },
105
106
  "@agimon-ai/po-codemode": {
106
- "version": "0.0.1-alpha.86",
107
+ "version": "0.0.1-alpha.88",
107
108
  "dependencies": [
108
109
  "@agimon-ai/po-config",
109
110
  "@agimon-ai/po-core",
@@ -111,7 +112,7 @@
111
112
  ]
112
113
  },
113
114
  "@agimon-ai/po-computer-use": {
114
- "version": "0.0.0-alpha.113",
115
+ "version": "0.0.0-alpha.115",
115
116
  "dependencies": [
116
117
  "@agimon-ai/po-config",
117
118
  "@agimon-ai/po-core",
@@ -121,26 +122,26 @@
121
122
  ]
122
123
  },
123
124
  "@agimon-ai/po-config": {
124
- "version": "0.0.0-alpha.113",
125
+ "version": "0.0.0-alpha.115",
125
126
  "dependencies": [
126
127
  "@agimon-ai/po-core",
127
128
  "@agimon-ai/po-web-components"
128
129
  ]
129
130
  },
130
131
  "@agimon-ai/po-core": {
131
- "version": "0.0.0-alpha.113",
132
+ "version": "0.0.0-alpha.115",
132
133
  "dependencies": [
133
134
  "@agimon-ai/po-web-security"
134
135
  ]
135
136
  },
136
137
  "@agimon-ai/po-db": {
137
- "version": "0.0.0-alpha.113",
138
+ "version": "0.0.0-alpha.115",
138
139
  "dependencies": [
139
140
  "@agimon-ai/po-core"
140
141
  ]
141
142
  },
142
143
  "@agimon-ai/po-domain": {
143
- "version": "0.0.0-alpha.113",
144
+ "version": "0.0.0-alpha.115",
144
145
  "dependencies": [
145
146
  "@agimon-ai/po-config",
146
147
  "@agimon-ai/po-core",
@@ -150,7 +151,7 @@
150
151
  ]
151
152
  },
152
153
  "@agimon-ai/po-edit": {
153
- "version": "0.0.0-alpha.113",
154
+ "version": "0.0.0-alpha.115",
154
155
  "dependencies": [
155
156
  "@agimon-ai/po-core",
156
157
  "@agimon-ai/po-hashline",
@@ -159,7 +160,7 @@
159
160
  ]
160
161
  },
161
162
  "@agimon-ai/po-file-edit": {
162
- "version": "0.0.0-alpha.113",
163
+ "version": "0.0.0-alpha.115",
163
164
  "dependencies": [
164
165
  "@agimon-ai/po-config",
165
166
  "@agimon-ai/po-core",
@@ -169,7 +170,7 @@
169
170
  ]
170
171
  },
171
172
  "@agimon-ai/po-git": {
172
- "version": "0.0.0-alpha.113",
173
+ "version": "0.0.0-alpha.115",
173
174
  "dependencies": [
174
175
  "@agimon-ai/po-core",
175
176
  "@agimon-ai/po-session",
@@ -178,7 +179,7 @@
178
179
  ]
179
180
  },
180
181
  "@agimon-ai/po-goal": {
181
- "version": "0.0.0-alpha.113",
182
+ "version": "0.0.0-alpha.115",
182
183
  "dependencies": [
183
184
  "@agimon-ai/po-core",
184
185
  "@agimon-ai/po-minor-mode",
@@ -187,7 +188,7 @@
187
188
  ]
188
189
  },
189
190
  "@agimon-ai/po-grep": {
190
- "version": "0.0.0-alpha.113",
191
+ "version": "0.0.0-alpha.115",
191
192
  "dependencies": [
192
193
  "@agimon-ai/po-core",
193
194
  "@agimon-ai/po-hashline",
@@ -196,11 +197,11 @@
196
197
  ]
197
198
  },
198
199
  "@agimon-ai/po-hashline": {
199
- "version": "0.0.0-alpha.113",
200
+ "version": "0.0.0-alpha.115",
200
201
  "dependencies": []
201
202
  },
202
203
  "@agimon-ai/po-help": {
203
- "version": "0.0.0-alpha.113",
204
+ "version": "0.0.0-alpha.115",
204
205
  "dependencies": [
205
206
  "@agimon-ai/po-core",
206
207
  "@agimon-ai/po-minor-mode",
@@ -209,14 +210,14 @@
209
210
  ]
210
211
  },
211
212
  "@agimon-ai/po-identity": {
212
- "version": "0.0.2-alpha.60",
213
+ "version": "0.0.2-alpha.62",
213
214
  "dependencies": [
214
215
  "@agimon-ai/po-core",
215
216
  "@agimon-ai/po-web-security"
216
217
  ]
217
218
  },
218
219
  "@agimon-ai/po-image-gen": {
219
- "version": "0.0.0-alpha.112",
220
+ "version": "0.0.0-alpha.114",
220
221
  "dependencies": [
221
222
  "@agimon-ai/po-core",
222
223
  "@agimon-ai/po-web-components",
@@ -224,7 +225,7 @@
224
225
  ]
225
226
  },
226
227
  "@agimon-ai/po-ios": {
227
- "version": "0.0.1-alpha.48",
228
+ "version": "0.0.1-alpha.50",
228
229
  "dependencies": [
229
230
  "@agimon-ai/po-artifact",
230
231
  "@agimon-ai/po-config",
@@ -234,7 +235,7 @@
234
235
  ]
235
236
  },
236
237
  "@agimon-ai/po-log": {
237
- "version": "0.0.0-alpha.113",
238
+ "version": "0.0.0-alpha.115",
238
239
  "dependencies": [
239
240
  "@agimon-ai/po-core",
240
241
  "@agimon-ai/po-web-components",
@@ -242,7 +243,7 @@
242
243
  ]
243
244
  },
244
245
  "@agimon-ai/po-major-mode": {
245
- "version": "0.0.0-alpha.113",
246
+ "version": "0.0.0-alpha.115",
246
247
  "dependencies": [
247
248
  "@agimon-ai/po-config",
248
249
  "@agimon-ai/po-core",
@@ -250,7 +251,7 @@
250
251
  ]
251
252
  },
252
253
  "@agimon-ai/po-mcp": {
253
- "version": "0.0.0-alpha.113",
254
+ "version": "0.0.0-alpha.115",
254
255
  "dependencies": [
255
256
  "@agimon-ai/po-config",
256
257
  "@agimon-ai/po-core",
@@ -261,43 +262,44 @@
261
262
  ]
262
263
  },
263
264
  "@agimon-ai/po-mcp-proxy": {
264
- "version": "0.0.1-alpha.92",
265
+ "version": "0.0.1-alpha.94",
265
266
  "dependencies": []
266
267
  },
267
268
  "@agimon-ai/po-minor-mode": {
268
- "version": "0.0.0-alpha.113",
269
+ "version": "0.0.0-alpha.115",
269
270
  "dependencies": [
270
271
  "@agimon-ai/po-core"
271
272
  ]
272
273
  },
273
274
  "@agimon-ai/po-model-guidance": {
274
- "version": "0.0.0-alpha.113",
275
+ "version": "0.0.0-alpha.115",
275
276
  "dependencies": [
276
277
  "@agimon-ai/po-config",
277
278
  "@agimon-ai/po-core"
278
279
  ]
279
280
  },
280
281
  "@agimon-ai/po-native-components": {
281
- "version": "0.0.0-alpha.113",
282
+ "version": "0.0.0-alpha.115",
282
283
  "dependencies": [
283
284
  "@agimon-ai/po-web-components"
284
285
  ]
285
286
  },
286
287
  "@agimon-ai/po-network": {
287
- "version": "0.0.2-alpha.60",
288
+ "version": "0.0.2-alpha.62",
288
289
  "dependencies": [
289
290
  "@agimon-ai/po-core",
290
291
  "@agimon-ai/po-web-security"
291
292
  ]
292
293
  },
293
294
  "@agimon-ai/po-notification": {
294
- "version": "0.0.0-alpha.113",
295
+ "version": "0.0.0-alpha.115",
295
296
  "dependencies": [
297
+ "@agimon-ai/po-autostop",
296
298
  "@agimon-ai/po-core"
297
299
  ]
298
300
  },
299
301
  "@agimon-ai/po-plan": {
300
- "version": "0.0.0-alpha.113",
302
+ "version": "0.0.0-alpha.115",
301
303
  "dependencies": [
302
304
  "@agimon-ai/po-config",
303
305
  "@agimon-ai/po-core",
@@ -307,7 +309,7 @@
307
309
  ]
308
310
  },
309
311
  "@agimon-ai/po-profile": {
310
- "version": "0.0.0-alpha.113",
312
+ "version": "0.0.0-alpha.115",
311
313
  "dependencies": [
312
314
  "@agimon-ai/po-config",
313
315
  "@agimon-ai/po-core",
@@ -315,7 +317,7 @@
315
317
  ]
316
318
  },
317
319
  "@agimon-ai/po-prompt": {
318
- "version": "0.0.0-alpha.113",
320
+ "version": "0.0.0-alpha.115",
319
321
  "dependencies": [
320
322
  "@agimon-ai/po-core",
321
323
  "@agimon-ai/po-web-components",
@@ -323,7 +325,7 @@
323
325
  ]
324
326
  },
325
327
  "@agimon-ai/po-read": {
326
- "version": "0.0.0-alpha.113",
328
+ "version": "0.0.0-alpha.115",
327
329
  "dependencies": [
328
330
  "@agimon-ai/po-config",
329
331
  "@agimon-ai/po-core",
@@ -333,7 +335,7 @@
333
335
  ]
334
336
  },
335
337
  "@agimon-ai/po-runner": {
336
- "version": "0.0.0-alpha.113",
338
+ "version": "0.0.0-alpha.115",
337
339
  "dependencies": [
338
340
  "@agimon-ai/po-core",
339
341
  "@agimon-ai/po-ui",
@@ -342,13 +344,13 @@
342
344
  ]
343
345
  },
344
346
  "@agimon-ai/po-sandbox": {
345
- "version": "0.0.0-alpha.113",
347
+ "version": "0.0.0-alpha.115",
346
348
  "dependencies": [
347
349
  "@agimon-ai/po-core"
348
350
  ]
349
351
  },
350
352
  "@agimon-ai/po-session": {
351
- "version": "0.0.0-alpha.113",
353
+ "version": "0.0.0-alpha.115",
352
354
  "dependencies": [
353
355
  "@agimon-ai/po-config",
354
356
  "@agimon-ai/po-core",
@@ -356,7 +358,7 @@
356
358
  ]
357
359
  },
358
360
  "@agimon-ai/po-skill": {
359
- "version": "0.0.0-alpha.113",
361
+ "version": "0.0.0-alpha.115",
360
362
  "dependencies": [
361
363
  "@agimon-ai/po-config",
362
364
  "@agimon-ai/po-core",
@@ -365,7 +367,7 @@
365
367
  ]
366
368
  },
367
369
  "@agimon-ai/po-style-system": {
368
- "version": "0.0.0-alpha.113",
370
+ "version": "0.0.0-alpha.115",
369
371
  "dependencies": [
370
372
  "@agimon-ai/po-core",
371
373
  "@agimon-ai/po-web-components",
@@ -373,7 +375,7 @@
373
375
  ]
374
376
  },
375
377
  "@agimon-ai/po-task": {
376
- "version": "0.0.0-alpha.113",
378
+ "version": "0.0.0-alpha.115",
377
379
  "dependencies": [
378
380
  "@agimon-ai/po-core",
379
381
  "@agimon-ai/po-ui",
@@ -381,7 +383,7 @@
381
383
  ]
382
384
  },
383
385
  "@agimon-ai/po-team": {
384
- "version": "0.0.0-alpha.113",
386
+ "version": "0.0.0-alpha.115",
385
387
  "dependencies": [
386
388
  "@agimon-ai/po-cache",
387
389
  "@agimon-ai/po-config",
@@ -393,7 +395,7 @@
393
395
  ]
394
396
  },
395
397
  "@agimon-ai/po-template-advanced": {
396
- "version": "0.0.0-alpha.113",
398
+ "version": "0.0.0-alpha.115",
397
399
  "dependencies": [
398
400
  "@agimon-ai/po-core",
399
401
  "@agimon-ai/po-web-components",
@@ -401,7 +403,7 @@
401
403
  ]
402
404
  },
403
405
  "@agimon-ai/po-template-elegant": {
404
- "version": "0.0.0-alpha.113",
406
+ "version": "0.0.0-alpha.115",
405
407
  "dependencies": [
406
408
  "@agimon-ai/po-core",
407
409
  "@agimon-ai/po-web-components",
@@ -409,7 +411,7 @@
409
411
  ]
410
412
  },
411
413
  "@agimon-ai/po-ui": {
412
- "version": "0.0.0-alpha.113",
414
+ "version": "0.0.0-alpha.115",
413
415
  "dependencies": [
414
416
  "@agimon-ai/po-core",
415
417
  "@agimon-ai/po-hashline",
@@ -418,7 +420,7 @@
418
420
  ]
419
421
  },
420
422
  "@agimon-ai/po-user-feedback": {
421
- "version": "0.0.0-alpha.113",
423
+ "version": "0.0.0-alpha.115",
422
424
  "dependencies": [
423
425
  "@agimon-ai/po-core",
424
426
  "@agimon-ai/po-minor-mode",
@@ -427,7 +429,7 @@
427
429
  ]
428
430
  },
429
431
  "@agimon-ai/po-vault": {
430
- "version": "0.0.0-alpha.113",
432
+ "version": "0.0.0-alpha.115",
431
433
  "dependencies": [
432
434
  "@agimon-ai/po-core",
433
435
  "@agimon-ai/po-db",
@@ -436,7 +438,7 @@
436
438
  ]
437
439
  },
438
440
  "@agimon-ai/po-vibe-lint": {
439
- "version": "0.0.1-alpha.82",
441
+ "version": "0.0.1-alpha.84",
440
442
  "dependencies": [
441
443
  "@agimon-ai/po-config",
442
444
  "@agimon-ai/po-core",
@@ -446,11 +448,12 @@
446
448
  ]
447
449
  },
448
450
  "@agimon-ai/po-voice": {
449
- "version": "0.0.0-alpha.113",
451
+ "version": "0.0.0-alpha.115",
450
452
  "dependencies": [
451
453
  "@agimon-ai/po-config",
452
454
  "@agimon-ai/po-core",
453
455
  "@agimon-ai/po-minor-mode",
456
+ "@agimon-ai/po-notification",
454
457
  "@agimon-ai/po-session",
455
458
  "@agimon-ai/po-ui",
456
459
  "@agimon-ai/po-web-components",
@@ -458,11 +461,11 @@
458
461
  ]
459
462
  },
460
463
  "@agimon-ai/po-web-components": {
461
- "version": "0.0.0-alpha.113",
464
+ "version": "0.0.0-alpha.115",
462
465
  "dependencies": []
463
466
  },
464
467
  "@agimon-ai/po-web-dashboard": {
465
- "version": "0.0.0-alpha.113",
468
+ "version": "0.0.0-alpha.115",
466
469
  "dependencies": [
467
470
  "@agimon-ai/po-core",
468
471
  "@agimon-ai/po-web-components",
@@ -470,18 +473,18 @@
470
473
  ]
471
474
  },
472
475
  "@agimon-ai/po-web-search": {
473
- "version": "0.0.0-alpha.112",
476
+ "version": "0.0.0-alpha.114",
474
477
  "dependencies": [
475
478
  "@agimon-ai/po-core",
476
479
  "@agimon-ai/po-web-components"
477
480
  ]
478
481
  },
479
482
  "@agimon-ai/po-web-security": {
480
- "version": "0.0.0-alpha.113",
483
+ "version": "0.0.0-alpha.115",
481
484
  "dependencies": []
482
485
  },
483
486
  "@agimon-ai/po-workflow": {
484
- "version": "0.0.0-alpha.113",
487
+ "version": "0.0.0-alpha.115",
485
488
  "dependencies": [
486
489
  "@agimon-ai/po-core",
487
490
  "@agimon-ai/po-ui",
package/dist/package.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.0-alpha.114";
2
+ var version = "0.0.0-alpha.116";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.0-alpha.114";
2
+ var version = "0.0.0-alpha.116";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -1,9 +1,7 @@
1
1
  let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
2
2
  let node_crypto = require("node:crypto");
3
- let _agimon_ai_po_core_backgroundWork = require("@agimon-ai/po-core/backgroundWork");
4
3
  let _agimon_ai_po_core_notification = require("@agimon-ai/po-core/notification");
5
4
  let _agimon_ai_po_notification = require("@agimon-ai/po-notification");
6
- let _agimon_ai_po_autostop = require("@agimon-ai/po-autostop");
7
5
  //#region src/builders/server/nativePush.ts
8
6
  const MAX_CREDENTIAL_BYTES = 65536;
9
7
  const FANOUT = 4;
@@ -117,17 +115,15 @@ function subscribeNativePush(hub, remote, sender, notice) {
117
115
  try {
118
116
  const state = await hub.sessionService.readExecutionState?.(sessionId);
119
117
  if (!current() || remote.state().status !== "on") return;
120
- if (!state?.backgroundWork) throw new Error("Execution state unavailable");
121
- const holds = held.holdingSince !== void 0 && Date.now() - held.holdingSince >= _agimon_ai_po_core_backgroundWork.BACKGROUND_RUNNER_DEADLINE_MS ? _agimon_ai_po_core_backgroundWork.holdsSettledSessionAfterRunnerDeadline : _agimon_ai_po_core_backgroundWork.holdsSettledSession;
122
- const { items, errors } = state.backgroundWork;
123
- if (items.some(holds) || errors.some((error) => _agimon_ai_po_core_backgroundWork.BACKGROUND_WORK_HOLDING_PROVIDERS.includes(error.provider))) {
124
- held.holdingSince ??= Date.now();
125
- later(sessionId, held, run, _agimon_ai_po_autostop.DEFAULT_AUTO_STOP_DELAYS.cooldownMs);
118
+ const decision = (0, _agimon_ai_po_notification.decideSessionFinished)(state, held.holdingSince, Date.now());
119
+ if (decision.action === "unavailable") throw new Error("Execution state unavailable");
120
+ if (decision.action === "hold") {
121
+ held.holdingSince = decision.holdingSince;
122
+ later(sessionId, held, run, decision.delayMs);
126
123
  return;
127
124
  }
128
- const decision = (0, _agimon_ai_po_autostop.decideOnRecheck)(state, _agimon_ai_po_autostop.DEFAULT_AUTO_STOP_DELAYS);
129
- if (decision.action === _agimon_ai_po_autostop.AUTO_STOP_ACTION.recheck) later(sessionId, held, run, decision.delayMs);
130
- else if (decision.action === _agimon_ai_po_autostop.AUTO_STOP_ACTION.shutdown) enqueue((0, _agimon_ai_po_core_notification.createPoNotificationEntryData)((0, _agimon_ai_po_notification.settledNotification)({
125
+ if (decision.action === "recheck") later(sessionId, held, run, decision.delayMs);
126
+ else if (decision.action === "finished") enqueue((0, _agimon_ai_po_core_notification.createPoNotificationEntryData)((0, _agimon_ai_po_notification.settledNotification)({
131
127
  cwd: held.session.cwd,
132
128
  sessionName: held.session.name
133
129
  })), sessionId);
@@ -158,7 +154,7 @@ function subscribeNativePush(hub, remote, sender, notice) {
158
154
  held.dialogs.clear();
159
155
  } else if (session.phase === "idle" && held.running) {
160
156
  held.running = false;
161
- later(session.id, held, held.run, _agimon_ai_po_autostop.DEFAULT_AUTO_STOP_DELAYS.cooldownMs);
157
+ later(session.id, held, held.run, _agimon_ai_po_notification.SESSION_FINISHED_COOLDOWN_MS);
162
158
  }
163
159
  };
164
160
  for (const session of hub.snapshot()) track(session);
@@ -1 +1 @@
1
- {"version":3,"file":"nativePush.cjs","names":["readBoundedFile","BACKGROUND_CONTEXT","createPrivateKey","BACKGROUND_RUNNER_DEADLINE_MS","holdsSettledSessionAfterRunnerDeadline","holdsSettledSession","BACKGROUND_WORK_HOLDING_PROVIDERS","DEFAULT_AUTO_STOP_DELAYS","decideOnRecheck","AUTO_STOP_ACTION","createPoNotificationEntryData","settledNotification","PO_NOTIFICATION_ENTRY_TYPE","isPoNotificationEntryData","notificationBody","attentionNotification"],"sources":["../../../../src/builders/server/nativePush.ts"],"sourcesContent":["import { createPrivateKey } from 'node:crypto';\n\nimport { AUTO_STOP_ACTION, DEFAULT_AUTO_STOP_DELAYS, decideOnRecheck } from '@agimon-ai/po-autostop';\nimport {\n BACKGROUND_RUNNER_DEADLINE_MS,\n BACKGROUND_WORK_HOLDING_PROVIDERS,\n holdsSettledSession,\n holdsSettledSessionAfterRunnerDeadline,\n} from '@agimon-ai/po-core/backgroundWork';\nimport type { HeadlessHub } from '@agimon-ai/po-core/headlessHub';\nimport {\n createPoNotificationEntryData,\n isPoNotificationEntryData,\n PO_NOTIFICATION_ENTRY_TYPE,\n} from '@agimon-ai/po-core/notification';\nimport { BACKGROUND_CONTEXT, readBoundedFile, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { RemoteRuntime } from '@agimon-ai/po-core/remoteRuntime';\nimport {\n attentionNotification,\n createNativePushSender,\n notificationBody,\n settledNotification,\n} from '@agimon-ai/po-notification';\n\ntype Sender = ReturnType<typeof createNativePushSender>;\ntype Credentials = Parameters<typeof createNativePushSender>[0];\ntype Remote = RemoteRuntime['remote'];\ntype Registration = Parameters<Sender['send']>[0];\nconst MAX_CREDENTIAL_BYTES = 64 * 1024;\nconst FANOUT = 4;\nconst MAX_PENDING = 64;\nconst SEND_TIMEOUT_MS = 10_000;\n\n/** Startup-only host credential loading. Diagnostics deliberately exclude paths and caught errors. */\nexport async function loadNativePushCredentials(\n environment: NodeJS.ProcessEnv,\n fs: FileSystem,\n notice: (message: string) => void,\n): Promise<Credentials> {\n const credentials: Credentials = { timeoutMs: SEND_TIMEOUT_MS };\n const read = async (file: string) =>\n new TextDecoder().decode(await readBoundedFile(fs, file, MAX_CREDENTIAL_BYTES, BACKGROUND_CONTEXT));\n try {\n const { PO_APNS_KEY_FILE: file, PO_APNS_KEY_ID: keyId, PO_APNS_TEAM_ID: teamId } = environment;\n if (!file || !keyId || !teamId || !/^[A-Z0-9]{10}$/u.test(keyId) || !/^[A-Z0-9]{10}$/u.test(teamId))\n throw new Error('Unavailable credentials');\n const key = await read(file);\n const parsed = createPrivateKey(key);\n if (parsed.asymmetricKeyType !== 'ec' || parsed.asymmetricKeyDetails?.namedCurve !== 'prime256v1')\n throw new Error('Invalid key');\n credentials.apns = { key, keyId, teamId };\n } catch {\n notice('Native push APNs disabled: missing or invalid credentials.');\n }\n try {\n const file = environment.PO_FCM_SERVICE_ACCOUNT_FILE;\n if (!file) throw new Error('Unavailable credentials');\n const value: unknown = JSON.parse(await read(file));\n if (!value || typeof value !== 'object') throw new Error('Invalid account');\n const account = value as Record<string, unknown>;\n const { client_email: clientEmail, private_key: privateKey, project_id: projectId } = account;\n if (\n account.type !== 'service_account' ||\n typeof clientEmail !== 'string' ||\n !/^[^\\s@]+@[^\\s@]+$/u.test(clientEmail) ||\n typeof projectId !== 'string' ||\n !/^[a-z][a-z0-9-]{4,61}[a-z0-9]$/u.test(projectId) ||\n typeof privateKey !== 'string'\n )\n throw new Error('Invalid account');\n const parsed = createPrivateKey(privateKey);\n if (parsed.asymmetricKeyType !== 'rsa' || (parsed.asymmetricKeyDetails?.modulusLength ?? 0) < 2048)\n throw new Error('Invalid key');\n credentials.fcm = { clientEmail, privateKey, projectId };\n } catch {\n notice('Native push FCM disabled: missing or invalid credentials.');\n }\n return credentials;\n}\n\nfunction endpoint(registration: Registration): string {\n return JSON.stringify([\n registration.platform,\n registration.token,\n registration.hostRef ?? null,\n registration.platform === 'ios' ? registration.environment : null,\n ]);\n}\n\n/** One live subscription for the entire hub, with no transcript reads or socket dependency. */\nexport function subscribeNativePush(\n hub: Pick<HeadlessHub, 'onEvent' | 'snapshot' | 'sessionService'>,\n remote: Pick<Remote, 'state' | 'listNativePushRegistrations' | 'clearNativePushRegistration'>,\n sender: Sender,\n notice: (message: string) => void,\n): () => void {\n type Job = {\n owners: ReturnType<Remote['listNativePushRegistrations']>;\n notification: Parameters<Sender['send']>[1];\n sessionId: string;\n };\n const pending: Job[] = [];\n let active = 0;\n let closed = false;\n const pump = (): void => {\n while (!closed && active < FANOUT && pending.length > 0) {\n const job = pending.shift()!;\n if (remote.state().status !== 'on') continue;\n const current = remote.listNativePushRegistrations();\n const owners = job.owners.filter((owner) =>\n current.some(\n (held) =>\n held.notificationId === owner.notificationId &&\n endpoint(held.registration) === endpoint(owner.registration),\n ),\n );\n if (owners.length === 0) continue;\n active++;\n void sender\n .send(owners[0]!.registration, job.notification, job.sessionId)\n .then((result) => {\n if (!closed && result.invalid)\n for (const owner of owners) remote.clearNativePushRegistration(owner.notificationId, owner.registration);\n })\n .catch(() => notice('Native push delivery failed.'))\n .finally(() => {\n active--;\n pump();\n });\n }\n };\n const enqueue = (notification: Job['notification'] | undefined, sessionId: string): void => {\n if (!notification || closed || remote.state().status !== 'on') return;\n const groups = new Map<string, Job['owners']>();\n for (const owner of remote.listNativePushRegistrations()) {\n const key = endpoint(owner.registration);\n const group = groups.get(key);\n if (group) group.push(owner);\n else groups.set(key, [owner]);\n }\n for (const owners of groups.values()) {\n if (pending.length + active >= MAX_PENDING) break; // Best effort, never an outbox.\n pending.push({ owners, notification, sessionId });\n }\n pump();\n };\n type Session = ReturnType<HeadlessHub['snapshot']>[number];\n type Tracked = {\n session: Session;\n running: boolean;\n run: number;\n dialogs: Set<string>;\n warned: boolean;\n timer?: ReturnType<typeof setTimeout>;\n holdingSince?: number;\n };\n const tracked = new Map<string, Tracked>();\n const cancel = (held: Tracked): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = undefined;\n held.holdingSince = undefined;\n };\n const later = (sessionId: string, held: Tracked, run: number, delayMs: number): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = setTimeout(() => {\n held.timer = undefined;\n void finished(sessionId, held, run);\n }, delayMs);\n held.timer.unref?.();\n };\n /** Same completion test autostop applies before it closes a session: cooldown, holding work, then idle. */\n const finished = async (sessionId: string, held: Tracked, run: number): Promise<void> => {\n if (held.session.parentSessionId !== undefined || remote.state().status !== 'on') return;\n const current = () =>\n !closed && tracked.get(sessionId) === held && held.run === run && held.session.phase === 'idle';\n if (!current()) return;\n try {\n // Hub summaries do not maintain pendingMessageCount. Only the host knows its queue.\n const state = await hub.sessionService.readExecutionState?.(sessionId);\n if (!current() || remote.state().status !== 'on') return;\n // Unknown background work cannot prove the run finished.\n if (!state?.backgroundWork) throw new Error('Execution state unavailable');\n const expired =\n held.holdingSince !== undefined && Date.now() - held.holdingSince >= BACKGROUND_RUNNER_DEADLINE_MS;\n const holds = expired ? holdsSettledSessionAfterRunnerDeadline : holdsSettledSession;\n const { items, errors } = state.backgroundWork;\n if (items.some(holds) || errors.some((error) => BACKGROUND_WORK_HOLDING_PROVIDERS.includes(error.provider))) {\n held.holdingSince ??= Date.now();\n later(sessionId, held, run, DEFAULT_AUTO_STOP_DELAYS.cooldownMs);\n return;\n }\n const decision = decideOnRecheck(state, DEFAULT_AUTO_STOP_DELAYS);\n if (decision.action === AUTO_STOP_ACTION.recheck) later(sessionId, held, run, decision.delayMs);\n else if (decision.action === AUTO_STOP_ACTION.shutdown)\n enqueue(\n createPoNotificationEntryData(settledNotification({ cwd: held.session.cwd, sessionName: held.session.name })),\n sessionId,\n );\n } catch {\n // ponytail: mirrors without a state reader stay quiet; add a mirror reader for completion parity.\n if (current() && remote.state().status === 'on' && !held.warned) {\n held.warned = true;\n notice('Native push completion check unavailable.');\n }\n }\n };\n const track = (session: Session): void => {\n let held = tracked.get(session.id);\n if (!held) {\n held = { session, running: false, run: 0, dialogs: new Set(), warned: false };\n tracked.set(session.id, held);\n }\n held.session = session;\n if (session.phase === 'turn' && !held.running) {\n cancel(held);\n held.running = true;\n held.run++;\n held.dialogs.clear();\n } else if (session.phase === 'idle' && held.running) {\n held.running = false;\n later(session.id, held, held.run, DEFAULT_AUTO_STOP_DELAYS.cooldownMs);\n }\n };\n for (const session of hub.snapshot()) track(session);\n const unsubscribe = hub.onEvent((event) => {\n if (closed) return;\n if (event.kind === 'upsert') {\n track(event.session);\n return;\n }\n if (event.kind === 'removed') {\n const held = tracked.get(event.sessionId);\n if (held) cancel(held);\n tracked.delete(event.sessionId);\n return;\n }\n // Toasts stay in-app: subagent and background completions would otherwise buzz the phone for every run.\n // Attention notices are the exception: the person must act (an approval), so every session pushes.\n if (event.kind === 'frame' && event.frame.type === 'entry_appended') {\n const entry = event.frame.entry as { customType?: unknown; data?: unknown } | undefined;\n if (\n entry?.customType === PO_NOTIFICATION_ENTRY_TYPE &&\n isPoNotificationEntryData(entry.data) &&\n entry.data.attention\n )\n enqueue(\n {\n ...entry.data,\n body: notificationBody(entry.data.body),\n subtitle: tracked.get(event.sessionId)?.session.name ?? entry.data.subtitle,\n },\n event.sessionId,\n );\n return;\n }\n if (event.kind !== 'frame' || event.frame.type !== 'extension_ui_request') return;\n const { frame, sessionId } = event;\n const held = tracked.get(sessionId);\n if (\n !held ||\n held.session.parentSessionId !== undefined ||\n held.session.phase !== 'turn' ||\n typeof frame.method !== 'string' ||\n !['select', 'confirm', 'input', 'editor'].includes(frame.method) ||\n typeof frame.id !== 'string' ||\n !frame.id.trim() ||\n held.dialogs.has(frame.id)\n )\n return;\n held.dialogs.add(frame.id);\n const text = [frame.message, frame.title].find(\n (value): value is string => typeof value === 'string' && !!value.trim(),\n );\n enqueue(\n createPoNotificationEntryData({\n ...attentionNotification(notificationBody(text ?? 'A question is waiting for your answer.')),\n subtitle: held.session.name,\n }),\n sessionId,\n );\n });\n return () => {\n closed = true;\n pending.length = 0;\n for (const held of tracked.values()) cancel(held);\n tracked.clear();\n unsubscribe();\n sender.close();\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nativePush.cjs","names":["readBoundedFile","BACKGROUND_CONTEXT","createPrivateKey","decideSessionFinished","createPoNotificationEntryData","settledNotification","SESSION_FINISHED_COOLDOWN_MS","PO_NOTIFICATION_ENTRY_TYPE","isPoNotificationEntryData","notificationBody","attentionNotification"],"sources":["../../../../src/builders/server/nativePush.ts"],"sourcesContent":["import { createPrivateKey } from 'node:crypto';\n\nimport type { HeadlessHub } from '@agimon-ai/po-core/headlessHub';\nimport {\n createPoNotificationEntryData,\n isPoNotificationEntryData,\n PO_NOTIFICATION_ENTRY_TYPE,\n} from '@agimon-ai/po-core/notification';\nimport { BACKGROUND_CONTEXT, readBoundedFile, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { RemoteRuntime } from '@agimon-ai/po-core/remoteRuntime';\nimport {\n attentionNotification,\n createNativePushSender,\n decideSessionFinished,\n notificationBody,\n SESSION_FINISHED_COOLDOWN_MS,\n settledNotification,\n} from '@agimon-ai/po-notification';\n\ntype Sender = ReturnType<typeof createNativePushSender>;\ntype Credentials = Parameters<typeof createNativePushSender>[0];\ntype Remote = RemoteRuntime['remote'];\ntype Registration = Parameters<Sender['send']>[0];\nconst MAX_CREDENTIAL_BYTES = 64 * 1024;\nconst FANOUT = 4;\nconst MAX_PENDING = 64;\nconst SEND_TIMEOUT_MS = 10_000;\n\n/** Startup-only host credential loading. Diagnostics deliberately exclude paths and caught errors. */\nexport async function loadNativePushCredentials(\n environment: NodeJS.ProcessEnv,\n fs: FileSystem,\n notice: (message: string) => void,\n): Promise<Credentials> {\n const credentials: Credentials = { timeoutMs: SEND_TIMEOUT_MS };\n const read = async (file: string) =>\n new TextDecoder().decode(await readBoundedFile(fs, file, MAX_CREDENTIAL_BYTES, BACKGROUND_CONTEXT));\n try {\n const { PO_APNS_KEY_FILE: file, PO_APNS_KEY_ID: keyId, PO_APNS_TEAM_ID: teamId } = environment;\n if (!file || !keyId || !teamId || !/^[A-Z0-9]{10}$/u.test(keyId) || !/^[A-Z0-9]{10}$/u.test(teamId))\n throw new Error('Unavailable credentials');\n const key = await read(file);\n const parsed = createPrivateKey(key);\n if (parsed.asymmetricKeyType !== 'ec' || parsed.asymmetricKeyDetails?.namedCurve !== 'prime256v1')\n throw new Error('Invalid key');\n credentials.apns = { key, keyId, teamId };\n } catch {\n notice('Native push APNs disabled: missing or invalid credentials.');\n }\n try {\n const file = environment.PO_FCM_SERVICE_ACCOUNT_FILE;\n if (!file) throw new Error('Unavailable credentials');\n const value: unknown = JSON.parse(await read(file));\n if (!value || typeof value !== 'object') throw new Error('Invalid account');\n const account = value as Record<string, unknown>;\n const { client_email: clientEmail, private_key: privateKey, project_id: projectId } = account;\n if (\n account.type !== 'service_account' ||\n typeof clientEmail !== 'string' ||\n !/^[^\\s@]+@[^\\s@]+$/u.test(clientEmail) ||\n typeof projectId !== 'string' ||\n !/^[a-z][a-z0-9-]{4,61}[a-z0-9]$/u.test(projectId) ||\n typeof privateKey !== 'string'\n )\n throw new Error('Invalid account');\n const parsed = createPrivateKey(privateKey);\n if (parsed.asymmetricKeyType !== 'rsa' || (parsed.asymmetricKeyDetails?.modulusLength ?? 0) < 2048)\n throw new Error('Invalid key');\n credentials.fcm = { clientEmail, privateKey, projectId };\n } catch {\n notice('Native push FCM disabled: missing or invalid credentials.');\n }\n return credentials;\n}\n\nfunction endpoint(registration: Registration): string {\n return JSON.stringify([\n registration.platform,\n registration.token,\n registration.hostRef ?? null,\n registration.platform === 'ios' ? registration.environment : null,\n ]);\n}\n\n/** One live subscription for the entire hub, with no transcript reads or socket dependency. */\nexport function subscribeNativePush(\n hub: Pick<HeadlessHub, 'onEvent' | 'snapshot' | 'sessionService'>,\n remote: Pick<Remote, 'state' | 'listNativePushRegistrations' | 'clearNativePushRegistration'>,\n sender: Sender,\n notice: (message: string) => void,\n): () => void {\n type Job = {\n owners: ReturnType<Remote['listNativePushRegistrations']>;\n notification: Parameters<Sender['send']>[1];\n sessionId: string;\n };\n const pending: Job[] = [];\n let active = 0;\n let closed = false;\n const pump = (): void => {\n while (!closed && active < FANOUT && pending.length > 0) {\n const job = pending.shift()!;\n if (remote.state().status !== 'on') continue;\n const current = remote.listNativePushRegistrations();\n const owners = job.owners.filter((owner) =>\n current.some(\n (held) =>\n held.notificationId === owner.notificationId &&\n endpoint(held.registration) === endpoint(owner.registration),\n ),\n );\n if (owners.length === 0) continue;\n active++;\n void sender\n .send(owners[0]!.registration, job.notification, job.sessionId)\n .then((result) => {\n if (!closed && result.invalid)\n for (const owner of owners) remote.clearNativePushRegistration(owner.notificationId, owner.registration);\n })\n .catch(() => notice('Native push delivery failed.'))\n .finally(() => {\n active--;\n pump();\n });\n }\n };\n const enqueue = (notification: Job['notification'] | undefined, sessionId: string): void => {\n if (!notification || closed || remote.state().status !== 'on') return;\n const groups = new Map<string, Job['owners']>();\n for (const owner of remote.listNativePushRegistrations()) {\n const key = endpoint(owner.registration);\n const group = groups.get(key);\n if (group) group.push(owner);\n else groups.set(key, [owner]);\n }\n for (const owners of groups.values()) {\n if (pending.length + active >= MAX_PENDING) break; // Best effort, never an outbox.\n pending.push({ owners, notification, sessionId });\n }\n pump();\n };\n type Session = ReturnType<HeadlessHub['snapshot']>[number];\n type Tracked = {\n session: Session;\n running: boolean;\n run: number;\n dialogs: Set<string>;\n warned: boolean;\n timer?: ReturnType<typeof setTimeout>;\n holdingSince?: number;\n };\n const tracked = new Map<string, Tracked>();\n const cancel = (held: Tracked): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = undefined;\n held.holdingSince = undefined;\n };\n const later = (sessionId: string, held: Tracked, run: number, delayMs: number): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = setTimeout(() => {\n held.timer = undefined;\n void finished(sessionId, held, run);\n }, delayMs);\n held.timer.unref?.();\n };\n /** Same completion test autostop applies before it closes a session: cooldown, holding work, then idle. */\n const finished = async (sessionId: string, held: Tracked, run: number): Promise<void> => {\n if (held.session.parentSessionId !== undefined || remote.state().status !== 'on') return;\n const current = () =>\n !closed && tracked.get(sessionId) === held && held.run === run && held.session.phase === 'idle';\n if (!current()) return;\n try {\n // Hub summaries do not maintain pendingMessageCount. Only the host knows its queue.\n const state = await hub.sessionService.readExecutionState?.(sessionId);\n if (!current() || remote.state().status !== 'on') return;\n const decision = decideSessionFinished(state, held.holdingSince, Date.now());\n if (decision.action === 'unavailable') throw new Error('Execution state unavailable');\n if (decision.action === 'hold') {\n held.holdingSince = decision.holdingSince;\n later(sessionId, held, run, decision.delayMs);\n return;\n }\n if (decision.action === 'recheck') later(sessionId, held, run, decision.delayMs);\n else if (decision.action === 'finished')\n enqueue(\n createPoNotificationEntryData(settledNotification({ cwd: held.session.cwd, sessionName: held.session.name })),\n sessionId,\n );\n } catch {\n // ponytail: mirrors without a state reader stay quiet; add a mirror reader for completion parity.\n if (current() && remote.state().status === 'on' && !held.warned) {\n held.warned = true;\n notice('Native push completion check unavailable.');\n }\n }\n };\n const track = (session: Session): void => {\n let held = tracked.get(session.id);\n if (!held) {\n held = { session, running: false, run: 0, dialogs: new Set(), warned: false };\n tracked.set(session.id, held);\n }\n held.session = session;\n if (session.phase === 'turn' && !held.running) {\n cancel(held);\n held.running = true;\n held.run++;\n held.dialogs.clear();\n } else if (session.phase === 'idle' && held.running) {\n held.running = false;\n later(session.id, held, held.run, SESSION_FINISHED_COOLDOWN_MS);\n }\n };\n for (const session of hub.snapshot()) track(session);\n const unsubscribe = hub.onEvent((event) => {\n if (closed) return;\n if (event.kind === 'upsert') {\n track(event.session);\n return;\n }\n if (event.kind === 'removed') {\n const held = tracked.get(event.sessionId);\n if (held) cancel(held);\n tracked.delete(event.sessionId);\n return;\n }\n // Toasts stay in-app: subagent and background completions would otherwise buzz the phone for every run.\n // Attention notices are the exception: the person must act (an approval), so every session pushes.\n if (event.kind === 'frame' && event.frame.type === 'entry_appended') {\n const entry = event.frame.entry as { customType?: unknown; data?: unknown } | undefined;\n if (\n entry?.customType === PO_NOTIFICATION_ENTRY_TYPE &&\n isPoNotificationEntryData(entry.data) &&\n entry.data.attention\n )\n enqueue(\n {\n ...entry.data,\n body: notificationBody(entry.data.body),\n subtitle: tracked.get(event.sessionId)?.session.name ?? entry.data.subtitle,\n },\n event.sessionId,\n );\n return;\n }\n if (event.kind !== 'frame' || event.frame.type !== 'extension_ui_request') return;\n const { frame, sessionId } = event;\n const held = tracked.get(sessionId);\n if (\n !held ||\n held.session.parentSessionId !== undefined ||\n held.session.phase !== 'turn' ||\n typeof frame.method !== 'string' ||\n !['select', 'confirm', 'input', 'editor'].includes(frame.method) ||\n typeof frame.id !== 'string' ||\n !frame.id.trim() ||\n held.dialogs.has(frame.id)\n )\n return;\n held.dialogs.add(frame.id);\n const text = [frame.message, frame.title].find(\n (value): value is string => typeof value === 'string' && !!value.trim(),\n );\n enqueue(\n createPoNotificationEntryData({\n ...attentionNotification(notificationBody(text ?? 'A question is waiting for your answer.')),\n subtitle: held.session.name,\n }),\n sessionId,\n );\n });\n return () => {\n closed = true;\n pending.length = 0;\n for (const held of tracked.values()) cancel(held);\n tracked.clear();\n unsubscribe();\n sender.close();\n };\n}\n"],"mappings":"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"}
@@ -1,9 +1,7 @@
1
1
  import { BACKGROUND_CONTEXT, readBoundedFile } from "@agimon-ai/po-core/platform";
2
2
  import { createPrivateKey } from "node:crypto";
3
- import { BACKGROUND_RUNNER_DEADLINE_MS, BACKGROUND_WORK_HOLDING_PROVIDERS, holdsSettledSession, holdsSettledSessionAfterRunnerDeadline } from "@agimon-ai/po-core/backgroundWork";
4
3
  import { PO_NOTIFICATION_ENTRY_TYPE, createPoNotificationEntryData, isPoNotificationEntryData } from "@agimon-ai/po-core/notification";
5
- import { attentionNotification, notificationBody, settledNotification } from "@agimon-ai/po-notification";
6
- import { AUTO_STOP_ACTION, DEFAULT_AUTO_STOP_DELAYS, decideOnRecheck } from "@agimon-ai/po-autostop";
4
+ import { SESSION_FINISHED_COOLDOWN_MS, attentionNotification, decideSessionFinished, notificationBody, settledNotification } from "@agimon-ai/po-notification";
7
5
  //#region src/builders/server/nativePush.ts
8
6
  const MAX_CREDENTIAL_BYTES = 65536;
9
7
  const FANOUT = 4;
@@ -117,17 +115,15 @@ function subscribeNativePush(hub, remote, sender, notice) {
117
115
  try {
118
116
  const state = await hub.sessionService.readExecutionState?.(sessionId);
119
117
  if (!current() || remote.state().status !== "on") return;
120
- if (!state?.backgroundWork) throw new Error("Execution state unavailable");
121
- const holds = held.holdingSince !== void 0 && Date.now() - held.holdingSince >= BACKGROUND_RUNNER_DEADLINE_MS ? holdsSettledSessionAfterRunnerDeadline : holdsSettledSession;
122
- const { items, errors } = state.backgroundWork;
123
- if (items.some(holds) || errors.some((error) => BACKGROUND_WORK_HOLDING_PROVIDERS.includes(error.provider))) {
124
- held.holdingSince ??= Date.now();
125
- later(sessionId, held, run, DEFAULT_AUTO_STOP_DELAYS.cooldownMs);
118
+ const decision = decideSessionFinished(state, held.holdingSince, Date.now());
119
+ if (decision.action === "unavailable") throw new Error("Execution state unavailable");
120
+ if (decision.action === "hold") {
121
+ held.holdingSince = decision.holdingSince;
122
+ later(sessionId, held, run, decision.delayMs);
126
123
  return;
127
124
  }
128
- const decision = decideOnRecheck(state, DEFAULT_AUTO_STOP_DELAYS);
129
- if (decision.action === AUTO_STOP_ACTION.recheck) later(sessionId, held, run, decision.delayMs);
130
- else if (decision.action === AUTO_STOP_ACTION.shutdown) enqueue(createPoNotificationEntryData(settledNotification({
125
+ if (decision.action === "recheck") later(sessionId, held, run, decision.delayMs);
126
+ else if (decision.action === "finished") enqueue(createPoNotificationEntryData(settledNotification({
131
127
  cwd: held.session.cwd,
132
128
  sessionName: held.session.name
133
129
  })), sessionId);
@@ -158,7 +154,7 @@ function subscribeNativePush(hub, remote, sender, notice) {
158
154
  held.dialogs.clear();
159
155
  } else if (session.phase === "idle" && held.running) {
160
156
  held.running = false;
161
- later(session.id, held, held.run, DEFAULT_AUTO_STOP_DELAYS.cooldownMs);
157
+ later(session.id, held, held.run, SESSION_FINISHED_COOLDOWN_MS);
162
158
  }
163
159
  };
164
160
  for (const session of hub.snapshot()) track(session);
@@ -1 +1 @@
1
- {"version":3,"file":"nativePush.mjs","names":[],"sources":["../../../../src/builders/server/nativePush.ts"],"sourcesContent":["import { createPrivateKey } from 'node:crypto';\n\nimport { AUTO_STOP_ACTION, DEFAULT_AUTO_STOP_DELAYS, decideOnRecheck } from '@agimon-ai/po-autostop';\nimport {\n BACKGROUND_RUNNER_DEADLINE_MS,\n BACKGROUND_WORK_HOLDING_PROVIDERS,\n holdsSettledSession,\n holdsSettledSessionAfterRunnerDeadline,\n} from '@agimon-ai/po-core/backgroundWork';\nimport type { HeadlessHub } from '@agimon-ai/po-core/headlessHub';\nimport {\n createPoNotificationEntryData,\n isPoNotificationEntryData,\n PO_NOTIFICATION_ENTRY_TYPE,\n} from '@agimon-ai/po-core/notification';\nimport { BACKGROUND_CONTEXT, readBoundedFile, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { RemoteRuntime } from '@agimon-ai/po-core/remoteRuntime';\nimport {\n attentionNotification,\n createNativePushSender,\n notificationBody,\n settledNotification,\n} from '@agimon-ai/po-notification';\n\ntype Sender = ReturnType<typeof createNativePushSender>;\ntype Credentials = Parameters<typeof createNativePushSender>[0];\ntype Remote = RemoteRuntime['remote'];\ntype Registration = Parameters<Sender['send']>[0];\nconst MAX_CREDENTIAL_BYTES = 64 * 1024;\nconst FANOUT = 4;\nconst MAX_PENDING = 64;\nconst SEND_TIMEOUT_MS = 10_000;\n\n/** Startup-only host credential loading. Diagnostics deliberately exclude paths and caught errors. */\nexport async function loadNativePushCredentials(\n environment: NodeJS.ProcessEnv,\n fs: FileSystem,\n notice: (message: string) => void,\n): Promise<Credentials> {\n const credentials: Credentials = { timeoutMs: SEND_TIMEOUT_MS };\n const read = async (file: string) =>\n new TextDecoder().decode(await readBoundedFile(fs, file, MAX_CREDENTIAL_BYTES, BACKGROUND_CONTEXT));\n try {\n const { PO_APNS_KEY_FILE: file, PO_APNS_KEY_ID: keyId, PO_APNS_TEAM_ID: teamId } = environment;\n if (!file || !keyId || !teamId || !/^[A-Z0-9]{10}$/u.test(keyId) || !/^[A-Z0-9]{10}$/u.test(teamId))\n throw new Error('Unavailable credentials');\n const key = await read(file);\n const parsed = createPrivateKey(key);\n if (parsed.asymmetricKeyType !== 'ec' || parsed.asymmetricKeyDetails?.namedCurve !== 'prime256v1')\n throw new Error('Invalid key');\n credentials.apns = { key, keyId, teamId };\n } catch {\n notice('Native push APNs disabled: missing or invalid credentials.');\n }\n try {\n const file = environment.PO_FCM_SERVICE_ACCOUNT_FILE;\n if (!file) throw new Error('Unavailable credentials');\n const value: unknown = JSON.parse(await read(file));\n if (!value || typeof value !== 'object') throw new Error('Invalid account');\n const account = value as Record<string, unknown>;\n const { client_email: clientEmail, private_key: privateKey, project_id: projectId } = account;\n if (\n account.type !== 'service_account' ||\n typeof clientEmail !== 'string' ||\n !/^[^\\s@]+@[^\\s@]+$/u.test(clientEmail) ||\n typeof projectId !== 'string' ||\n !/^[a-z][a-z0-9-]{4,61}[a-z0-9]$/u.test(projectId) ||\n typeof privateKey !== 'string'\n )\n throw new Error('Invalid account');\n const parsed = createPrivateKey(privateKey);\n if (parsed.asymmetricKeyType !== 'rsa' || (parsed.asymmetricKeyDetails?.modulusLength ?? 0) < 2048)\n throw new Error('Invalid key');\n credentials.fcm = { clientEmail, privateKey, projectId };\n } catch {\n notice('Native push FCM disabled: missing or invalid credentials.');\n }\n return credentials;\n}\n\nfunction endpoint(registration: Registration): string {\n return JSON.stringify([\n registration.platform,\n registration.token,\n registration.hostRef ?? null,\n registration.platform === 'ios' ? registration.environment : null,\n ]);\n}\n\n/** One live subscription for the entire hub, with no transcript reads or socket dependency. */\nexport function subscribeNativePush(\n hub: Pick<HeadlessHub, 'onEvent' | 'snapshot' | 'sessionService'>,\n remote: Pick<Remote, 'state' | 'listNativePushRegistrations' | 'clearNativePushRegistration'>,\n sender: Sender,\n notice: (message: string) => void,\n): () => void {\n type Job = {\n owners: ReturnType<Remote['listNativePushRegistrations']>;\n notification: Parameters<Sender['send']>[1];\n sessionId: string;\n };\n const pending: Job[] = [];\n let active = 0;\n let closed = false;\n const pump = (): void => {\n while (!closed && active < FANOUT && pending.length > 0) {\n const job = pending.shift()!;\n if (remote.state().status !== 'on') continue;\n const current = remote.listNativePushRegistrations();\n const owners = job.owners.filter((owner) =>\n current.some(\n (held) =>\n held.notificationId === owner.notificationId &&\n endpoint(held.registration) === endpoint(owner.registration),\n ),\n );\n if (owners.length === 0) continue;\n active++;\n void sender\n .send(owners[0]!.registration, job.notification, job.sessionId)\n .then((result) => {\n if (!closed && result.invalid)\n for (const owner of owners) remote.clearNativePushRegistration(owner.notificationId, owner.registration);\n })\n .catch(() => notice('Native push delivery failed.'))\n .finally(() => {\n active--;\n pump();\n });\n }\n };\n const enqueue = (notification: Job['notification'] | undefined, sessionId: string): void => {\n if (!notification || closed || remote.state().status !== 'on') return;\n const groups = new Map<string, Job['owners']>();\n for (const owner of remote.listNativePushRegistrations()) {\n const key = endpoint(owner.registration);\n const group = groups.get(key);\n if (group) group.push(owner);\n else groups.set(key, [owner]);\n }\n for (const owners of groups.values()) {\n if (pending.length + active >= MAX_PENDING) break; // Best effort, never an outbox.\n pending.push({ owners, notification, sessionId });\n }\n pump();\n };\n type Session = ReturnType<HeadlessHub['snapshot']>[number];\n type Tracked = {\n session: Session;\n running: boolean;\n run: number;\n dialogs: Set<string>;\n warned: boolean;\n timer?: ReturnType<typeof setTimeout>;\n holdingSince?: number;\n };\n const tracked = new Map<string, Tracked>();\n const cancel = (held: Tracked): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = undefined;\n held.holdingSince = undefined;\n };\n const later = (sessionId: string, held: Tracked, run: number, delayMs: number): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = setTimeout(() => {\n held.timer = undefined;\n void finished(sessionId, held, run);\n }, delayMs);\n held.timer.unref?.();\n };\n /** Same completion test autostop applies before it closes a session: cooldown, holding work, then idle. */\n const finished = async (sessionId: string, held: Tracked, run: number): Promise<void> => {\n if (held.session.parentSessionId !== undefined || remote.state().status !== 'on') return;\n const current = () =>\n !closed && tracked.get(sessionId) === held && held.run === run && held.session.phase === 'idle';\n if (!current()) return;\n try {\n // Hub summaries do not maintain pendingMessageCount. Only the host knows its queue.\n const state = await hub.sessionService.readExecutionState?.(sessionId);\n if (!current() || remote.state().status !== 'on') return;\n // Unknown background work cannot prove the run finished.\n if (!state?.backgroundWork) throw new Error('Execution state unavailable');\n const expired =\n held.holdingSince !== undefined && Date.now() - held.holdingSince >= BACKGROUND_RUNNER_DEADLINE_MS;\n const holds = expired ? holdsSettledSessionAfterRunnerDeadline : holdsSettledSession;\n const { items, errors } = state.backgroundWork;\n if (items.some(holds) || errors.some((error) => BACKGROUND_WORK_HOLDING_PROVIDERS.includes(error.provider))) {\n held.holdingSince ??= Date.now();\n later(sessionId, held, run, DEFAULT_AUTO_STOP_DELAYS.cooldownMs);\n return;\n }\n const decision = decideOnRecheck(state, DEFAULT_AUTO_STOP_DELAYS);\n if (decision.action === AUTO_STOP_ACTION.recheck) later(sessionId, held, run, decision.delayMs);\n else if (decision.action === AUTO_STOP_ACTION.shutdown)\n enqueue(\n createPoNotificationEntryData(settledNotification({ cwd: held.session.cwd, sessionName: held.session.name })),\n sessionId,\n );\n } catch {\n // ponytail: mirrors without a state reader stay quiet; add a mirror reader for completion parity.\n if (current() && remote.state().status === 'on' && !held.warned) {\n held.warned = true;\n notice('Native push completion check unavailable.');\n }\n }\n };\n const track = (session: Session): void => {\n let held = tracked.get(session.id);\n if (!held) {\n held = { session, running: false, run: 0, dialogs: new Set(), warned: false };\n tracked.set(session.id, held);\n }\n held.session = session;\n if (session.phase === 'turn' && !held.running) {\n cancel(held);\n held.running = true;\n held.run++;\n held.dialogs.clear();\n } else if (session.phase === 'idle' && held.running) {\n held.running = false;\n later(session.id, held, held.run, DEFAULT_AUTO_STOP_DELAYS.cooldownMs);\n }\n };\n for (const session of hub.snapshot()) track(session);\n const unsubscribe = hub.onEvent((event) => {\n if (closed) return;\n if (event.kind === 'upsert') {\n track(event.session);\n return;\n }\n if (event.kind === 'removed') {\n const held = tracked.get(event.sessionId);\n if (held) cancel(held);\n tracked.delete(event.sessionId);\n return;\n }\n // Toasts stay in-app: subagent and background completions would otherwise buzz the phone for every run.\n // Attention notices are the exception: the person must act (an approval), so every session pushes.\n if (event.kind === 'frame' && event.frame.type === 'entry_appended') {\n const entry = event.frame.entry as { customType?: unknown; data?: unknown } | undefined;\n if (\n entry?.customType === PO_NOTIFICATION_ENTRY_TYPE &&\n isPoNotificationEntryData(entry.data) &&\n entry.data.attention\n )\n enqueue(\n {\n ...entry.data,\n body: notificationBody(entry.data.body),\n subtitle: tracked.get(event.sessionId)?.session.name ?? entry.data.subtitle,\n },\n event.sessionId,\n );\n return;\n }\n if (event.kind !== 'frame' || event.frame.type !== 'extension_ui_request') return;\n const { frame, sessionId } = event;\n const held = tracked.get(sessionId);\n if (\n !held ||\n held.session.parentSessionId !== undefined ||\n held.session.phase !== 'turn' ||\n typeof frame.method !== 'string' ||\n !['select', 'confirm', 'input', 'editor'].includes(frame.method) ||\n typeof frame.id !== 'string' ||\n !frame.id.trim() ||\n held.dialogs.has(frame.id)\n )\n return;\n held.dialogs.add(frame.id);\n const text = [frame.message, frame.title].find(\n (value): value is string => typeof value === 'string' && !!value.trim(),\n );\n enqueue(\n createPoNotificationEntryData({\n ...attentionNotification(notificationBody(text ?? 'A question is waiting for your answer.')),\n subtitle: held.session.name,\n }),\n sessionId,\n );\n });\n return () => {\n closed = true;\n pending.length = 0;\n for (const held of tracked.values()) cancel(held);\n tracked.clear();\n unsubscribe();\n sender.close();\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nativePush.mjs","names":[],"sources":["../../../../src/builders/server/nativePush.ts"],"sourcesContent":["import { createPrivateKey } from 'node:crypto';\n\nimport type { HeadlessHub } from '@agimon-ai/po-core/headlessHub';\nimport {\n createPoNotificationEntryData,\n isPoNotificationEntryData,\n PO_NOTIFICATION_ENTRY_TYPE,\n} from '@agimon-ai/po-core/notification';\nimport { BACKGROUND_CONTEXT, readBoundedFile, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { RemoteRuntime } from '@agimon-ai/po-core/remoteRuntime';\nimport {\n attentionNotification,\n createNativePushSender,\n decideSessionFinished,\n notificationBody,\n SESSION_FINISHED_COOLDOWN_MS,\n settledNotification,\n} from '@agimon-ai/po-notification';\n\ntype Sender = ReturnType<typeof createNativePushSender>;\ntype Credentials = Parameters<typeof createNativePushSender>[0];\ntype Remote = RemoteRuntime['remote'];\ntype Registration = Parameters<Sender['send']>[0];\nconst MAX_CREDENTIAL_BYTES = 64 * 1024;\nconst FANOUT = 4;\nconst MAX_PENDING = 64;\nconst SEND_TIMEOUT_MS = 10_000;\n\n/** Startup-only host credential loading. Diagnostics deliberately exclude paths and caught errors. */\nexport async function loadNativePushCredentials(\n environment: NodeJS.ProcessEnv,\n fs: FileSystem,\n notice: (message: string) => void,\n): Promise<Credentials> {\n const credentials: Credentials = { timeoutMs: SEND_TIMEOUT_MS };\n const read = async (file: string) =>\n new TextDecoder().decode(await readBoundedFile(fs, file, MAX_CREDENTIAL_BYTES, BACKGROUND_CONTEXT));\n try {\n const { PO_APNS_KEY_FILE: file, PO_APNS_KEY_ID: keyId, PO_APNS_TEAM_ID: teamId } = environment;\n if (!file || !keyId || !teamId || !/^[A-Z0-9]{10}$/u.test(keyId) || !/^[A-Z0-9]{10}$/u.test(teamId))\n throw new Error('Unavailable credentials');\n const key = await read(file);\n const parsed = createPrivateKey(key);\n if (parsed.asymmetricKeyType !== 'ec' || parsed.asymmetricKeyDetails?.namedCurve !== 'prime256v1')\n throw new Error('Invalid key');\n credentials.apns = { key, keyId, teamId };\n } catch {\n notice('Native push APNs disabled: missing or invalid credentials.');\n }\n try {\n const file = environment.PO_FCM_SERVICE_ACCOUNT_FILE;\n if (!file) throw new Error('Unavailable credentials');\n const value: unknown = JSON.parse(await read(file));\n if (!value || typeof value !== 'object') throw new Error('Invalid account');\n const account = value as Record<string, unknown>;\n const { client_email: clientEmail, private_key: privateKey, project_id: projectId } = account;\n if (\n account.type !== 'service_account' ||\n typeof clientEmail !== 'string' ||\n !/^[^\\s@]+@[^\\s@]+$/u.test(clientEmail) ||\n typeof projectId !== 'string' ||\n !/^[a-z][a-z0-9-]{4,61}[a-z0-9]$/u.test(projectId) ||\n typeof privateKey !== 'string'\n )\n throw new Error('Invalid account');\n const parsed = createPrivateKey(privateKey);\n if (parsed.asymmetricKeyType !== 'rsa' || (parsed.asymmetricKeyDetails?.modulusLength ?? 0) < 2048)\n throw new Error('Invalid key');\n credentials.fcm = { clientEmail, privateKey, projectId };\n } catch {\n notice('Native push FCM disabled: missing or invalid credentials.');\n }\n return credentials;\n}\n\nfunction endpoint(registration: Registration): string {\n return JSON.stringify([\n registration.platform,\n registration.token,\n registration.hostRef ?? null,\n registration.platform === 'ios' ? registration.environment : null,\n ]);\n}\n\n/** One live subscription for the entire hub, with no transcript reads or socket dependency. */\nexport function subscribeNativePush(\n hub: Pick<HeadlessHub, 'onEvent' | 'snapshot' | 'sessionService'>,\n remote: Pick<Remote, 'state' | 'listNativePushRegistrations' | 'clearNativePushRegistration'>,\n sender: Sender,\n notice: (message: string) => void,\n): () => void {\n type Job = {\n owners: ReturnType<Remote['listNativePushRegistrations']>;\n notification: Parameters<Sender['send']>[1];\n sessionId: string;\n };\n const pending: Job[] = [];\n let active = 0;\n let closed = false;\n const pump = (): void => {\n while (!closed && active < FANOUT && pending.length > 0) {\n const job = pending.shift()!;\n if (remote.state().status !== 'on') continue;\n const current = remote.listNativePushRegistrations();\n const owners = job.owners.filter((owner) =>\n current.some(\n (held) =>\n held.notificationId === owner.notificationId &&\n endpoint(held.registration) === endpoint(owner.registration),\n ),\n );\n if (owners.length === 0) continue;\n active++;\n void sender\n .send(owners[0]!.registration, job.notification, job.sessionId)\n .then((result) => {\n if (!closed && result.invalid)\n for (const owner of owners) remote.clearNativePushRegistration(owner.notificationId, owner.registration);\n })\n .catch(() => notice('Native push delivery failed.'))\n .finally(() => {\n active--;\n pump();\n });\n }\n };\n const enqueue = (notification: Job['notification'] | undefined, sessionId: string): void => {\n if (!notification || closed || remote.state().status !== 'on') return;\n const groups = new Map<string, Job['owners']>();\n for (const owner of remote.listNativePushRegistrations()) {\n const key = endpoint(owner.registration);\n const group = groups.get(key);\n if (group) group.push(owner);\n else groups.set(key, [owner]);\n }\n for (const owners of groups.values()) {\n if (pending.length + active >= MAX_PENDING) break; // Best effort, never an outbox.\n pending.push({ owners, notification, sessionId });\n }\n pump();\n };\n type Session = ReturnType<HeadlessHub['snapshot']>[number];\n type Tracked = {\n session: Session;\n running: boolean;\n run: number;\n dialogs: Set<string>;\n warned: boolean;\n timer?: ReturnType<typeof setTimeout>;\n holdingSince?: number;\n };\n const tracked = new Map<string, Tracked>();\n const cancel = (held: Tracked): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = undefined;\n held.holdingSince = undefined;\n };\n const later = (sessionId: string, held: Tracked, run: number, delayMs: number): void => {\n if (held.timer !== undefined) clearTimeout(held.timer);\n held.timer = setTimeout(() => {\n held.timer = undefined;\n void finished(sessionId, held, run);\n }, delayMs);\n held.timer.unref?.();\n };\n /** Same completion test autostop applies before it closes a session: cooldown, holding work, then idle. */\n const finished = async (sessionId: string, held: Tracked, run: number): Promise<void> => {\n if (held.session.parentSessionId !== undefined || remote.state().status !== 'on') return;\n const current = () =>\n !closed && tracked.get(sessionId) === held && held.run === run && held.session.phase === 'idle';\n if (!current()) return;\n try {\n // Hub summaries do not maintain pendingMessageCount. Only the host knows its queue.\n const state = await hub.sessionService.readExecutionState?.(sessionId);\n if (!current() || remote.state().status !== 'on') return;\n const decision = decideSessionFinished(state, held.holdingSince, Date.now());\n if (decision.action === 'unavailable') throw new Error('Execution state unavailable');\n if (decision.action === 'hold') {\n held.holdingSince = decision.holdingSince;\n later(sessionId, held, run, decision.delayMs);\n return;\n }\n if (decision.action === 'recheck') later(sessionId, held, run, decision.delayMs);\n else if (decision.action === 'finished')\n enqueue(\n createPoNotificationEntryData(settledNotification({ cwd: held.session.cwd, sessionName: held.session.name })),\n sessionId,\n );\n } catch {\n // ponytail: mirrors without a state reader stay quiet; add a mirror reader for completion parity.\n if (current() && remote.state().status === 'on' && !held.warned) {\n held.warned = true;\n notice('Native push completion check unavailable.');\n }\n }\n };\n const track = (session: Session): void => {\n let held = tracked.get(session.id);\n if (!held) {\n held = { session, running: false, run: 0, dialogs: new Set(), warned: false };\n tracked.set(session.id, held);\n }\n held.session = session;\n if (session.phase === 'turn' && !held.running) {\n cancel(held);\n held.running = true;\n held.run++;\n held.dialogs.clear();\n } else if (session.phase === 'idle' && held.running) {\n held.running = false;\n later(session.id, held, held.run, SESSION_FINISHED_COOLDOWN_MS);\n }\n };\n for (const session of hub.snapshot()) track(session);\n const unsubscribe = hub.onEvent((event) => {\n if (closed) return;\n if (event.kind === 'upsert') {\n track(event.session);\n return;\n }\n if (event.kind === 'removed') {\n const held = tracked.get(event.sessionId);\n if (held) cancel(held);\n tracked.delete(event.sessionId);\n return;\n }\n // Toasts stay in-app: subagent and background completions would otherwise buzz the phone for every run.\n // Attention notices are the exception: the person must act (an approval), so every session pushes.\n if (event.kind === 'frame' && event.frame.type === 'entry_appended') {\n const entry = event.frame.entry as { customType?: unknown; data?: unknown } | undefined;\n if (\n entry?.customType === PO_NOTIFICATION_ENTRY_TYPE &&\n isPoNotificationEntryData(entry.data) &&\n entry.data.attention\n )\n enqueue(\n {\n ...entry.data,\n body: notificationBody(entry.data.body),\n subtitle: tracked.get(event.sessionId)?.session.name ?? entry.data.subtitle,\n },\n event.sessionId,\n );\n return;\n }\n if (event.kind !== 'frame' || event.frame.type !== 'extension_ui_request') return;\n const { frame, sessionId } = event;\n const held = tracked.get(sessionId);\n if (\n !held ||\n held.session.parentSessionId !== undefined ||\n held.session.phase !== 'turn' ||\n typeof frame.method !== 'string' ||\n !['select', 'confirm', 'input', 'editor'].includes(frame.method) ||\n typeof frame.id !== 'string' ||\n !frame.id.trim() ||\n held.dialogs.has(frame.id)\n )\n return;\n held.dialogs.add(frame.id);\n const text = [frame.message, frame.title].find(\n (value): value is string => typeof value === 'string' && !!value.trim(),\n );\n enqueue(\n createPoNotificationEntryData({\n ...attentionNotification(notificationBody(text ?? 'A question is waiting for your answer.')),\n subtitle: held.session.name,\n }),\n sessionId,\n );\n });\n return () => {\n closed = true;\n pending.length = 0;\n for (const held of tracked.values()) cancel(held);\n tracked.clear();\n unsubscribe();\n sender.close();\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po",
3
- "version": "0.0.0-alpha.114",
3
+ "version": "0.0.0-alpha.116",
4
4
  "description": "Opinionated, composable agent harness for scoped tools, skills, MCP servers, and developer workflows.",
5
5
  "keywords": [
6
6
  "agent-harness",
@@ -177,28 +177,28 @@
177
177
  "registry": "https://registry.npmjs.org/"
178
178
  },
179
179
  "dependencies": {
180
- "@agimon-ai/po-approval": "0.0.0-alpha.113",
181
- "@agimon-ai/po-artifact": "0.0.0-alpha.112",
182
- "@agimon-ai/po-autostop": "0.0.0-alpha.113",
183
- "@agimon-ai/po-cache": "0.0.0-alpha.113",
184
- "@agimon-ai/po-config": "0.0.0-alpha.113",
185
- "@agimon-ai/po-core": "0.0.0-alpha.113",
186
- "@agimon-ai/po-db": "0.0.0-alpha.113",
187
- "@agimon-ai/po-domain": "0.0.0-alpha.113",
188
- "@agimon-ai/po-logsink": "0.0.1-alpha.95",
189
- "@agimon-ai/po-major-mode": "0.0.0-alpha.113",
190
- "@agimon-ai/po-mcp-proxy": "0.0.1-alpha.92",
191
- "@agimon-ai/po-minor-mode": "0.0.0-alpha.113",
192
- "@agimon-ai/po-native-components": "0.0.0-alpha.113",
193
- "@agimon-ai/po-network": "0.0.2-alpha.60",
194
- "@agimon-ai/po-notification": "0.0.0-alpha.113",
195
- "@agimon-ai/po-profile": "0.0.0-alpha.113",
196
- "@agimon-ai/po-session": "0.0.0-alpha.113",
197
- "@agimon-ai/po-skill": "0.0.0-alpha.113",
198
- "@agimon-ai/po-ui": "0.0.0-alpha.113",
199
- "@agimon-ai/po-vault": "0.0.0-alpha.113",
200
- "@agimon-ai/po-web-components": "0.0.0-alpha.113",
201
- "@agimon-ai/po-web-security": "0.0.0-alpha.113",
180
+ "@agimon-ai/po-approval": "0.0.0-alpha.115",
181
+ "@agimon-ai/po-artifact": "0.0.0-alpha.114",
182
+ "@agimon-ai/po-autostop": "0.0.0-alpha.115",
183
+ "@agimon-ai/po-cache": "0.0.0-alpha.115",
184
+ "@agimon-ai/po-config": "0.0.0-alpha.115",
185
+ "@agimon-ai/po-core": "0.0.0-alpha.115",
186
+ "@agimon-ai/po-db": "0.0.0-alpha.115",
187
+ "@agimon-ai/po-domain": "0.0.0-alpha.115",
188
+ "@agimon-ai/po-logsink": "0.0.1-alpha.97",
189
+ "@agimon-ai/po-major-mode": "0.0.0-alpha.115",
190
+ "@agimon-ai/po-mcp-proxy": "0.0.1-alpha.94",
191
+ "@agimon-ai/po-minor-mode": "0.0.0-alpha.115",
192
+ "@agimon-ai/po-native-components": "0.0.0-alpha.115",
193
+ "@agimon-ai/po-network": "0.0.2-alpha.62",
194
+ "@agimon-ai/po-notification": "0.0.0-alpha.115",
195
+ "@agimon-ai/po-profile": "0.0.0-alpha.115",
196
+ "@agimon-ai/po-session": "0.0.0-alpha.115",
197
+ "@agimon-ai/po-skill": "0.0.0-alpha.115",
198
+ "@agimon-ai/po-ui": "0.0.0-alpha.115",
199
+ "@agimon-ai/po-vault": "0.0.0-alpha.115",
200
+ "@agimon-ai/po-web-components": "0.0.0-alpha.115",
201
+ "@agimon-ai/po-web-security": "0.0.0-alpha.115",
202
202
  "@deepseek-ai/cordis": "4.0.5",
203
203
  "@earendil-works/pi-agent-core": "1.5.0",
204
204
  "@earendil-works/pi-coding-agent": "1.5.0",
@@ -220,10 +220,10 @@
220
220
  "yaml": "2.9.1"
221
221
  },
222
222
  "devDependencies": {
223
- "@agimon-ai/po-calendar": "0.0.1-alpha.72",
224
- "@agimon-ai/po-identity": "0.0.2-alpha.60",
225
- "@agimon-ai/po-runner": "0.0.0-alpha.113",
226
- "@agimon-ai/vibe-lint-plugin-po-cli": "0.0.0-alpha.113",
223
+ "@agimon-ai/po-calendar": "0.0.1-alpha.74",
224
+ "@agimon-ai/po-identity": "0.0.2-alpha.62",
225
+ "@agimon-ai/po-runner": "0.0.0-alpha.115",
226
+ "@agimon-ai/vibe-lint-plugin-po-cli": "0.0.0-alpha.115",
227
227
  "@earendil-works/pi-ai": "1.5.0",
228
228
  "@modelcontextprotocol/ext-apps": "2.0.3",
229
229
  "@modelcontextprotocol/server": "2.3.1",