@letrquan/book 0.3.0 → 0.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
package/dist/sdk.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ interface Clock {
|
|
|
56
56
|
declare const providerModelSchema: z.ZodObject<{
|
|
57
57
|
label: z.ZodOptional<z.ZodString>;
|
|
58
58
|
contextWindow: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
reportedContextWindow: z.ZodOptional<z.ZodNumber>;
|
|
59
60
|
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
60
61
|
vision: z.ZodOptional<z.ZodBoolean>;
|
|
61
62
|
manual: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -81,6 +82,44 @@ declare const providerModelSchema: z.ZodObject<{
|
|
|
81
82
|
}>>>;
|
|
82
83
|
}, z.core.$strip>]>>;
|
|
83
84
|
}, z.core.$strip>;
|
|
85
|
+
declare const providerConfigSchema: z.ZodObject<{
|
|
86
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
87
|
+
openai: "openai";
|
|
88
|
+
anthropic: "anthropic";
|
|
89
|
+
}>>;
|
|
90
|
+
baseURL: z.ZodOptional<z.ZodString>;
|
|
91
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
92
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
93
|
+
models: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
94
|
+
label: z.ZodOptional<z.ZodString>;
|
|
95
|
+
contextWindow: z.ZodOptional<z.ZodNumber>;
|
|
96
|
+
reportedContextWindow: z.ZodOptional<z.ZodNumber>;
|
|
97
|
+
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
98
|
+
vision: z.ZodOptional<z.ZodBoolean>;
|
|
99
|
+
manual: z.ZodOptional<z.ZodBoolean>;
|
|
100
|
+
editFormat: z.ZodOptional<z.ZodEnum<{
|
|
101
|
+
patch: "patch";
|
|
102
|
+
replace: "replace";
|
|
103
|
+
whole: "whole";
|
|
104
|
+
}>>;
|
|
105
|
+
effort: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
106
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
107
|
+
low: "low";
|
|
108
|
+
medium: "medium";
|
|
109
|
+
high: "high";
|
|
110
|
+
xhigh: "xhigh";
|
|
111
|
+
max: "max";
|
|
112
|
+
}>>;
|
|
113
|
+
levels: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
114
|
+
low: "low";
|
|
115
|
+
medium: "medium";
|
|
116
|
+
high: "high";
|
|
117
|
+
xhigh: "xhigh";
|
|
118
|
+
max: "max";
|
|
119
|
+
}>>>;
|
|
120
|
+
}, z.core.$strip>]>>;
|
|
121
|
+
}, z.core.$strip>>>;
|
|
122
|
+
}, z.core.$strip>;
|
|
84
123
|
declare const toolDiscoverySettingsSchema: z.ZodObject<{
|
|
85
124
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
86
125
|
auto: "auto";
|
|
@@ -122,6 +161,7 @@ declare const skillSettingsSchema: z.ZodObject<{
|
|
|
122
161
|
}, z.core.$strip>;
|
|
123
162
|
type SkillSettings = z.infer<typeof skillSettingsSchema>;
|
|
124
163
|
type ProviderModelConfig = z.infer<typeof providerModelSchema>;
|
|
164
|
+
type ProviderConfig = z.infer<typeof providerConfigSchema>;
|
|
125
165
|
/** The supported production strategy. */
|
|
126
166
|
declare const compactStrategySchema: z.ZodLiteral<"summary">;
|
|
127
167
|
type CompactStrategy = z.infer<typeof compactStrategySchema>;
|
|
@@ -140,6 +180,7 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
140
180
|
xhigh: "xhigh";
|
|
141
181
|
max: "max";
|
|
142
182
|
}>>;
|
|
183
|
+
advisorModel: z.ZodOptional<z.ZodString>;
|
|
143
184
|
maxTurns: z.ZodOptional<z.ZodNumber>;
|
|
144
185
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
145
186
|
effort: z.ZodOptional<z.ZodEnum<{
|
|
@@ -184,6 +225,13 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
184
225
|
approved: "approved";
|
|
185
226
|
rejected: "rejected";
|
|
186
227
|
}>>>;
|
|
228
|
+
workspaceProviders: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
229
|
+
fingerprint: z.ZodString;
|
|
230
|
+
choice: z.ZodEnum<{
|
|
231
|
+
approved: "approved";
|
|
232
|
+
rejected: "rejected";
|
|
233
|
+
}>;
|
|
234
|
+
}, z.core.$strip>>>;
|
|
187
235
|
env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
188
236
|
provider: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
189
237
|
type: z.ZodDefault<z.ZodEnum<{
|
|
@@ -196,6 +244,7 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
196
244
|
models: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
197
245
|
label: z.ZodOptional<z.ZodString>;
|
|
198
246
|
contextWindow: z.ZodOptional<z.ZodNumber>;
|
|
247
|
+
reportedContextWindow: z.ZodOptional<z.ZodNumber>;
|
|
199
248
|
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
200
249
|
vision: z.ZodOptional<z.ZodBoolean>;
|
|
201
250
|
manual: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -241,6 +290,7 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
241
290
|
allowWrite: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
242
291
|
denyWrite: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
243
292
|
denyRead: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
293
|
+
allowGitWrites: z.ZodDefault<z.ZodBoolean>;
|
|
244
294
|
}, z.core.$strip>>;
|
|
245
295
|
network: z.ZodPrefault<z.ZodObject<{
|
|
246
296
|
allowedDomains: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -316,6 +366,7 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
316
366
|
requestTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
317
367
|
streamStallTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
318
368
|
thinkingStallTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
369
|
+
firstDeltaStallTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
319
370
|
toolRetries: z.ZodDefault<z.ZodNumber>;
|
|
320
371
|
watchdog: z.ZodDefault<z.ZodBoolean>;
|
|
321
372
|
streamReissueAttempts: z.ZodDefault<z.ZodNumber>;
|
|
@@ -324,15 +375,6 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
324
375
|
memory: z.ZodPrefault<z.ZodObject<{
|
|
325
376
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
326
377
|
autoSave: z.ZodDefault<z.ZodBoolean>;
|
|
327
|
-
requireApproval: z.ZodDefault<z.ZodBoolean>;
|
|
328
|
-
quarantineExternal: z.ZodDefault<z.ZodBoolean>;
|
|
329
|
-
extraction: z.ZodPrefault<z.ZodObject<{
|
|
330
|
-
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
331
|
-
idleHours: z.ZodDefault<z.ZodNumber>;
|
|
332
|
-
minMessages: z.ZodDefault<z.ZodNumber>;
|
|
333
|
-
maxPerSession: z.ZodDefault<z.ZodNumber>;
|
|
334
|
-
maxSessionsPerRun: z.ZodDefault<z.ZodNumber>;
|
|
335
|
-
}, z.core.$strip>>;
|
|
336
378
|
}, z.core.$strip>>;
|
|
337
379
|
continuation: z.ZodPrefault<z.ZodObject<{
|
|
338
380
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -345,8 +387,8 @@ declare const bookSettingsSchema: z.ZodObject<{
|
|
|
345
387
|
agents: z.ZodPrefault<z.ZodObject<{
|
|
346
388
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
347
389
|
manual: "manual";
|
|
348
|
-
adaptive: "adaptive";
|
|
349
390
|
off: "off";
|
|
391
|
+
adaptive: "adaptive";
|
|
350
392
|
}>>;
|
|
351
393
|
maxConcurrent: z.ZodDefault<z.ZodNumber>;
|
|
352
394
|
maxSpawned: z.ZodDefault<z.ZodNumber>;
|
|
@@ -423,31 +465,43 @@ type BookSettings = z.infer<typeof bookSettingsSchema>;
|
|
|
423
465
|
/**
|
|
424
466
|
* Resolved settings after layering (all defaults filled).
|
|
425
467
|
*/
|
|
426
|
-
type ResolvedSettings = Required<Omit<BookSettings, 'model' | 'compactModel' | 'compactEffort' | 'maxTurns' | 'maxTokens' | 'effort' | 'theme' | 'shell' | 'autoCompactEnabled' | 'defaultMode' | 'disableBypassPermissionsMode'>> & Pick<BookSettings, 'model' | 'compactModel' | 'compactEffort' | 'maxTurns' | 'maxTokens' | 'effort' | 'theme' | 'shell' | 'autoCompactEnabled' | 'defaultMode' | 'disableBypassPermissionsMode'>;
|
|
468
|
+
type ResolvedSettings = Required<Omit<BookSettings, 'model' | 'compactModel' | 'compactEffort' | 'advisorModel' | 'maxTurns' | 'maxTokens' | 'effort' | 'theme' | 'shell' | 'autoCompactEnabled' | 'defaultMode' | 'disableBypassPermissionsMode'>> & Pick<BookSettings, 'model' | 'compactModel' | 'compactEffort' | 'advisorModel' | 'maxTurns' | 'maxTokens' | 'effort' | 'theme' | 'shell' | 'autoCompactEnabled' | 'defaultMode' | 'disableBypassPermissionsMode'>;
|
|
427
469
|
|
|
428
470
|
declare const MEMORY_TYPES: readonly ["user", "feedback", "project", "reference"];
|
|
429
471
|
type MemoryType = (typeof MEMORY_TYPES)[number];
|
|
430
|
-
type MemoryStatus = 'approved' | 'pending' | 'discarded' | 'superseded';
|
|
431
472
|
interface MemoryFileSummary {
|
|
432
473
|
name: string;
|
|
433
474
|
path: string;
|
|
434
475
|
type?: MemoryType;
|
|
435
|
-
status?: MemoryStatus;
|
|
436
476
|
title?: string;
|
|
437
|
-
|
|
438
|
-
|
|
477
|
+
/** The frontmatter `description`: what the memory is for, in a line. */
|
|
478
|
+
description?: string;
|
|
439
479
|
size: number;
|
|
440
480
|
driver?: SessionDriver;
|
|
481
|
+
/**
|
|
482
|
+
* The file still carries the retired `status: superseded` frontmatter of the old save tool. It
|
|
483
|
+
* is no longer retired by anything, so it loads like any other file; `/memory status` lists it
|
|
484
|
+
* as legacy.
|
|
485
|
+
*/
|
|
486
|
+
legacySuperseded?: boolean;
|
|
441
487
|
}
|
|
442
488
|
interface LoadedMemoryContext {
|
|
443
489
|
dir: string;
|
|
444
490
|
indexFile: string | null;
|
|
445
491
|
indexLoaded: boolean;
|
|
492
|
+
/** Non-empty lines in MEMORY.md (the whole file, not only what loaded). */
|
|
446
493
|
indexLineCount: number;
|
|
494
|
+
/** Non-empty lines that loaded, the cut marker excluded. */
|
|
447
495
|
loadedLineCount: number;
|
|
496
|
+
/** UTF-8 size of the trimmed MEMORY.md. */
|
|
497
|
+
indexByteCount: number;
|
|
498
|
+
/** The index was cut at the line limit. */
|
|
499
|
+
wasLineTruncated: boolean;
|
|
500
|
+
/** The index was cut at the byte limit. */
|
|
501
|
+
wasByteTruncated: boolean;
|
|
502
|
+
/** The loaded index; ends with a warning marker when it was cut. */
|
|
448
503
|
indexText: string;
|
|
449
504
|
files: MemoryFileSummary[];
|
|
450
|
-
candidates: MemoryFileSummary[];
|
|
451
505
|
}
|
|
452
506
|
|
|
453
507
|
interface LearnedModelWindowEntry {
|
|
@@ -461,6 +515,89 @@ interface ModelWindowStore {
|
|
|
461
515
|
all(): Record<string, LearnedModelWindowEntry>;
|
|
462
516
|
}
|
|
463
517
|
|
|
518
|
+
/** A provider entry as a settings file wrote it: every key optional, nothing defaulted. */
|
|
519
|
+
type ProviderDeclaration = Partial<ProviderConfig>;
|
|
520
|
+
type WorkspaceProviderState = 'approved' | 'rejected' | 'pending';
|
|
521
|
+
/** Override keys one workspace layer wrote for a provider a trusted layer defines. */
|
|
522
|
+
interface DroppedProviderOverride {
|
|
523
|
+
path: string;
|
|
524
|
+
providerId: string;
|
|
525
|
+
/** Which of `baseURL`, `baseUrl`, `apiKey` and `type` the layer wrote. */
|
|
526
|
+
keys: string[];
|
|
527
|
+
}
|
|
528
|
+
/** A provider only workspace layers declare, merged across them, with what the user decides on. */
|
|
529
|
+
interface WorkspaceProviderEntry {
|
|
530
|
+
id: string;
|
|
531
|
+
/** The merged declaration, later layers over earlier ones. */
|
|
532
|
+
declaration: ProviderDeclaration;
|
|
533
|
+
fingerprint: string;
|
|
534
|
+
/** Every layer that declared it, in merge order. */
|
|
535
|
+
layers: string[];
|
|
536
|
+
}
|
|
537
|
+
interface WorkspaceProviderPartition {
|
|
538
|
+
/** The user approved this exact declaration: it joins the resolved providers. */
|
|
539
|
+
approved: WorkspaceProviderEntry[];
|
|
540
|
+
/** No decision recorded for this declaration: withheld until the user makes one. */
|
|
541
|
+
pending: WorkspaceProviderEntry[];
|
|
542
|
+
/** Explicitly refused: withheld, and not re-offered. */
|
|
543
|
+
rejected: WorkspaceProviderEntry[];
|
|
544
|
+
/**
|
|
545
|
+
* A provider named {@link LEGACY_BASE_URL_ID}: that id is where the decision about a legacy
|
|
546
|
+
* `baseUrl` lives, so a decision recorded under it could not tell the two apart. Withheld
|
|
547
|
+
* whatever the trust store holds, and never offered for approval.
|
|
548
|
+
*/
|
|
549
|
+
reserved: WorkspaceProviderEntry[];
|
|
550
|
+
}
|
|
551
|
+
/** A model selection a workspace layer made that names a withheld workspace provider. */
|
|
552
|
+
interface WithheldModelSelection {
|
|
553
|
+
/** The slot: `model`, `compactModel`, `advisorModel` or `agents.profiles.<name>.model`. */
|
|
554
|
+
key: string;
|
|
555
|
+
value: string;
|
|
556
|
+
providerId: string;
|
|
557
|
+
/** The workspace layer the value came from. */
|
|
558
|
+
path: string;
|
|
559
|
+
/** What applies instead: the trusted layers' own value, or `undefined` when they set none. */
|
|
560
|
+
fallback: string | undefined;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* A model selection the user's own settings, `--model` or the environment made, whose prefix
|
|
564
|
+
* names only a withheld workspace provider. It is unaffected: it resolves as it would had the
|
|
565
|
+
* workspace not declared that provider. Approving the provider is what would change that.
|
|
566
|
+
*/
|
|
567
|
+
interface UnaffectedModelSelection {
|
|
568
|
+
key: string;
|
|
569
|
+
value: string;
|
|
570
|
+
providerId: string;
|
|
571
|
+
/** Where it came from: a settings path, `--model` or an environment variable. */
|
|
572
|
+
source: string;
|
|
573
|
+
}
|
|
574
|
+
/** The legacy `.bookrc.json` `baseUrl`, and whether the user approved it. */
|
|
575
|
+
interface LegacyBaseUrlGate {
|
|
576
|
+
baseUrl: string;
|
|
577
|
+
state: WorkspaceProviderState;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* What the provider gate did to one resolution (#418), for the config that carries it and for the
|
|
581
|
+
* hosts that report it. Computed from the layer files themselves, never read back from a settings
|
|
582
|
+
* key a workspace layer could write. Build one with {@link createWorkspaceProviderGate}.
|
|
583
|
+
*/
|
|
584
|
+
interface WorkspaceProviderGate {
|
|
585
|
+
/** Provider ids only workspace layers define: they never inherit a key. */
|
|
586
|
+
declared: string[];
|
|
587
|
+
/** The subset of {@link declared} that is withheld (pending, rejected or reserved). */
|
|
588
|
+
withheld: string[];
|
|
589
|
+
/** The same ids sorted by the user's decision on the declaration as written. */
|
|
590
|
+
partition: WorkspaceProviderPartition;
|
|
591
|
+
/** Origin, key and protocol overrides a workspace layer wrote for a trusted provider; dropped. */
|
|
592
|
+
droppedOverrides: DroppedProviderOverride[];
|
|
593
|
+
/** Model selections that fell back to the trusted layers' value. */
|
|
594
|
+
withheldSelections: WithheldModelSelection[];
|
|
595
|
+
/** The user's own selections that name only a withheld provider, and so are unaffected. */
|
|
596
|
+
unaffectedSelections: UnaffectedModelSelection[];
|
|
597
|
+
/** The legacy `.bookrc.json` `baseUrl`, when the workspace has one. */
|
|
598
|
+
legacyBaseUrl?: LegacyBaseUrlGate;
|
|
599
|
+
}
|
|
600
|
+
|
|
464
601
|
type PermissionMode = 'default' | 'auto' | 'plan' | 'accept-edits' | 'dontAsk' | 'bypassPermissions';
|
|
465
602
|
/**
|
|
466
603
|
* A command resolved to a direct argv spawn — no intermediate shell parses it.
|
|
@@ -472,10 +609,16 @@ type PermissionMode = 'default' | 'auto' | 'plan' | 'accept-edits' | 'dontAsk' |
|
|
|
472
609
|
* command string goes to the platform shell as before. Unsandboxed commands
|
|
473
610
|
* use the same form when the session shell is spawned as argv (see
|
|
474
611
|
* `ResolvedShell`).
|
|
612
|
+
*
|
|
613
|
+
* `env` holds variables the spawn must add to the child's environment, merged
|
|
614
|
+
* last over everything else. Git Bash and a POSIX-named sh on Windows receive
|
|
615
|
+
* their command there rather than in `args` (see `shellExecution`), so every
|
|
616
|
+
* site that spawns an `exec` must apply it to that spawn alone.
|
|
475
617
|
*/
|
|
476
618
|
interface CommandExecution {
|
|
477
619
|
file: string;
|
|
478
620
|
args: string[];
|
|
621
|
+
env?: Record<string, string>;
|
|
479
622
|
}
|
|
480
623
|
/** The program that interprets a Bash tool command string. */
|
|
481
624
|
type ShellKind = 'bash' | 'pwsh' | 'powershell' | 'cmd' | 'sh';
|
|
@@ -498,6 +641,17 @@ interface ResolvedShell {
|
|
|
498
641
|
source: ShellSource;
|
|
499
642
|
/** Set when a requested shell could not be honoured and the automatic ladder was used instead. */
|
|
500
643
|
warning?: string;
|
|
644
|
+
/**
|
|
645
|
+
* Set on Windows for a `kind: 'bash'` or `kind: 'sh'` executable that runs on the MSYS runtime
|
|
646
|
+
* (Git for Windows' `bin\bash.exe`, `usr\bin\bash.exe` and `sh.exe`; MSYS2's `bash.exe`,
|
|
647
|
+
* `dash.exe`, `zsh.exe` and other POSIX shells), detected at resolution by `msys-2.0.dll` beside
|
|
648
|
+
* it, or in `..\usr\bin` for one in a `bin` directory (#532, #548). It does not decide delivery: every non-WSL bash and every
|
|
649
|
+
* POSIX-named sh gets its command out of band, flagged or not (`receivesCommandOutOfBand`). It
|
|
650
|
+
* only lets the permission scanner read `'` as on Linux (`readsSingleQuoteAsPosix`), because
|
|
651
|
+
* Windows CI proves this runtime's delivery. Never set off Windows, in a WSL launcher directory,
|
|
652
|
+
* or for a non-POSIX shell, a Cygwin shell or a shim or copy with no DLL beside it.
|
|
653
|
+
*/
|
|
654
|
+
msys?: true;
|
|
501
655
|
}
|
|
502
656
|
/**
|
|
503
657
|
* What phase of retry is currently active (drives the TUI spinner label).
|
|
@@ -538,6 +692,16 @@ interface RetryConfig {
|
|
|
538
692
|
outputCapContinuations?: number;
|
|
539
693
|
/** Stall ceiling while the model is thinking; absent falls back to the chat one. */
|
|
540
694
|
thinkingStallTimeoutMs?: number;
|
|
695
|
+
/**
|
|
696
|
+
* First-delta ceiling for a model that may think silently (#379): how long a
|
|
697
|
+
* stream may stay quiet before its first content, tool-call or reasoning
|
|
698
|
+
* delta when the request sends no `reasoning_effort` and the model's catalog
|
|
699
|
+
* entry does not settle the question. A router that buffers the whole
|
|
700
|
+
* thinking block sends a role-only chunk and then nothing for up to about
|
|
701
|
+
* 80 s, past the chat ceiling. Optional, and absent falls back to the chat
|
|
702
|
+
* one, so a hand-built RetryConfig keeps the behavior it was written for.
|
|
703
|
+
*/
|
|
704
|
+
firstDeltaStallTimeoutMs?: number;
|
|
541
705
|
}
|
|
542
706
|
type AgentTaskStatus = 'pending' | 'in_progress' | 'completed' | 'deleted';
|
|
543
707
|
interface AgentTask {
|
|
@@ -592,6 +756,8 @@ interface BackgroundShellRecord {
|
|
|
592
756
|
timer?: NodeJS.Timeout;
|
|
593
757
|
retentionTimer?: NodeJS.Timeout;
|
|
594
758
|
sandboxed?: boolean;
|
|
759
|
+
/** The sandbox's git-dir mode (issue 373): true = every git dir of the workspace read-only. */
|
|
760
|
+
gitDirReadOnly?: boolean;
|
|
595
761
|
}
|
|
596
762
|
interface BackgroundShellStore {
|
|
597
763
|
nextId: number;
|
|
@@ -619,6 +785,8 @@ interface AgentConfig {
|
|
|
619
785
|
modelProviderWarning?: string;
|
|
620
786
|
/** Optional provider/model used only for historical conversation compaction. */
|
|
621
787
|
compactModel?: string;
|
|
788
|
+
/** Optional stronger provider/model the `Advisor` tool consults (`/advisor`). */
|
|
789
|
+
advisorModel?: string;
|
|
622
790
|
compactEffort?: AgentConfig['effort'];
|
|
623
791
|
/** Supported production context-reduction strategy. */
|
|
624
792
|
compactStrategy: CompactStrategy;
|
|
@@ -648,6 +816,24 @@ interface AgentConfig {
|
|
|
648
816
|
/** Plain-model fallback values used after provider/model switches. */
|
|
649
817
|
defaultApiKey?: string;
|
|
650
818
|
defaultBaseUrl?: string;
|
|
819
|
+
/**
|
|
820
|
+
* Whether `defaultBaseUrl` was chosen with BOOK_BASE_URL rather than being the built-in OpenAI
|
|
821
|
+
* default or a legacy `.bookrc.json` `baseUrl` (a repository can ship that file, so it is no
|
|
822
|
+
* proof of the user's choice). Recorded at load, never inferred from the URL.
|
|
823
|
+
*/
|
|
824
|
+
defaultBaseUrlExplicit?: boolean;
|
|
825
|
+
/**
|
|
826
|
+
* Provider ids only a workspace layer declares, computed by the loader from the layer files
|
|
827
|
+
* themselves (#418) -- never from a settings key, which a workspace layer could write. A
|
|
828
|
+
* declared provider never inherits a key, `BOOK_API_KEY` and the session key included; and a
|
|
829
|
+
* withheld one (pending, rejected or reserved) is absent from `settings.provider`, so a model id
|
|
830
|
+
* a workspace controls that names it is refused instead of falling through to the default
|
|
831
|
+
* endpoint, while the user's own selection with that prefix resolves as the plain fallback. The
|
|
832
|
+
* rest of the gate (what was dropped, which selections fell back or were left alone, the legacy
|
|
833
|
+
* `baseUrl`) is what the hosts report. It describes one resolution of the settings: whoever
|
|
834
|
+
* re-resolves them in a session replaces the gate with the one computed alongside.
|
|
835
|
+
*/
|
|
836
|
+
workspaceProviderGate?: WorkspaceProviderGate;
|
|
651
837
|
defaultProvider?: 'anthropic' | 'openai' | 'auto';
|
|
652
838
|
autoCompactEnabled: boolean;
|
|
653
839
|
/**
|
|
@@ -715,16 +901,29 @@ interface ShellStartOptions {
|
|
|
715
901
|
effectiveCommand: string;
|
|
716
902
|
/**
|
|
717
903
|
* Present for sandboxed commands and for a session shell spawned as argv
|
|
718
|
-
* (Git Bash or PowerShell
|
|
904
|
+
* (on Windows Git Bash, a POSIX sh or PowerShell, where Git Bash and a POSIX
|
|
905
|
+
* sh carry the command in `env`); absent means Node's `shell: true`.
|
|
719
906
|
*/
|
|
720
907
|
exec?: CommandExecution;
|
|
721
908
|
workdir: string;
|
|
722
909
|
env: NodeJS.ProcessEnv;
|
|
723
910
|
sandboxed: boolean;
|
|
911
|
+
/**
|
|
912
|
+
* The sandbox's git-dir mode (issue 373): true binds every git directory of the
|
|
913
|
+
* workspace read-only, which is what the failed `git` command's note describes.
|
|
914
|
+
* Persisted with the spec so a restored job still reports its own note.
|
|
915
|
+
*/
|
|
916
|
+
gitDirReadOnly?: boolean;
|
|
724
917
|
title?: string;
|
|
725
918
|
notify?: BackgroundShellNotify;
|
|
726
919
|
timeoutMs?: number;
|
|
727
920
|
lifetime?: 'session' | 'persistent';
|
|
921
|
+
/**
|
|
922
|
+
* Ends a persistent start that is still waiting for its runner: the runner is killed and the job
|
|
923
|
+
* forgotten, as for a start that outran its budget. Read only by a persistent start; a session
|
|
924
|
+
* shell is spawned at once and has nothing to wait for.
|
|
925
|
+
*/
|
|
926
|
+
signal?: AbortSignal;
|
|
728
927
|
workspace?: string;
|
|
729
928
|
envOverrides?: Record<string, string>;
|
|
730
929
|
parentSessionId?: string;
|
|
@@ -745,6 +944,7 @@ interface ShellAdoptOptions {
|
|
|
745
944
|
effectiveCommand: string;
|
|
746
945
|
workdir: string;
|
|
747
946
|
sandboxed: boolean;
|
|
947
|
+
gitDirReadOnly?: boolean;
|
|
748
948
|
title?: string;
|
|
749
949
|
/** When the command really started, not when it was adopted. */
|
|
750
950
|
startedAt?: number;
|
|
@@ -790,8 +990,12 @@ declare class ShellJobManager {
|
|
|
790
990
|
private readonly unwrittenLost;
|
|
791
991
|
/** Set by `dispose()`: a disposed manager takes on no new work. */
|
|
792
992
|
private disposed;
|
|
993
|
+
/** Secrets the buffer trim must not cut (#411); the Bash tools hand over the session's set. */
|
|
994
|
+
private providerSecrets;
|
|
793
995
|
constructor(store: BackgroundShellStore, options?: ShellJobManagerOptions);
|
|
794
996
|
private readonly clock;
|
|
997
|
+
/** The provider secrets the 5 MB buffer trim keeps whole (#411). */
|
|
998
|
+
setProviderSecrets(secrets: readonly string[] | undefined): void;
|
|
795
999
|
subscribe(listener: (event: ShellJobEvent) => void): () => void;
|
|
796
1000
|
configureWorkspace(workspace: string): void;
|
|
797
1001
|
list(): BackgroundShellRecord[];
|
|
@@ -821,8 +1025,30 @@ declare class ShellJobManager {
|
|
|
821
1025
|
* between.
|
|
822
1026
|
*/
|
|
823
1027
|
adopt(options: ShellAdoptOptions): BackgroundShellRecord | undefined;
|
|
824
|
-
|
|
825
|
-
|
|
1028
|
+
/**
|
|
1029
|
+
* The next unread chunk. `secrets` are values the caller will redact from what it gets back
|
|
1030
|
+
* (#411): the chunk never ends inside one, so a key cut in half across two reads cannot slip past
|
|
1031
|
+
* an exact-match redactor. `readOffset` and `remaining` follow the adjusted cut.
|
|
1032
|
+
*/
|
|
1033
|
+
readOutput(shellId: string, limit?: number, secrets?: readonly string[]): ShellOutputResult | undefined;
|
|
1034
|
+
/**
|
|
1035
|
+
* Cut the next chunk out of `unread` and advance the cursor past it.
|
|
1036
|
+
*
|
|
1037
|
+
* With secrets configured, a running job's read stops after the last line break, so a key being
|
|
1038
|
+
* written is not returned in halves: the unterminated trailing line waits until it ends or the
|
|
1039
|
+
* job does. The rule looks at line breaks and length only, never at the secrets' values: a
|
|
1040
|
+
* rule that held back "text that starts a key" would let a command probe the key by printing
|
|
1041
|
+
* candidates and seeing which BashOutput comes back empty. A line with no break at all is
|
|
1042
|
+
* returned in {@link UNTERMINATED_READ_CAP}-character pieces so a prompt or a long line is not
|
|
1043
|
+
* held forever. The chunk never ends inside a whole key (`secretSafeCut`), and a finished job
|
|
1044
|
+
* returns everything.
|
|
1045
|
+
*/
|
|
1046
|
+
private takeUnread;
|
|
1047
|
+
/**
|
|
1048
|
+
* The last `limit` characters of the output. With `secrets`, a window whose front edge falls
|
|
1049
|
+
* inside a key starts where that key starts, so the redactor the caller applies sees it whole.
|
|
1050
|
+
*/
|
|
1051
|
+
readTail(shellId: string, limit?: number, secrets?: readonly string[]): string | undefined;
|
|
826
1052
|
stop(shellId: string, reason?: 'killed' | 'timed_out'): Promise<boolean>;
|
|
827
1053
|
dismiss(shellId: string): void;
|
|
828
1054
|
acknowledgeCompletion(shellId: string, sequence?: number, consumer?: 'ui' | 'agent'): void;
|
|
@@ -884,7 +1110,22 @@ declare class ShellJobManager {
|
|
|
884
1110
|
}
|
|
885
1111
|
|
|
886
1112
|
type AgentTerminalStatus = 'completed' | 'failed' | 'cancelled' | 'timed_out' | 'interrupted';
|
|
887
|
-
type AgentTerminalReason = 'normal_completion' | 'blocked_by_policy' | 'provider_error' | 'provider_timeout' | 'stream_stall' | 'protocol_error' | 'context_overflow' | 'budget_exceeded' | 'budget_unverifiable' | 'max_turns' | 'runtime_error' | 'caller_cancelled' | 'user_cancelled' | 'session_disposed' | 'session_replaced' | 'transport_interrupted' | 'output_cap' | 'credentials_rejected'
|
|
1113
|
+
type AgentTerminalReason = 'normal_completion' | 'blocked_by_policy' | 'provider_error' | 'provider_timeout' | 'stream_stall' | 'protocol_error' | 'context_overflow' | 'budget_exceeded' | 'budget_unverifiable' | 'max_turns' | 'runtime_error' | 'caller_cancelled' | 'user_cancelled' | 'session_disposed' | 'session_replaced' | 'transport_interrupted' | 'output_cap' | 'credentials_rejected'
|
|
1114
|
+
/**
|
|
1115
|
+
* A usage or plan limit is spent, or the provider stated a wait longer than the
|
|
1116
|
+
* retry layer can sit out (provider codes `usage_limit` and `rate_limit_wait`).
|
|
1117
|
+
* Not a rejected key: nothing needs rotating, the run waits for the reset or an
|
|
1118
|
+
* operator picks another model. A 402 and `insufficient_quota` stay
|
|
1119
|
+
* `credentials_rejected`.
|
|
1120
|
+
*/
|
|
1121
|
+
| 'quota_exhausted' | 'model_unavailable' | 'objective_complete' | 'continuation_limit' | 'blocked_plan' | 'no_progress'
|
|
1122
|
+
/** The run's `/goal` was judged impossible, or was still unmet at the check cap. */
|
|
1123
|
+
| 'goal_unmet'
|
|
1124
|
+
/**
|
|
1125
|
+
* The run's `/goal` check could not run (provider error, timeout, unreadable
|
|
1126
|
+
* answer). The run was let end rather than trapped, but nothing confirmed the goal.
|
|
1127
|
+
*/
|
|
1128
|
+
| 'goal_unverified' | 'all_tools_blocked' | 'plan_stop' | 'handoff_requested' | 'missing_terminal';
|
|
888
1129
|
interface AgentTerminalOutcome {
|
|
889
1130
|
readonly status: AgentTerminalStatus;
|
|
890
1131
|
readonly reason: AgentTerminalReason;
|
|
@@ -893,6 +1134,52 @@ interface AgentTerminalOutcome {
|
|
|
893
1134
|
readonly providerCode?: string;
|
|
894
1135
|
}
|
|
895
1136
|
|
|
1137
|
+
/**
|
|
1138
|
+
* A session goal set with `/goal <condition>`: the host keeps the agent working
|
|
1139
|
+
* until a separate evaluator judges, from the transcript, that the condition
|
|
1140
|
+
* holds. Lives on the session runtime and survives a resume through `goal`
|
|
1141
|
+
* session records.
|
|
1142
|
+
*/
|
|
1143
|
+
interface ActiveGoal {
|
|
1144
|
+
/** The condition as the user wrote it; also the agent's directive. */
|
|
1145
|
+
condition: string;
|
|
1146
|
+
/** Evaluations that found the condition unmet. */
|
|
1147
|
+
iterations: number;
|
|
1148
|
+
/** Epoch milliseconds when the goal was set. */
|
|
1149
|
+
setAt: number;
|
|
1150
|
+
/** `restored` when a resume brought back a goal set in an earlier process. */
|
|
1151
|
+
origin: 'user' | 'restored';
|
|
1152
|
+
/** Output tokens the root agent and the goal checks have spent while the goal was set. */
|
|
1153
|
+
outputTokens: number;
|
|
1154
|
+
/** The evaluator's reason from the most recent unmet check. */
|
|
1155
|
+
lastReason?: string;
|
|
1156
|
+
}
|
|
1157
|
+
/** One change in a goal's state, reported to the host as it happens. */
|
|
1158
|
+
type GoalStatusEvent = {
|
|
1159
|
+
state: 'set';
|
|
1160
|
+
condition: string;
|
|
1161
|
+
} | {
|
|
1162
|
+
state: 'cleared';
|
|
1163
|
+
condition: string;
|
|
1164
|
+
} | {
|
|
1165
|
+
state: 'unmet';
|
|
1166
|
+
condition: string;
|
|
1167
|
+
reason: string;
|
|
1168
|
+
iterations: number;
|
|
1169
|
+
} | {
|
|
1170
|
+
state: 'met' | 'failed';
|
|
1171
|
+
condition: string;
|
|
1172
|
+
reason: string;
|
|
1173
|
+
iterations: number;
|
|
1174
|
+
durationMs: number;
|
|
1175
|
+
tokens: number;
|
|
1176
|
+
} | {
|
|
1177
|
+
state: 'paused';
|
|
1178
|
+
condition: string;
|
|
1179
|
+
cause: 'block_cap' | 'check_failed' | 'max_turns';
|
|
1180
|
+
detail?: string;
|
|
1181
|
+
};
|
|
1182
|
+
|
|
896
1183
|
/** What triggered a compaction attempt. */
|
|
897
1184
|
type CompactTrigger = 'manual' | 'auto';
|
|
898
1185
|
/**
|
|
@@ -1007,6 +1294,13 @@ interface CompactBoundary {
|
|
|
1007
1294
|
timestamp: number;
|
|
1008
1295
|
/** Earlier user turns kept verbatim ahead of the checkpoint; absent on records written before Carried Turns. */
|
|
1009
1296
|
carriedCount?: number;
|
|
1297
|
+
/**
|
|
1298
|
+
* The compaction used reduced-fidelity coverage. Live only: the host shows it on the boundary's
|
|
1299
|
+
* row, it is not written to the session record, and a resumed session's boundaries lack it.
|
|
1300
|
+
*/
|
|
1301
|
+
degraded?: boolean;
|
|
1302
|
+
/** The compaction's warning, for the same live row as `degraded`; not persisted either. */
|
|
1303
|
+
warning?: string;
|
|
1010
1304
|
}
|
|
1011
1305
|
interface RewindCheckpointMetadata {
|
|
1012
1306
|
snapshotId?: string;
|
|
@@ -1019,12 +1313,18 @@ interface RewindTarget extends RewindCheckpointMetadata {
|
|
|
1019
1313
|
id: string;
|
|
1020
1314
|
userEventId: string;
|
|
1021
1315
|
prompt: string;
|
|
1316
|
+
/**
|
|
1317
|
+
* `prompt` is what the user typed: their text, or the command line that ran a command. Only such
|
|
1318
|
+
* a prompt goes back in the composer on a rewind. Absent for any other turn (a command's body, a
|
|
1319
|
+
* notification) and for one recorded before this was, whose prompt may be a command's body.
|
|
1320
|
+
*/
|
|
1321
|
+
promptTyped?: boolean;
|
|
1022
1322
|
attachments?: ImageAttachment[];
|
|
1023
1323
|
timestamp: number;
|
|
1024
1324
|
codeAvailable: boolean;
|
|
1025
1325
|
}
|
|
1026
1326
|
interface SessionRecord {
|
|
1027
|
-
type: 'user' | 'assistant' | 'local' | 'tool_call' | 'tool_result' | 'usage' | 'session_meta' | 'compact' | 'turn_checkpoint' | 'rewind' | 'plan';
|
|
1327
|
+
type: 'user' | 'assistant' | 'local' | 'tool_call' | 'tool_result' | 'usage' | 'session_meta' | 'compact' | 'turn_checkpoint' | 'rewind' | 'plan' | 'goal';
|
|
1028
1328
|
eventId?: string;
|
|
1029
1329
|
timestamp: number;
|
|
1030
1330
|
data: unknown;
|
|
@@ -1044,6 +1344,17 @@ interface PlanRecordData {
|
|
|
1044
1344
|
todos?: PersistedTodo[];
|
|
1045
1345
|
tasks?: AgentTask[];
|
|
1046
1346
|
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Payload stored in a SessionRecord of type `goal` — last record wins.
|
|
1349
|
+
*
|
|
1350
|
+
* Written whenever the session's `/goal` changes: set, checked unmet, met, failed
|
|
1351
|
+
* or cleared. `goal: null` is a cleared goal, so a resume after a goal was met
|
|
1352
|
+
* does not bring it back. Readers must tolerate absence, as for `plan`.
|
|
1353
|
+
*/
|
|
1354
|
+
interface GoalRecordData {
|
|
1355
|
+
version: 1;
|
|
1356
|
+
goal: ActiveGoal | null;
|
|
1357
|
+
}
|
|
1047
1358
|
interface PersistedTodo {
|
|
1048
1359
|
content: string;
|
|
1049
1360
|
status: string;
|
|
@@ -1293,6 +1604,8 @@ interface LoadedSession {
|
|
|
1293
1604
|
* not survive the restart".
|
|
1294
1605
|
*/
|
|
1295
1606
|
plan?: PlanRecordData;
|
|
1607
|
+
/** The newest `goal` record; `goal: null` inside it is a goal that was cleared or reached. */
|
|
1608
|
+
goal?: GoalRecordData;
|
|
1296
1609
|
/**
|
|
1297
1610
|
* Token totals recorded by earlier processes of this session, summed from the
|
|
1298
1611
|
* `usage` records. USD is deliberately not stored: pricing changes between
|
|
@@ -1342,13 +1655,22 @@ interface SessionStoreInterface {
|
|
|
1342
1655
|
list(): SessionMeta[];
|
|
1343
1656
|
findByName(name: string): SessionMeta | undefined;
|
|
1344
1657
|
findById(id: string): SessionMeta | undefined;
|
|
1345
|
-
|
|
1658
|
+
/**
|
|
1659
|
+
* The newest session in `cwd` with at least one message, or the newest one `accept` approves
|
|
1660
|
+
* when given; empty sessions are skipped by default.
|
|
1661
|
+
*/
|
|
1662
|
+
mostRecentInCwd(cwd: string, accept?: (meta: SessionMeta) => boolean): SessionMeta | undefined;
|
|
1346
1663
|
saveImageAttachment?(sessionId: string, image: {
|
|
1347
1664
|
bytes: Uint8Array;
|
|
1348
1665
|
mediaType: ImageAttachment['mediaType'];
|
|
1349
1666
|
displayName?: string;
|
|
1350
1667
|
}): ImageAttachment;
|
|
1351
1668
|
readImageAttachment?(sessionId: string, attachment: ImageAttachment): Uint8Array;
|
|
1669
|
+
/**
|
|
1670
|
+
* Remove a session nothing was ever recorded in (a bare header, no name), file and index
|
|
1671
|
+
* entry together. Returns whether it was removed. A store that omits it keeps every session.
|
|
1672
|
+
*/
|
|
1673
|
+
removeUntouched?(id: string): boolean;
|
|
1352
1674
|
cleanup(days: number, preserveIds?: ReadonlySet<string>): number;
|
|
1353
1675
|
}
|
|
1354
1676
|
|
|
@@ -1393,7 +1715,7 @@ interface ConfigCommandDisplay {
|
|
|
1393
1715
|
workspace: string;
|
|
1394
1716
|
};
|
|
1395
1717
|
}
|
|
1396
|
-
type ContextWindowSource = 'declared' | 'learned' | 'family' | 'default';
|
|
1718
|
+
type ContextWindowSource = 'declared' | 'learned' | 'reported' | 'family' | 'default';
|
|
1397
1719
|
interface ContextCommandDisplay {
|
|
1398
1720
|
kind: 'context';
|
|
1399
1721
|
model: string;
|
|
@@ -1432,6 +1754,11 @@ interface UsageCommandDisplay {
|
|
|
1432
1754
|
rate?: {
|
|
1433
1755
|
inputPerMillion: number;
|
|
1434
1756
|
outputPerMillion: number;
|
|
1757
|
+
longPrompt?: {
|
|
1758
|
+
overPromptTokens: number;
|
|
1759
|
+
inputPerMillion: number;
|
|
1760
|
+
outputPerMillion: number;
|
|
1761
|
+
};
|
|
1435
1762
|
};
|
|
1436
1763
|
estimatedCostUsd?: number;
|
|
1437
1764
|
/** Delegated agents inside `usage`, so the sheet can say so (#370). */
|
|
@@ -1660,6 +1987,9 @@ interface AgentRunResult {
|
|
|
1660
1987
|
readonly ambient?: AgentRunAmbientSnapshot;
|
|
1661
1988
|
}
|
|
1662
1989
|
|
|
1990
|
+
/** {@link reachesUncPath} for one top-level call, answering from what it already probed. */
|
|
1991
|
+
type UncGuard = (path: string) => boolean;
|
|
1992
|
+
|
|
1663
1993
|
type SkillSource = 'user' | 'project';
|
|
1664
1994
|
type SkillRootKind = 'book' | 'agents' | 'claude' | 'opencode';
|
|
1665
1995
|
type SkillIssueSeverity = 'error' | 'warning';
|
|
@@ -1730,8 +2060,16 @@ interface DiscoverSkillsOptions {
|
|
|
1730
2060
|
enabled?: boolean;
|
|
1731
2061
|
executionOverrides?: Readonly<Record<string, SkillExecution>>;
|
|
1732
2062
|
}
|
|
1733
|
-
/**
|
|
1734
|
-
|
|
2063
|
+
/**
|
|
2064
|
+
* Roots are ordered from lowest to highest precedence.
|
|
2065
|
+
*
|
|
2066
|
+
* A project root is a path a repository can commit as a link, and on Windows resolving a link to
|
|
2067
|
+
* a network share opens the share, which can send the user's credentials to the host it names
|
|
2068
|
+
* (#567, #579). A project root that reaches one is therefore not a root at all, so nothing that
|
|
2069
|
+
* takes its roots from here resolves it. The user's own roots are theirs to keep on a share.
|
|
2070
|
+
* `reachesUnc` is the UNC guard of the discovery this is part of, so the two share what it probed.
|
|
2071
|
+
*/
|
|
2072
|
+
declare function skillRoots(workspace: string, options?: DiscoverSkillsOptions, reachesUnc?: UncGuard): SkillRoot[];
|
|
1735
2073
|
declare function discoverSkills(workspace: string, overrides?: Readonly<Record<string, SkillActivation>>, options?: DiscoverSkillsOptions): Skill[];
|
|
1736
2074
|
declare function applySkillOverrides(skills: readonly Skill[], overrides: Readonly<Record<string, SkillActivation>>, executionOverrides?: Readonly<Record<string, SkillExecution>>, enabled?: boolean): Skill[];
|
|
1737
2075
|
declare function loadSkillBody(skill: Skill): LoadedSkillBody;
|
|
@@ -2135,11 +2473,12 @@ type AgentRuntimeEvent = {
|
|
|
2135
2473
|
/**
|
|
2136
2474
|
* A line the child's own run raised for the operator, such as a read it was refused (#305
|
|
2137
2475
|
* item 4). A child's transcript is summarised into a handoff, so a refusal inside it is
|
|
2138
|
-
* otherwise invisible to whoever delegated it.
|
|
2476
|
+
* otherwise invisible to whoever delegated it. Without an `agentId` it is the manager's own
|
|
2477
|
+
* one-time line (#579).
|
|
2139
2478
|
*/
|
|
2140
2479
|
| {
|
|
2141
2480
|
type: 'agent_notice';
|
|
2142
|
-
agentId
|
|
2481
|
+
agentId?: string;
|
|
2143
2482
|
message: string;
|
|
2144
2483
|
} | {
|
|
2145
2484
|
type: 'agent_persistence';
|
|
@@ -2216,6 +2555,19 @@ interface AgentApplyResult {
|
|
|
2216
2555
|
commit?: string;
|
|
2217
2556
|
error?: string;
|
|
2218
2557
|
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Who started an apply: the user (`/agent apply`, `/review --fix`) or the model (`AgentApply`).
|
|
2560
|
+
* The model's `AgentStop` stops only the applies the model started (#527).
|
|
2561
|
+
*/
|
|
2562
|
+
type AgentApplyStarter = 'user' | 'model';
|
|
2563
|
+
/** What a stop that kept the user's applies running did with the agent's applies. */
|
|
2564
|
+
interface AgentStopOutcome {
|
|
2565
|
+
agent: AgentRecord;
|
|
2566
|
+
/** The evidence id of each model-started apply it stopped, which has ended. */
|
|
2567
|
+
stoppedApplies: string[];
|
|
2568
|
+
/** The evidence id of each user-started apply it left running. */
|
|
2569
|
+
keptUserApplies: string[];
|
|
2570
|
+
}
|
|
2219
2571
|
|
|
2220
2572
|
/**
|
|
2221
2573
|
* A small, always-current file saying what a run is doing right now.
|
|
@@ -2549,6 +2901,14 @@ interface AgentLoopCallbacks {
|
|
|
2549
2901
|
onAgentEvent?: (event: AgentRuntimeEvent) => void;
|
|
2550
2902
|
/** Called when a host or system notice should be surfaced (e.g. memory candidate saved). */
|
|
2551
2903
|
onNotice?: (notice: string) => void;
|
|
2904
|
+
/** Called when the session's `/goal` changes state at a stop (checked, met, failed, paused). */
|
|
2905
|
+
onGoalStatus?: (event: GoalStatusEvent) => void;
|
|
2906
|
+
/**
|
|
2907
|
+
* Spend on a side request the loop or a tool made on another model -- a goal check, an
|
|
2908
|
+
* `Advisor` consultation. Not the turn's own usage, so a host must not feed it to the
|
|
2909
|
+
* context meter; it belongs on the session's bill, priced at `metadata`'s model.
|
|
2910
|
+
*/
|
|
2911
|
+
onAuxiliaryUsage?: (usage: Usage, metadata: ProviderResponseMetadata, source: string) => void;
|
|
2552
2912
|
}
|
|
2553
2913
|
|
|
2554
2914
|
interface ProviderStreamOptions {
|
|
@@ -2850,10 +3210,80 @@ interface Sandbox {
|
|
|
2850
3210
|
*
|
|
2851
3211
|
* Returns null if the sandbox is unavailable.
|
|
2852
3212
|
*/
|
|
2853
|
-
wrap(command: string, workspaceRoot: string): CommandExecution | null;
|
|
3213
|
+
wrap(command: string, workspaceRoot: string, protectedPaths?: ProtectedWorkspacePaths): CommandExecution | null;
|
|
2854
3214
|
/** One-line description of the policy actually enforced, for diagnostics. */
|
|
2855
3215
|
describe(): string;
|
|
2856
3216
|
}
|
|
3217
|
+
/**
|
|
3218
|
+
* One workspace path the namespace must shield, and how.
|
|
3219
|
+
*
|
|
3220
|
+
* - `directory` — a directory the host reads after the command exits. Bound
|
|
3221
|
+
* read-only when it exists, masked with an empty read-only tmpfs when it does
|
|
3222
|
+
* not, which both hides any contents the host grows there later and makes
|
|
3223
|
+
* creating it fail.
|
|
3224
|
+
* - `file` — a file the host reads after the command exits. Bound read-only
|
|
3225
|
+
* when it exists: bubblewrap aborts the whole invocation on a tmpfs mounted
|
|
3226
|
+
* over a file path ("Not a directory"), so an absent file cannot be masked at
|
|
3227
|
+
* all.
|
|
3228
|
+
* - `hidden-file` — a file whose *contents* must not reach the command, bound
|
|
3229
|
+
* over with `/dev/null`. A read-only bind of `.book/` still serves what is in
|
|
3230
|
+
* it, and the namespace shares the host network, so a key the user stored
|
|
3231
|
+
* there would be one `cat` away from a sandboxed command.
|
|
3232
|
+
*/
|
|
3233
|
+
type ProtectedMountKind = 'directory' | 'file' | 'hidden-file';
|
|
3234
|
+
interface ProtectedMount {
|
|
3235
|
+
/**
|
|
3236
|
+
* The path the namespace mounts at, in the workspace's own spelling;
|
|
3237
|
+
* containment is decided on canonical paths.
|
|
3238
|
+
*/
|
|
3239
|
+
path: string;
|
|
3240
|
+
kind: ProtectedMountKind;
|
|
3241
|
+
/** False when nothing exists at it, which for a directory becomes a mask. */
|
|
3242
|
+
present: boolean;
|
|
3243
|
+
}
|
|
3244
|
+
interface ProtectedWorkspacePaths {
|
|
3245
|
+
/**
|
|
3246
|
+
* Directories the namespace must pin as mount points in their own right.
|
|
3247
|
+
*
|
|
3248
|
+
* A read-only child mount is not enough: the directory that holds it can still
|
|
3249
|
+
* be renamed away and replaced, and rename(2) on a path that is not a mount
|
|
3250
|
+
* point succeeds. `--bind <dir> <dir>` pins it, which is what makes
|
|
3251
|
+
* `mv .git .git.old` fail with EBUSY instead of redirecting the host's git.
|
|
3252
|
+
*
|
|
3253
|
+
* Only the workspace's own top-level `.git` is pinned, and only in the opt-out
|
|
3254
|
+
* mode (`allowGitWrites: true`) where git directories stay writable. Read-only
|
|
3255
|
+
* mode has no pin because the git directory is already a mount point through
|
|
3256
|
+
* its own `--ro-bind`, and the half-done `git worktree remove` it describes
|
|
3257
|
+
* (deleting the work tree and failing on the read-only admin directory) now
|
|
3258
|
+
* happens by design in read-only mode.
|
|
3259
|
+
*/
|
|
3260
|
+
pinnedDirectories: string[];
|
|
3261
|
+
mounts: ProtectedMount[];
|
|
3262
|
+
/**
|
|
3263
|
+
* Control paths this namespace cannot protect at all. A refusal rather than a
|
|
3264
|
+
* mount: bwrap aborts the whole invocation on the shapes that could be
|
|
3265
|
+
* mistaken for one, and guessing would leave a control file writable while
|
|
3266
|
+
* reporting a sandbox.
|
|
3267
|
+
*/
|
|
3268
|
+
refusals: ControlPathRefusal[];
|
|
3269
|
+
/**
|
|
3270
|
+
* Control mounts inside read-only git dirs (hooks, config, etc.), preserved in
|
|
3271
|
+
* read-only mode so an allowWrite opt-in on a git dir can re-apply them.
|
|
3272
|
+
*/
|
|
3273
|
+
gitDirControlMounts: ProtectedMount[];
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Why a workspace cannot be sandboxed, and which path says so.
|
|
3277
|
+
*
|
|
3278
|
+
* Both are shapes a repository controls and no mount can cover. The run is
|
|
3279
|
+
* refused with the path named, because the fix — replace the link, trim the
|
|
3280
|
+
* config — is a change to the workspace, not to a Book setting.
|
|
3281
|
+
*/
|
|
3282
|
+
type ControlPathRefusalReason = 'symlinked-control-path' | 'oversized-git-config' | 'workspace-root-is-git-dir';
|
|
3283
|
+
interface ControlPathRefusal {
|
|
3284
|
+
path: string;
|
|
3285
|
+
reason: ControlPathRefusalReason;
|
|
3286
|
+
}
|
|
2857
3287
|
|
|
2858
3288
|
interface SessionRuntimeOptions {
|
|
2859
3289
|
tasks?: AgentTask[];
|
|
@@ -2869,18 +3299,17 @@ interface SessionRuntimeOptions {
|
|
|
2869
3299
|
skillRegistry?: SkillRegistry;
|
|
2870
3300
|
/** Skill root discovery options, shared by the registry and the watcher so they agree. */
|
|
2871
3301
|
skillDiscoveryOptions?: DiscoverSkillsOptions;
|
|
2872
|
-
/**
|
|
2873
|
-
* The conversation this runtime is being built for. Its tool calls seed
|
|
2874
|
-
* `usedToolNames`, so every runtime built for a loaded, resumed, or rewound
|
|
2875
|
-
* conversation knows what that conversation already read.
|
|
2876
|
-
*/
|
|
2877
|
-
history?: Message[];
|
|
2878
3302
|
/**
|
|
2879
3303
|
* Whether this runtime is the session's long-lived one. `false` marks a runtime that is disposed
|
|
2880
3304
|
* when one run ends — a Task subagent's, a managed agent's — and so may not adopt a timed-out
|
|
2881
3305
|
* command as a background shell it would then destroy. Defaults to `true`.
|
|
2882
3306
|
*/
|
|
2883
3307
|
ownsSessionShells?: boolean;
|
|
3308
|
+
/**
|
|
3309
|
+
* The store instances of every agent manager this session has disposed. Hand on the outgoing
|
|
3310
|
+
* runtime's set to its replacement; see {@link SessionRuntime.disposedAgentInstanceIds}.
|
|
3311
|
+
*/
|
|
3312
|
+
disposedAgentInstanceIds?: Set<string>;
|
|
2884
3313
|
}
|
|
2885
3314
|
/** Mutable resources owned by one logical agent session. */
|
|
2886
3315
|
declare class SessionRuntime {
|
|
@@ -2897,6 +3326,18 @@ declare class SessionRuntime {
|
|
|
2897
3326
|
* Surfaced once in `<session-state>` so a dropped plan is a visible event.
|
|
2898
3327
|
*/
|
|
2899
3328
|
planUnrestored: boolean;
|
|
3329
|
+
/**
|
|
3330
|
+
* The session's `/goal`, if one is set. Read by the root loop each time the agent
|
|
3331
|
+
* stops without calling a tool; mutated there and by the host's `/goal` command.
|
|
3332
|
+
* A runtime is replaced on `/clear`, which is what clears the goal with it.
|
|
3333
|
+
*/
|
|
3334
|
+
activeGoal?: ActiveGoal;
|
|
3335
|
+
/**
|
|
3336
|
+
* The loaded memory snapshot whose `MEMORY.md` was last seeded into the observation ledger, so
|
|
3337
|
+
* the seed happens once per snapshot: a later turn must not claim the model has seen a file that
|
|
3338
|
+
* changed since. A reloaded snapshot (a new object) is seeded again, as a resumed session is.
|
|
3339
|
+
*/
|
|
3340
|
+
memoryIndexSeededFor?: object;
|
|
2900
3341
|
readonly backgroundShells: BackgroundShellStore;
|
|
2901
3342
|
readonly fileObservationLedger: Map<string, FileObservation>;
|
|
2902
3343
|
readonly toolDiscoveryState: ToolDiscoveryState;
|
|
@@ -2937,13 +3378,6 @@ declare class SessionRuntime {
|
|
|
2937
3378
|
* there, as it was before #302.
|
|
2938
3379
|
*/
|
|
2939
3380
|
readonly ownsSessionShells: boolean;
|
|
2940
|
-
/**
|
|
2941
|
-
* Canonical names of tools executed in this conversation, seeded from the
|
|
2942
|
-
* conversation it was built for. A runtime is replaced rather than cleared
|
|
2943
|
-
* when the host projects a different conversation, so this only ever grows
|
|
2944
|
-
* within one conversation.
|
|
2945
|
-
*/
|
|
2946
|
-
readonly usedToolNames: Set<string>;
|
|
2947
3381
|
/**
|
|
2948
3382
|
* Files this session's tool output was clipped into, as absolute paths.
|
|
2949
3383
|
*
|
|
@@ -2954,6 +3388,15 @@ declare class SessionRuntime {
|
|
|
2954
3388
|
*/
|
|
2955
3389
|
readonly clippedOutputPaths: Set<string>;
|
|
2956
3390
|
agentManager?: AgentManager;
|
|
3391
|
+
/**
|
|
3392
|
+
* The store instances of the agent managers this session has disposed, its earlier runtimes'
|
|
3393
|
+
* included: one set, handed from each runtime to the next by `AgentSession.replaceRuntime`.
|
|
3394
|
+
* Every manager built on this runtime gives it to its store, which adds its own instance when it
|
|
3395
|
+
* is disposed and reads each instance in it as gone, whatever its lease file says (#568). A lease
|
|
3396
|
+
* outlives its dispose when the unlink fails (a Windows scanner holding the file), and would read
|
|
3397
|
+
* as a live owner of the records that manager wrote for up to 15 s.
|
|
3398
|
+
*/
|
|
3399
|
+
readonly disposedAgentInstanceIds: Set<string>;
|
|
2957
3400
|
private readonly abortControllers;
|
|
2958
3401
|
private readonly timers;
|
|
2959
3402
|
private readonly childProcesses;
|
|
@@ -3025,6 +3468,13 @@ declare function runAgentLoop(config: AgentConfig, registry: ToolRegistry, userM
|
|
|
3025
3468
|
userMessageKind?: Message['kind'];
|
|
3026
3469
|
/** The prompt was resolved from a command or delegated, not typed by the user. */
|
|
3027
3470
|
userMessageDerived?: boolean;
|
|
3471
|
+
/**
|
|
3472
|
+
* The text the user typed for this turn, when the turn comes from their typing: what they sent,
|
|
3473
|
+
* or the command line that ran a command. Only its `$name` tokens invoke a skill by name; a
|
|
3474
|
+
* token anywhere else in the prompt (a command's body, an inlined file, a notification, a
|
|
3475
|
+
* delegated task) is plain text. Absent, no skill is invoked by name.
|
|
3476
|
+
*/
|
|
3477
|
+
typedText?: string;
|
|
3028
3478
|
/** Synthetic host notifications bypass user-authored prompt hooks and memory capture. */
|
|
3029
3479
|
skipUserPromptHooks?: boolean;
|
|
3030
3480
|
/** Host identity for each streamed assistant turn. */
|
|
@@ -3118,9 +3568,34 @@ declare function createAgentWorktree(snapshot: AgentSnapshot, agentId: string, w
|
|
|
3118
3568
|
declare function removeSnapshotRef(snapshot: AgentSnapshot): Promise<void>;
|
|
3119
3569
|
declare function checkoutAgentCommit(worktree: string, commit: string): Promise<void>;
|
|
3120
3570
|
declare function commitAgentWork(record: AgentRecord, snapshot: AgentSnapshot): Promise<PatchCandidate | undefined>;
|
|
3121
|
-
|
|
3571
|
+
/**
|
|
3572
|
+
* What a caller can say about how an apply may be stopped, beyond the `signal` it is given.
|
|
3573
|
+
*
|
|
3574
|
+
* `signal` stops the apply's **reads**: the candidate resolution, the pending-pick check, the
|
|
3575
|
+
* fingerprint, the delta, the pre-check and the signing configuration read. Those are where an
|
|
3576
|
+
* operator's slow `gpg.ssh.defaultKeyCommand` can hold an apply for up to 30 s per call (#491), and
|
|
3577
|
+
* none of them has written anything, so a stop there leaves nothing behind.
|
|
3578
|
+
*
|
|
3579
|
+
* `mutationSignal` stops the **mutation step**: the `cherry-pick`, or the `git apply -` of a dirty
|
|
3580
|
+
* workspace. It is separate because stopping that step is not free: a stop SIGTERMs the pick and
|
|
3581
|
+
* relies on the unsignalled `cherry-pick --abort` that follows to put the operator's repository
|
|
3582
|
+
* back, and a caller whose process exits right after it aborts (`dispose()`) never lets that
|
|
3583
|
+
* cleanup run. A caller that wants the reads stopped and nothing after them passes a signal here
|
|
3584
|
+
* that it never aborts. Absent, the mutation step uses `signal`, so a caller that passes one signal
|
|
3585
|
+
* gets one behaviour for all of it.
|
|
3586
|
+
*/
|
|
3587
|
+
interface ApplyCandidateOptions {
|
|
3588
|
+
mutationSignal?: AbortSignal;
|
|
3589
|
+
}
|
|
3590
|
+
declare function applyVerifiedCandidate(snapshot: AgentSnapshot, candidate: PatchCandidate, signal?: AbortSignal, options?: ApplyCandidateOptions): Promise<AgentApplyResult>;
|
|
3122
3591
|
|
|
3123
3592
|
type AtomicWriteOperation = 'lock' | 'serialize' | 'write' | 'fsync' | 'rename';
|
|
3593
|
+
/**
|
|
3594
|
+
* Outcome of an atomic JSON write.
|
|
3595
|
+
*
|
|
3596
|
+
* `attempts` counts how many times this write tried: 1 for a clean write, plus 1 for each contended
|
|
3597
|
+
* retry of either the lock step or the rename step. A stale-lock reclaim is not a retry.
|
|
3598
|
+
*/
|
|
3124
3599
|
type AtomicWriteResult = {
|
|
3125
3600
|
status: 'ok';
|
|
3126
3601
|
target: string;
|
|
@@ -3155,6 +3630,11 @@ type AtomicWriteResult = {
|
|
|
3155
3630
|
};
|
|
3156
3631
|
/** What one {@link AtomicJsonWriter.writeAsync} call is allowed to decide for itself. */
|
|
3157
3632
|
interface AtomicAsyncWriteOptions {
|
|
3633
|
+
/**
|
|
3634
|
+
* A complete temp file an earlier write left behind, renamed over the target instead of writing a
|
|
3635
|
+
* new one: the `preparedTemp` of {@link AtomicJsonWriter.write}.
|
|
3636
|
+
*/
|
|
3637
|
+
preparedTemp?: string;
|
|
3158
3638
|
/**
|
|
3159
3639
|
* Asked immediately before the rename, which is the only step of the write that changes the
|
|
3160
3640
|
* target.
|
|
@@ -3178,6 +3658,17 @@ interface AtomicJsonWriterOptions {
|
|
|
3178
3658
|
instanceId: string;
|
|
3179
3659
|
pid?: number;
|
|
3180
3660
|
hostname: string;
|
|
3661
|
+
/**
|
|
3662
|
+
* The budget of one write, counted on `now` from before the lock is taken: the lock, the
|
|
3663
|
+
* fsyncs of the lock and temp files, and the rename retries all spend it, so on a slow disk the
|
|
3664
|
+
* fsyncs alone can use it up. The budget bounds the back-off ladder, not the work already done,
|
|
3665
|
+
* so a rename that hits contention is always retried once after its back-off, even when the
|
|
3666
|
+
* budget is gone by then; after that retry the deadline applies and the write returns `busy`.
|
|
3667
|
+
* That back-off is the clamped ladder step while the budget lasts, and with little or none left
|
|
3668
|
+
* it is the first rename step at least and the current step at most. A write whose rename
|
|
3669
|
+
* fails every time therefore ends within the budget plus one such back-off. The lock phase has no such retry: a lock still contended at the
|
|
3670
|
+
* deadline is `busy`.
|
|
3671
|
+
*/
|
|
3181
3672
|
deadlineMs?: number;
|
|
3182
3673
|
now?: () => number;
|
|
3183
3674
|
randomId?: () => string;
|
|
@@ -3261,8 +3752,30 @@ declare class AtomicJsonWriter {
|
|
|
3261
3752
|
*
|
|
3262
3753
|
* {@link AtomicAsyncWriteOptions.canCommit} is the one thing this path has that the synchronous
|
|
3263
3754
|
* one does not, and it exists because this path is the one a caller can no longer wait for.
|
|
3755
|
+
* Without it the write cannot be withdrawn, so the result is never `cancelled`.
|
|
3264
3756
|
*/
|
|
3265
|
-
writeAsync(target: string, value: unknown, options?: AtomicAsyncWriteOptions): Promise<AtomicWriteResult
|
|
3757
|
+
writeAsync(target: string, value: unknown, options?: Omit<AtomicAsyncWriteOptions, 'canCommit'>): Promise<Exclude<AtomicWriteResult, {
|
|
3758
|
+
status: 'cancelled';
|
|
3759
|
+
}>>;
|
|
3760
|
+
writeAsync(target: string, value: unknown, options: AtomicAsyncWriteOptions): Promise<AtomicWriteResult>;
|
|
3761
|
+
/** What both write paths record in a lock file they take: who holds it, and since when. */
|
|
3762
|
+
private lockOwnerDocument;
|
|
3763
|
+
/** A temp file name unique to this writer, which recovery parses back to its target and owner. */
|
|
3764
|
+
private tempPathFor;
|
|
3765
|
+
/**
|
|
3766
|
+
* Whether a contended lock may be taken from its holder, given what its file said: a lock with a
|
|
3767
|
+
* valid owner only when that owner is gone, one without once it is older than the stale window.
|
|
3768
|
+
*/
|
|
3769
|
+
private mayReclaim;
|
|
3770
|
+
/**
|
|
3771
|
+
* How long to back off before `attempt`: the ladder step, cut short by the time left to the
|
|
3772
|
+
* deadline. `guaranteed` is the one retry past the deadline, after the first contended rename.
|
|
3773
|
+
* Cut to nothing, that retry would hit the same contention again; so it waits at least the first
|
|
3774
|
+
* rename step, whatever is left, and never more than the ladder step: the ladder is shared with
|
|
3775
|
+
* the lock phase, and a step it has climbed to could block the main thread for a long time past
|
|
3776
|
+
* the deadline.
|
|
3777
|
+
*/
|
|
3778
|
+
private backoffMs;
|
|
3266
3779
|
private pause;
|
|
3267
3780
|
private pauseAsync;
|
|
3268
3781
|
private reclaimStaleLockAsync;
|
|
@@ -3297,6 +3810,12 @@ interface AgentStoreOptions {
|
|
|
3297
3810
|
eventSink?: (event: AgentStorePersistenceEvent) => void;
|
|
3298
3811
|
writer?: AtomicJsonWriter;
|
|
3299
3812
|
writerOptions?: Omit<AtomicJsonWriterOptions, 'instanceId' | 'hostname'>;
|
|
3813
|
+
/**
|
|
3814
|
+
* The instances of the stores the session this one belongs to has disposed, shared rather than
|
|
3815
|
+
* copied: this store adds its own when it is disposed, and reads each one in it as gone, whatever
|
|
3816
|
+
* its lease says (#568). See {@link AgentStore.aliveByLease}.
|
|
3817
|
+
*/
|
|
3818
|
+
disposedInstanceIds?: Set<string>;
|
|
3300
3819
|
}
|
|
3301
3820
|
declare class AgentStore {
|
|
3302
3821
|
readonly directory: string;
|
|
@@ -3320,8 +3839,14 @@ declare class AgentStore {
|
|
|
3320
3839
|
private readonly processAlive;
|
|
3321
3840
|
private readonly eventSink?;
|
|
3322
3841
|
private readonly writer;
|
|
3842
|
+
private readonly disposedInstanceIds?;
|
|
3323
3843
|
private enabled;
|
|
3324
3844
|
private disposed;
|
|
3845
|
+
/**
|
|
3846
|
+
* Set while {@link open} loads the store. The writes `load` finds it needs are queued rather than
|
|
3847
|
+
* made, and `open` flushes them on the thread pool once loading is done.
|
|
3848
|
+
*/
|
|
3849
|
+
private opening;
|
|
3325
3850
|
private heartbeatTimer?;
|
|
3326
3851
|
/**
|
|
3327
3852
|
* The heartbeat write in flight, if any. See {@link refreshLease}: a tick that arrives while one
|
|
@@ -3337,13 +3862,53 @@ declare class AgentStore {
|
|
|
3337
3862
|
private readonly evidence;
|
|
3338
3863
|
private readonly snapshots;
|
|
3339
3864
|
private readonly pendingWrites;
|
|
3340
|
-
|
|
3865
|
+
/**
|
|
3866
|
+
* A store that has not touched the disk yet, and is not usable until it has: {@link open} is the
|
|
3867
|
+
* way to get one.
|
|
3868
|
+
*/
|
|
3869
|
+
private constructor();
|
|
3870
|
+
/**
|
|
3871
|
+
* Open the store for `repoHash` under `root`: create its directories, take this instance's lease
|
|
3872
|
+
* and start the heartbeat, then recover the temp files a crashed write left behind, load what is
|
|
3873
|
+
* persisted, and make the writes loading found it needed.
|
|
3874
|
+
*
|
|
3875
|
+
* All of that is on the thread pool. It is the first thing the TUI does after its first frame, and
|
|
3876
|
+
* done synchronously on a slow disk its `open`s and `fsync`s held the event loop for hundreds of
|
|
3877
|
+
* milliseconds: keys typed in that window arrived in one read and were taken for a paste (#516).
|
|
3878
|
+
*
|
|
3879
|
+
* The lease comes before the first read, because on the thread pool the reads no longer run in
|
|
3880
|
+
* one piece: a disposed manager's {@link recordLateApplication} can land between them. It
|
|
3881
|
+
* writes only when no other instance holds a live lease, so with the lease already on disk it
|
|
3882
|
+
* stands down; taken after loading, it found none, wrote a record this store had already read,
|
|
3883
|
+
* and this store's next save of that stale copy undid it.
|
|
3884
|
+
*
|
|
3885
|
+
* The promise resolves only once the lease is on disk, so no caller ever holds a store without one
|
|
3886
|
+
* (#357). A store whose directories cannot be created, or whose contents cannot be read, is not an
|
|
3887
|
+
* error: it resolves disabled, and runs in memory only.
|
|
3888
|
+
*/
|
|
3889
|
+
static open(repoHash: string, root?: string, enabled?: boolean, options?: AgentStoreOptions): Promise<AgentStore>;
|
|
3890
|
+
private initialize;
|
|
3891
|
+
/**
|
|
3892
|
+
* Turn an opening store whose contents could not be read into a disabled one: nothing of what it
|
|
3893
|
+
* read is kept, nothing it queued is written, and the lease it took is removed with its heartbeat,
|
|
3894
|
+
* since a disabled store writes nothing that a lease would speak for.
|
|
3895
|
+
*/
|
|
3896
|
+
private disableAfterFailedLoad;
|
|
3341
3897
|
private ensureDirectories;
|
|
3342
3898
|
private safeLog;
|
|
3899
|
+
private quarantinePath;
|
|
3343
3900
|
private quarantine;
|
|
3901
|
+
/** {@link quarantine}, on the thread pool, for what {@link open} reads. */
|
|
3902
|
+
private quarantineAsync;
|
|
3344
3903
|
private readJson;
|
|
3904
|
+
/** {@link readJson}, on the thread pool, for what {@link open} reads. */
|
|
3905
|
+
private readJsonAsync;
|
|
3345
3906
|
private targetMatchesId;
|
|
3346
3907
|
private loadDirectory;
|
|
3908
|
+
/**
|
|
3909
|
+
* Read what is persisted into memory. Runs only inside {@link open}, so the writes it finds it
|
|
3910
|
+
* needs are queued, and `open` makes them once it is done.
|
|
3911
|
+
*/
|
|
3347
3912
|
private load;
|
|
3348
3913
|
private migrateAgent;
|
|
3349
3914
|
private agentPath;
|
|
@@ -3358,6 +3923,11 @@ declare class AgentStore {
|
|
|
3358
3923
|
private cancelPendingWrite;
|
|
3359
3924
|
private queueWrite;
|
|
3360
3925
|
private flushPending;
|
|
3926
|
+
/** {@link flushPending} on the thread pool: the writes {@link open} makes. */
|
|
3927
|
+
private flushPendingAsync;
|
|
3928
|
+
private beginFlush;
|
|
3929
|
+
/** What both flushes do with the result: settle the pending write, or schedule its retry. */
|
|
3930
|
+
private finishFlush;
|
|
3361
3931
|
private schedulePending;
|
|
3362
3932
|
private enterDegraded;
|
|
3363
3933
|
private maybeRecover;
|
|
@@ -3371,6 +3941,45 @@ declare class AgentStore {
|
|
|
3371
3941
|
defer?: boolean;
|
|
3372
3942
|
required?: boolean;
|
|
3373
3943
|
}): AtomicWriteResult;
|
|
3944
|
+
/**
|
|
3945
|
+
* Record on disk how an apply that outlived its manager ended: the one fact that can only be
|
|
3946
|
+
* known after this store was disposed. A pick that finished after `dispose()` is on the
|
|
3947
|
+
* operator's branch, and nothing else records that.
|
|
3948
|
+
*
|
|
3949
|
+
* The session's next manager may have opened this store's directory by then and written the
|
|
3950
|
+
* same record (#518), so this is a field-level update rather than a save of the disposed
|
|
3951
|
+
* manager's copy: the record is re-read from disk and only its `applicationStatus` changes, with
|
|
3952
|
+
* the owner and `updatedAt` stamped as every write stamps them. Written to disk only, since the
|
|
3953
|
+
* disposed store's cache has no reader left.
|
|
3954
|
+
*
|
|
3955
|
+
* Nothing is written, and the record is left as it is, when another instance holds a live lease
|
|
3956
|
+
* here (the record is its to write now), when the record on disk is newer than `seenUpdatedAt`,
|
|
3957
|
+
* the `updatedAt` of the copy the disposed manager last had (someone wrote it since), or when
|
|
3958
|
+
* the record is not on disk.
|
|
3959
|
+
*
|
|
3960
|
+
* That is decided twice: once before the write, and again immediately before each rename, which
|
|
3961
|
+
* is the step that changes the file. Between the two are a lock, a temp file and its fsync, and a
|
|
3962
|
+
* save by the next manager or another process can land in that window (its writer reclaims the
|
|
3963
|
+
* lock of a store that is gone); the copy read before it would otherwise be renamed over it.
|
|
3964
|
+
*/
|
|
3965
|
+
recordLateApplication(agentId: string, status: AgentRecord['applicationStatus'], seenUpdatedAt: number): Promise<'recorded' | 'skipped' | 'failed'>;
|
|
3966
|
+
/**
|
|
3967
|
+
* What one write of {@link recordLateApplication} not landing makes of it, logged without any of
|
|
3968
|
+
* the record's content; `undefined` when the write landed.
|
|
3969
|
+
*/
|
|
3970
|
+
private lateWriteSetback;
|
|
3971
|
+
/**
|
|
3972
|
+
* Why a late write to the record at `path` must not land now, or `undefined` if it may: the skip
|
|
3973
|
+
* rule of {@link recordLateApplication}, with `newestAllowed` the `updatedAt` the record on disk
|
|
3974
|
+
* may have. Synchronous, because it is asked as the `canCommit` of a write, and an answer that
|
|
3975
|
+
* waited for the thread pool would be one the rename no longer immediately follows. It is a
|
|
3976
|
+
* directory listing and a few small reads, once per late apply.
|
|
3977
|
+
*/
|
|
3978
|
+
private lateWriteConflict;
|
|
3979
|
+
/** Whether an instance other than this one holds a live lease in `instances/`. */
|
|
3980
|
+
private anotherInstanceIsLive;
|
|
3981
|
+
/** Whether `lease`, as read from `instances/`, is another instance's, and live. */
|
|
3982
|
+
private isLiveForeignLease;
|
|
3374
3983
|
removeAgent(agentId: string): void;
|
|
3375
3984
|
removePlan(planId: string): void;
|
|
3376
3985
|
removeEvidence(evidenceId: string): void;
|
|
@@ -3383,6 +3992,11 @@ declare class AgentStore {
|
|
|
3383
3992
|
recoverAbandonedAgents(): AgentRecord[];
|
|
3384
3993
|
markActiveInterrupted(): AgentRecord[];
|
|
3385
3994
|
isOwnedByCurrent(agentId: string): boolean;
|
|
3995
|
+
/**
|
|
3996
|
+
* The instance that wrote the newest copy of the record this store knows, as
|
|
3997
|
+
* {@link isOwnedByLiveForeign} reads it.
|
|
3998
|
+
*/
|
|
3999
|
+
ownerInstanceId(agentId: string): string | undefined;
|
|
3386
4000
|
isOwnedByLiveForeign(agentId: string): boolean;
|
|
3387
4001
|
hasPendingAgent(agentId: string): boolean;
|
|
3388
4002
|
getPersistenceState(): 'healthy' | 'degraded_busy' | 'degraded_unavailable';
|
|
@@ -3392,13 +4006,16 @@ declare class AgentStore {
|
|
|
3392
4006
|
dispose(): void;
|
|
3393
4007
|
private refreshForeignSummaries;
|
|
3394
4008
|
/**
|
|
3395
|
-
* Take the lease for this instance, before
|
|
4009
|
+
* Take the lease for this instance, before this store reads anything and before anything else
|
|
4010
|
+
* reads it.
|
|
3396
4011
|
*
|
|
3397
|
-
*
|
|
3398
|
-
*
|
|
3399
|
-
*
|
|
3400
|
-
*
|
|
3401
|
-
*
|
|
4012
|
+
* {@link open} waits for this write before it resolves, and that order is the guarantee: a store
|
|
4013
|
+
* that has just been opened is asked whether its own instance is alive, and answered from this
|
|
4014
|
+
* file. A store handed out before its lease landed would have a window with no lease at all, which
|
|
4015
|
+
* is what a manager reading a just-created store saw when its first agent looked abandoned (#357).
|
|
4016
|
+
* The write itself is on the thread pool like the heartbeat's, because on a slow disk its lock,
|
|
4017
|
+
* fsyncs and rename are what held the TUI's event loop after its first frame (#516): the caller
|
|
4018
|
+
* waits for it, the event loop does not.
|
|
3402
4019
|
*
|
|
3403
4020
|
* A failure is not fatal here either, for the reason it is not fatal on the heartbeat: what is
|
|
3404
4021
|
* written is this instance's own ownership record, and every other write carries the same metadata.
|
|
@@ -3412,7 +4029,7 @@ declare class AgentStore {
|
|
|
3412
4029
|
* every one of them a disk round trip, all of it on whichever thread scheduled the timer — for the
|
|
3413
4030
|
* TUI, the event loop the keystrokes and the model stream are on. On a busy disk that is hundreds
|
|
3414
4031
|
* of milliseconds every five seconds, and it lands on the user rather than on the heartbeat
|
|
3415
|
-
* (#357). {@link takeLease} is the
|
|
4032
|
+
* (#357). {@link takeLease} is on the thread pool too, but awaited: see there.
|
|
3416
4033
|
*
|
|
3417
4034
|
* Two properties come with moving it off the thread and are the reason this is not simply a
|
|
3418
4035
|
* fire-and-forget:
|
|
@@ -3433,11 +4050,55 @@ declare class AgentStore {
|
|
|
3433
4050
|
private refreshLease;
|
|
3434
4051
|
private leaseDocument;
|
|
3435
4052
|
private removeLease;
|
|
4053
|
+
/**
|
|
4054
|
+
* {@link removeLease}, for a store that is done with its lease. A lease the unlink leaves behind
|
|
4055
|
+
* (a Windows scanner holding the file, say) reads as fresh for up to {@link LEASE_FRESH_MS}, so
|
|
4056
|
+
* it is overwritten with a dead one, best effort (#568). This session's own later stores read the
|
|
4057
|
+
* instance as gone either way: see {@link aliveByLease}.
|
|
4058
|
+
*/
|
|
4059
|
+
private releaseLease;
|
|
4060
|
+
/**
|
|
4061
|
+
* Overwrite this instance's lease with one that reads as dead to every store: a heartbeat stale on
|
|
4062
|
+
* any clock, and no process, so that a store of another process on this host does not fall back
|
|
4063
|
+
* to this one's pid, which is still alive (see {@link isLiveForeignLease}). In place, not by temp
|
|
4064
|
+
* file and rename like every other lease write: a file whose unlink was refused refuses a rename
|
|
4065
|
+
* onto it the same way, but may still take a write. A reader that catches the write half done
|
|
4066
|
+
* reads no lease, which is no fresher than a dead one.
|
|
4067
|
+
*/
|
|
4068
|
+
private markLeaseStale;
|
|
3436
4069
|
private scheduleHeartbeat;
|
|
3437
4070
|
private defaultProcessAlive;
|
|
3438
4071
|
private isInstanceAlive;
|
|
4072
|
+
/** {@link isInstanceAlive}, reading the lease on the thread pool, for {@link open}. */
|
|
4073
|
+
private isInstanceAliveAsync;
|
|
4074
|
+
private instanceLeasePath;
|
|
4075
|
+
/**
|
|
4076
|
+
* Whether an instance other than this one is alive, given its lease as read and the owner a
|
|
4077
|
+
* record names. Every caller has already answered for this store's own instance.
|
|
4078
|
+
*
|
|
4079
|
+
* A fresh lease is alive. Without one, an owner on this host is alive while its process is (a
|
|
4080
|
+
* Book from before leases, or one whose heartbeat is late), except when that process is this one
|
|
4081
|
+
* (#544). Every live store takes its lease before anything else and keeps it fresh until
|
|
4082
|
+
* `dispose()` removes it, so an instance of this process without one is taken for a disposed
|
|
4083
|
+
* sibling: the manager a `/clear`, `/resume` or `/rewind` replaced. Its records are the next
|
|
4084
|
+
* manager's to write and recover, and its locks are left over. No registry of disposed instances
|
|
4085
|
+
* is needed for that, and an earlier process whose pid this one now has reads as gone too, which
|
|
4086
|
+
* it is. The cost is a live sibling whose lease is missing or late: its first lease write failed,
|
|
4087
|
+
* or its heartbeat stalled past {@link LEASE_FRESH_MS}. It reads as gone until its next
|
|
4088
|
+
* heartbeat lands. Two live stores on one repository in one process come only from an SDK host
|
|
4089
|
+
* running sessions side by side; the TUI and print mode dispose a runtime before replacing it.
|
|
4090
|
+
*
|
|
4091
|
+
* What that rule misses is a disposed sibling whose lease outlived its dispose: the unlink failed
|
|
4092
|
+
* (a Windows scanner holding the file), and the lease reads as fresh for up to
|
|
4093
|
+
* {@link LEASE_FRESH_MS}. So an instance the session has disposed, one in
|
|
4094
|
+
* {@link AgentStoreOptions.disposedInstanceIds}, is gone whatever its lease says (#568). The set
|
|
4095
|
+
* holds only this session's own instances, so a live instance of another process, or one another
|
|
4096
|
+
* session in this process has not disposed, is judged as before.
|
|
4097
|
+
*/
|
|
4098
|
+
private aliveByLease;
|
|
3439
4099
|
private isLockOwnerAlive;
|
|
3440
4100
|
private parseTemp;
|
|
4101
|
+
/** Promote or discard the temp files crashed writes left behind. Runs only inside {@link open}. */
|
|
3441
4102
|
private recoverTemps;
|
|
3442
4103
|
}
|
|
3443
4104
|
|
|
@@ -3459,7 +4120,9 @@ interface ManagerOptions {
|
|
|
3459
4120
|
runtime?: SessionRuntime;
|
|
3460
4121
|
permissionMode?: string;
|
|
3461
4122
|
persistPermissionRule?: (rule: string) => void;
|
|
3462
|
-
createStore?: (repoHash: string, root: string | undefined, enabled: boolean
|
|
4123
|
+
createStore?: (repoHash: string, root: string | undefined, enabled: boolean,
|
|
4124
|
+
/** What the manager hands every store it opens: its session's disposed instances (#568). */
|
|
4125
|
+
options: Pick<AgentStoreOptions, 'disposedInstanceIds'>) => AgentStore | Promise<AgentStore>;
|
|
3463
4126
|
}
|
|
3464
4127
|
interface SubscribeOptions {
|
|
3465
4128
|
snapshot?: boolean;
|
|
@@ -3486,23 +4149,40 @@ declare class AgentManager {
|
|
|
3486
4149
|
private readonly snapshotPromises;
|
|
3487
4150
|
private readonly queue;
|
|
3488
4151
|
private readonly controllers;
|
|
4152
|
+
/**
|
|
4153
|
+
* Applies in flight, per agent. An apply runs after its agent has finished, so no run controller
|
|
4154
|
+
* covers it; `stop` and `dispose` abort these instead. A set because one agent can be applied
|
|
4155
|
+
* more than once at a time.
|
|
4156
|
+
*
|
|
4157
|
+
* Two controllers because the two ways in are not the same stop. `stop` ends the whole apply,
|
|
4158
|
+
* including a cherry-pick that is running, whose rollback it then waits for (`settled`).
|
|
4159
|
+
* `dispose` ends only the reads: it is synchronous and the process usually exits right after it,
|
|
4160
|
+
* so a pick it killed would never be rolled back and the operator's repository would be left
|
|
4161
|
+
* mid-pick. The pick that is running finishes, bounded by its own timeout.
|
|
4162
|
+
*/
|
|
4163
|
+
private readonly applies;
|
|
3489
4164
|
private readonly questionResolvers;
|
|
3490
4165
|
private readonly permissionResolvers;
|
|
3491
4166
|
private readonly permissionRules;
|
|
3492
4167
|
private readonly activities;
|
|
3493
4168
|
private readonly waiters;
|
|
3494
4169
|
private readonly idleWaiters;
|
|
4170
|
+
/** Settles each `wait` still pending, so a disposed manager leaves none of their timers armed. */
|
|
4171
|
+
private readonly pendingWaits;
|
|
3495
4172
|
private active;
|
|
3496
4173
|
private interactivePermissions;
|
|
3497
4174
|
private permissionMode;
|
|
3498
4175
|
private permissionModeOverride?;
|
|
3499
4176
|
private readonly subscribers;
|
|
4177
|
+
private readonly heldNotices;
|
|
3500
4178
|
private legacyEventSink?;
|
|
3501
4179
|
private readonly textBuffers;
|
|
3502
4180
|
private readonly textTimers;
|
|
3503
4181
|
private hookEventSink?;
|
|
3504
4182
|
private exitHandler?;
|
|
3505
4183
|
private disposed;
|
|
4184
|
+
/** The session's current manager, asked when an apply lands after dispose (#540). */
|
|
4185
|
+
private lateApplicationReceiver?;
|
|
3506
4186
|
private persistenceState;
|
|
3507
4187
|
constructor(config: AgentConfig, parentDefinitions: ToolDefinition[], options?: ManagerOptions);
|
|
3508
4188
|
setEventSink(sink?: (event: AgentRuntimeEvent) => void, hookSink?: (event: string, payload: Record<string, unknown>) => void): void;
|
|
@@ -3535,6 +4215,27 @@ declare class AgentManager {
|
|
|
3535
4215
|
private recordForReturn;
|
|
3536
4216
|
private assertMutable;
|
|
3537
4217
|
getPersistenceState(): 'healthy' | 'degraded_busy' | 'degraded_unavailable';
|
|
4218
|
+
/**
|
|
4219
|
+
* The user's home directory when this workspace is strictly below it, has no repository of its
|
|
4220
|
+
* own, and home holds a `.git`: the dotfiles repository Book has ignored since #572.
|
|
4221
|
+
*/
|
|
4222
|
+
private homeDotfilesAbove;
|
|
4223
|
+
/**
|
|
4224
|
+
* A project below the user's home with no repository of its own used to resolve to the home
|
|
4225
|
+
* directory's, so its records, worktrees and snapshot refs were keyed by it (#579). Since #572
|
|
4226
|
+
* that `.git` is a dotfiles repository Book ignores, and that state is no longer found. Say so
|
|
4227
|
+
* once, with the way to clear it; never read it back, and never move or delete it. Records carry
|
|
4228
|
+
* no workspace, so every record under the home key is counted, the home repository's own
|
|
4229
|
+
* included.
|
|
4230
|
+
*/
|
|
4231
|
+
private reportOrphanedHomeState;
|
|
4232
|
+
/**
|
|
4233
|
+
* The manager's own one-time line for the operator. Held until a host is listening, because the
|
|
4234
|
+
* first initialization can run before the TUI subscribes (or between its resubscribes), and a
|
|
4235
|
+
* notice nobody saw was not reported.
|
|
4236
|
+
*/
|
|
4237
|
+
private emitManagerNotice;
|
|
4238
|
+
private flushHeldNotices;
|
|
3538
4239
|
private handlePersistenceEvent;
|
|
3539
4240
|
/**
|
|
3540
4241
|
* Turn a write's result into either "it is on disk" or the error an operator sees, and report the
|
|
@@ -3593,8 +4294,20 @@ declare class AgentManager {
|
|
|
3593
4294
|
get(agentId: string): Promise<AgentRecord | undefined>;
|
|
3594
4295
|
dismiss(agentId: string): Promise<void>;
|
|
3595
4296
|
send(agentId: string, message: string, evidenceIds?: string[], runContext?: Pick<AgentRunContext, 'rootRunId' | 'runId'>): Promise<AgentRecord>;
|
|
3596
|
-
wait(agentId: string, timeoutMs?: number): Promise<AgentRecord>;
|
|
4297
|
+
wait(agentId: string, timeoutMs?: number, signal?: AbortSignal): Promise<AgentRecord>;
|
|
4298
|
+
/**
|
|
4299
|
+
* Stop an agent's run, and every apply of its result that is running.
|
|
4300
|
+
*
|
|
4301
|
+
* `keepUserApplies` is the model's `AgentStop` (#527): it stops only the applies the model
|
|
4302
|
+
* started with `AgentApply`, leaves running the ones the user started (`/agent apply`,
|
|
4303
|
+
* `/review --fix`), and says which it did with each. Every other caller is the user's own stop,
|
|
4304
|
+
* or a host's, and stops them all.
|
|
4305
|
+
*/
|
|
3597
4306
|
stop(agentId: string, reason?: string): Promise<AgentRecord>;
|
|
4307
|
+
stop(agentId: string, reason: string | undefined, options: {
|
|
4308
|
+
keepUserApplies: true;
|
|
4309
|
+
}): Promise<AgentStopOutcome>;
|
|
4310
|
+
private stopAgent;
|
|
3598
4311
|
publishEvidence(agentId: string, input: PublishEvidenceInput): Promise<EvidenceItem>;
|
|
3599
4312
|
listEvidence(options?: {
|
|
3600
4313
|
requesterAgentId?: string;
|
|
@@ -3602,7 +4315,63 @@ declare class AgentManager {
|
|
|
3602
4315
|
ids?: string[];
|
|
3603
4316
|
}): Promise<EvidenceItem[]>;
|
|
3604
4317
|
reviewEvidence(reviewerAgentId: string, evidenceId: string, verdict: 'pass' | 'fail' | 'inconclusive', notes?: string): Promise<EvidenceItem>;
|
|
3605
|
-
apply(agentId: string, evidenceId?: string
|
|
4318
|
+
apply(agentId: string, evidenceId?: string, options?: {
|
|
4319
|
+
signal?: AbortSignal;
|
|
4320
|
+
startedBy?: AgentApplyStarter;
|
|
4321
|
+
}): Promise<AgentApplyResult>;
|
|
4322
|
+
/** Record how an apply of `record`'s candidate ended: on the record, to subscribers, in telemetry. */
|
|
4323
|
+
private recordApplication;
|
|
4324
|
+
/**
|
|
4325
|
+
* Name the manager an apply that outlives this one hands its outcome to: the session's current
|
|
4326
|
+
* manager, asked when the pick lands rather than now, since a session can switch again in the
|
|
4327
|
+
* meantime and creates its manager lazily. Called where a session's runtime is replaced.
|
|
4328
|
+
*/
|
|
4329
|
+
handOffLateApplications(current: () => AgentManager | undefined): void;
|
|
4330
|
+
/**
|
|
4331
|
+
* Hand the outcome of an apply that outlived this manager to the session's current manager
|
|
4332
|
+
* (#540). True when that manager took it: it recorded the outcome, or left the record alone on
|
|
4333
|
+
* purpose. False when there is no such manager or the hand-off failed, and the caller's own late
|
|
4334
|
+
* write is what is left. A failure is logged with its code only, and never fails the apply,
|
|
4335
|
+
* whose pick is on the operator's branch either way.
|
|
4336
|
+
*/
|
|
4337
|
+
private handOverLateApplication;
|
|
4338
|
+
/**
|
|
4339
|
+
* Record the outcome of an apply that a disposed manager of this session started (#540).
|
|
4340
|
+
*
|
|
4341
|
+
* Only `applicationStatus` changes, on the record as this manager has it now, so its own later
|
|
4342
|
+
* saves keep it. The record is left alone when it is gone, when its candidate is no longer the
|
|
4343
|
+
* one applied (a re-run replaced it: `applied` would be false of the new one), or when another
|
|
4344
|
+
* live owner holds it (#518, {@link heldByLiveOwner}). A skip is logged rather than thrown,
|
|
4345
|
+
* because the pick is on the branch either way and nobody is waiting on this to fail.
|
|
4346
|
+
*
|
|
4347
|
+
* False when this manager was disposed before it could open its store, and took nothing.
|
|
4348
|
+
*/
|
|
4349
|
+
private receiveLateApplication;
|
|
4350
|
+
/**
|
|
4351
|
+
* Whether a live instance other than this one holds the record a late application is for, so
|
|
4352
|
+
* the application must leave it alone. Asked once the record has been read again. The owner it
|
|
4353
|
+
* compares is the one {@link AgentStore.isOwnedByLiveForeign} judges: the writer of the newest
|
|
4354
|
+
* copy the store knows, which is the one on disk unless the store holds a newer one. Two paths:
|
|
4355
|
+
*
|
|
4356
|
+
* - **Main (#544, #568):** an instance of this process without a fresh lease is a disposed one
|
|
4357
|
+
* and owns nothing, and so does one this session has disposed, whatever its lease file says.
|
|
4358
|
+
* That covers the manager handing the pick over, and any manager of this session disposed
|
|
4359
|
+
* after it that wrote the record in between, even when `dispose()` could not remove its lease
|
|
4360
|
+
* (a scanner holding the file) and the lease reads as fresh for up to 15 s.
|
|
4361
|
+
* - **Fallback, the handing-over instance:** it is disposed, whatever its lease file says. This
|
|
4362
|
+
* manager may not know the session's disposed instances: a host can build it by hand rather
|
|
4363
|
+
* than on the session's runtime. A pick landing while that lease reads as fresh would then be
|
|
4364
|
+
* skipped and the record say `not_applied` for good. So a record whose owner is
|
|
4365
|
+
* {@link LateApplication.fromInstanceId} is this manager's to write. No other instance is
|
|
4366
|
+
* exempt: a live owner keeps the record.
|
|
4367
|
+
*/
|
|
4368
|
+
private heldByLiveOwner;
|
|
4369
|
+
/**
|
|
4370
|
+
* One record as it is on disk now, read on its own rather than by listing the store: the copy
|
|
4371
|
+
* `get()` would take (this manager has none, another live instance owns it, or it is newer), or
|
|
4372
|
+
* else this manager's own, hydrated. `undefined` when the record is not on disk.
|
|
4373
|
+
*/
|
|
4374
|
+
private reloadAgent;
|
|
3606
4375
|
private pump;
|
|
3607
4376
|
private evidenceContext;
|
|
3608
4377
|
private systemPrompt;
|
|
@@ -3748,6 +4517,13 @@ interface ToolCall {
|
|
|
3748
4517
|
raw: string;
|
|
3749
4518
|
error: string;
|
|
3750
4519
|
};
|
|
4520
|
+
/**
|
|
4521
|
+
* Set when the call's arguments, joined across every streamed fragment, were empty or
|
|
4522
|
+
* whitespace on the wire: the name and id arrived and no argument text followed. `arguments`
|
|
4523
|
+
* is `{}` as for a literal `{}`, which this does not mark. The registry rejects such a call
|
|
4524
|
+
* as a route's wire shape when the tool has required arguments, and runs it with `{}` otherwise.
|
|
4525
|
+
*/
|
|
4526
|
+
emptyOnWire?: true;
|
|
3751
4527
|
}
|
|
3752
4528
|
/** Display-only trace for a tool invoked inside a Task subagent. */
|
|
3753
4529
|
interface NestedToolInvocation {
|
|
@@ -3765,7 +4541,10 @@ interface NestedToolObserver {
|
|
|
3765
4541
|
}
|
|
3766
4542
|
interface FileMutationSummary {
|
|
3767
4543
|
kind: 'create' | 'update' | 'delete';
|
|
4544
|
+
/** Workspace-relative; a memory file is `memory/<name>.md`, relative to the memory directory. */
|
|
3768
4545
|
filePath: string;
|
|
4546
|
+
/** The file is in the project memory directory (`ToolContext.memoryWrite`). */
|
|
4547
|
+
memory?: true;
|
|
3769
4548
|
addedLines: number;
|
|
3770
4549
|
removedLines: number;
|
|
3771
4550
|
}
|
|
@@ -3894,6 +4673,11 @@ interface ToolDefinition {
|
|
|
3894
4673
|
argumentAliases?: Record<string, string>;
|
|
3895
4674
|
/** Per-item argument aliases for array arguments (argument name → alias → canonical). */
|
|
3896
4675
|
arrayItemArgumentAliases?: Record<string, Record<string, string>>;
|
|
4676
|
+
/**
|
|
4677
|
+
* Cross-harness arguments with no effect here, dropped before validation rather than refused:
|
|
4678
|
+
* another harness's spelling for something this tool always does (Grep's `-n`).
|
|
4679
|
+
*/
|
|
4680
|
+
ignoredArguments?: readonly string[];
|
|
3897
4681
|
catalog?: ToolCatalogMetadata;
|
|
3898
4682
|
policy?: ToolPolicy;
|
|
3899
4683
|
/** When true, the tool is safe to retry once on transient failure (Read, Grep, WebFetch, etc.). */
|
|
@@ -3908,18 +4692,20 @@ interface ToolDefinition {
|
|
|
3908
4692
|
timeoutMs?: number | ((context: ToolContext) => number | undefined);
|
|
3909
4693
|
execute: (args: Record<string, unknown>, context: ToolContext) => Promise<ToolResult>;
|
|
3910
4694
|
}
|
|
3911
|
-
interface ReadOnlyRoot {
|
|
3912
|
-
root: string;
|
|
3913
|
-
/** Subpaths relative to root that must not be read (e.g. ['.inbox']). */
|
|
3914
|
-
exclude?: readonly string[];
|
|
3915
|
-
}
|
|
3916
4695
|
interface ToolContext {
|
|
3917
4696
|
workspaceRoot: string;
|
|
3918
4697
|
/**
|
|
3919
|
-
*
|
|
3920
|
-
*
|
|
4698
|
+
* Paths outside the workspace that the Read tool may read: the project memory directory and
|
|
4699
|
+
* the files this session clipped a result into.
|
|
4700
|
+
*/
|
|
4701
|
+
readOnlyRoots?: readonly string[];
|
|
4702
|
+
/**
|
|
4703
|
+
* The project memory directory as a *search* root: Glob and Grep reach the files in it (an
|
|
4704
|
+
* absolute pattern or `path`), as Read does through `readOnlyRoots`. Set for every agent that
|
|
4705
|
+
* can read memory, a subagent included. A dot-entry below it (`.format`, a backup) stays out of
|
|
4706
|
+
* a search's results.
|
|
3921
4707
|
*/
|
|
3922
|
-
|
|
4708
|
+
memorySearchRoot?: string;
|
|
3923
4709
|
/**
|
|
3924
4710
|
* Honored `additionalDirectories`, as resolved real roots the file tools may serve alongside
|
|
3925
4711
|
* the workspace: Read, Glob and Grep reach them, and so do the write tools' root check. A read
|
|
@@ -3928,6 +4714,25 @@ interface ToolContext {
|
|
|
3928
4714
|
* managed child and a subagent see the same roots as their parent.
|
|
3929
4715
|
*/
|
|
3930
4716
|
additionalRoots?: readonly string[];
|
|
4717
|
+
/**
|
|
4718
|
+
* The project memory directory as a *write* root, for the root agent only. Set by the agent loop
|
|
4719
|
+
* when memory is enabled with `autoSave` on, and never copied into a subagent or managed child:
|
|
4720
|
+
* a Task subagent runs in `bypassPermissions`, so the field's absence is what keeps it out of
|
|
4721
|
+
* memory. Write, Edit, MultiEdit and ApplyPatch then reach `*.md` files in `root` (see
|
|
4722
|
+
* `resolveMutationPath`), through the guard in `memory-write-guard.ts`.
|
|
4723
|
+
*
|
|
4724
|
+
* `scope` is `learnings` when another agent drives the session. `sessionId` stamps provenance.
|
|
4725
|
+
*/
|
|
4726
|
+
memoryWrite?: {
|
|
4727
|
+
root: string;
|
|
4728
|
+
scope: 'learnings' | 'full';
|
|
4729
|
+
sessionId?: string;
|
|
4730
|
+
};
|
|
4731
|
+
/**
|
|
4732
|
+
* The provider secrets the agent loop redacts from every tool result (#411), longest first.
|
|
4733
|
+
* `BashOutput` reads with them so a chunk never ends inside one. Populated by the agent loop.
|
|
4734
|
+
*/
|
|
4735
|
+
providerSecrets?: readonly string[];
|
|
3931
4736
|
env: Record<string, string>;
|
|
3932
4737
|
/** Explicit environment overrides safe to persist for opt-in persistent jobs. */
|
|
3933
4738
|
envOverrides?: Record<string, string>;
|
|
@@ -4000,12 +4805,16 @@ interface ToolContext {
|
|
|
4000
4805
|
onHookEvent?: (event: string, payload: Record<string, unknown>) => void;
|
|
4001
4806
|
/** Host sink for one-line informational notices (e.g. memory saved). */
|
|
4002
4807
|
onNotice?: (notice: string) => void;
|
|
4003
|
-
/** Canonical tool names invoked so far in this session. */
|
|
4004
|
-
usedToolNames?: Set<string>;
|
|
4005
4808
|
/** Per-session capability/discovery controller installed by the agent loop. */
|
|
4006
4809
|
toolDiscovery?: ToolDiscoveryContext;
|
|
4007
4810
|
/** Mutable resources owned by the current session, separate from configuration. */
|
|
4008
4811
|
runtime?: SessionRuntime;
|
|
4812
|
+
/** The conversation as the running loop holds it, for a tool that reads it whole (`Advisor`). */
|
|
4813
|
+
conversation?: () => readonly Message[];
|
|
4814
|
+
/** The system prompt of the request the running loop last sent, for the same tool. */
|
|
4815
|
+
systemPrompt?: () => string;
|
|
4816
|
+
/** Spend on a model the tool called itself (`AgentLoopCallbacks.onAuxiliaryUsage`). */
|
|
4817
|
+
onAuxiliaryUsage?: (usage: Usage, metadata: ProviderResponseMetadata, source: string) => void;
|
|
4009
4818
|
}
|
|
4010
4819
|
interface ToolSearchMatch {
|
|
4011
4820
|
name: string;
|
|
@@ -4160,6 +4969,9 @@ type AgentEvent = {
|
|
|
4160
4969
|
type: 'notice';
|
|
4161
4970
|
message: string;
|
|
4162
4971
|
title?: string;
|
|
4972
|
+
} | {
|
|
4973
|
+
type: 'goal_status';
|
|
4974
|
+
event: GoalStatusEvent;
|
|
4163
4975
|
} | {
|
|
4164
4976
|
type: 'error';
|
|
4165
4977
|
error: string;
|