@p4code/cli 0.1.19 → 0.1.21
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/dist/bin.mjs +1262 -55
- package/dist/client/assets/{DiffPanel-CseUmWvE.js → DiffPanel-2Gqo7b0N.js} +3 -3
- package/dist/client/assets/{FilePreviewPanel-Dm7l5FFh.js → FilePreviewPanel-Po3D7bHa.js} +5 -5
- package/dist/client/assets/PreviewPanel-ebSBxbsf.js +2 -0
- package/dist/client/assets/{angular-html-YobKPLun.js → angular-html-BhHAxMde.js} +2 -2
- package/dist/client/assets/{angular-ts-Ac3I6TFV.js → angular-ts-CxZFyQM4.js} +2 -2
- package/dist/client/assets/{apl-Dwl73GTS.js → apl-D8rOr4fH.js} +2 -2
- package/dist/client/assets/arrow-right-Dseaz2v4.js +2 -0
- package/dist/client/assets/{astro-KBgldnxZ.js → astro-DYSYdOU9.js} +2 -2
- package/dist/client/assets/{blade-DTU2NGhh.js → blade-DaHnYPa_.js} +2 -2
- package/dist/client/assets/{c-COF4kWCp.js → c-oRWOBveW.js} +2 -2
- package/dist/client/assets/{cobol-BA7JsdNr.js → cobol-Djebq6RA.js} +2 -2
- package/dist/client/assets/{coffee-PFN_w4qL.js → coffee-CKhxS88r.js} +2 -2
- package/dist/client/assets/{cpp-CpT6w-g5.js → cpp-BjVgWq1h.js} +2 -2
- package/dist/client/assets/{crystal-BrlaRT0B.js → crystal-DXzIaEKx.js} +2 -2
- package/dist/client/assets/{css-C2LMO3MX.js → css-7AulbBAq.js} +2 -2
- package/dist/client/assets/{edge-DBkQ-Ajd.js → edge-B3VgcX6t.js} +2 -2
- package/dist/client/assets/{elixir-EbSNDEx0.js → elixir-DGUU7Wwf.js} +2 -2
- package/dist/client/assets/{elm-CSs0Wzy2.js → elm-B9tr8qBJ.js} +2 -2
- package/dist/client/assets/{erb-BBmMQG9b.js → erb-BgSl2c_f.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-CifvTzWP.js → fileCommentAnnotations-C06ZwrTh.js} +2 -2
- package/dist/client/assets/{git-rebase-C09BpZDw.js → git-rebase-CJMhrhmS.js} +2 -2
- package/dist/client/assets/{glimmer-js-D4Bqn6Yg.js → glimmer-js-Dzf5VMSx.js} +2 -2
- package/dist/client/assets/{glimmer-ts-fLCHL8o6.js → glimmer-ts-CHD8gl5-.js} +2 -2
- package/dist/client/assets/{glsl-17sV9oGm.js → glsl-CDpKClnF.js} +2 -2
- package/dist/client/assets/{graphql-BqFquulN.js → graphql-DX6Ul3G5.js} +2 -2
- package/dist/client/assets/{hack-D6Jpv-j9.js → hack-NU-7p0gC.js} +2 -2
- package/dist/client/assets/{haml-D0eo8PEJ.js → haml-Cf7Y-ZgP.js} +2 -2
- package/dist/client/assets/{handlebars-HG0XNkEg.js → handlebars-BOvzNDfr.js} +2 -2
- package/dist/client/assets/{html-Bh3padJ5.js → html-CokKN6Uo.js} +2 -2
- package/dist/client/assets/{html-derivative-DVHw4MNX.js → html-derivative-CcTOncd6.js} +2 -2
- package/dist/client/assets/{http-CYCXaOA5.js → http-CGr88I6S.js} +2 -2
- package/dist/client/assets/{hurl-D4oK46oh.js → hurl-BfdIF20D.js} +2 -2
- package/dist/client/assets/index-BwGqPoA8.js +847 -0
- package/dist/client/assets/index-eSuLivhH.css +1 -0
- package/dist/client/assets/{java-nvFYZOCN.js → java-DjcrYPKU.js} +2 -2
- package/dist/client/assets/{javascript-CMBxjf-M.js → javascript-C1msiELJ.js} +2 -2
- package/dist/client/assets/{jinja-vsPdkFFj.js → jinja-BP-z74Sl.js} +2 -2
- package/dist/client/assets/{jison-uky2pPNE.js → jison-DTU_ib6_.js} +2 -2
- package/dist/client/assets/{json-DOSqa2F1.js → json-n2oR8huX.js} +2 -2
- package/dist/client/assets/{jsx-Ci7CVNQe.js → jsx-DLc8ZGPF.js} +2 -2
- package/dist/client/assets/{julia-J2m7N3_R.js → julia-BFtTfktg.js} +2 -2
- package/dist/client/assets/{just-BDy3oM_N.js → just-CBZfmiGm.js} +2 -2
- package/dist/client/assets/{latex-CMD49F7D.js → latex-B9jcgQKp.js} +2 -2
- package/dist/client/assets/{liquid-Dq6k2YW6.js → liquid-D-PCb97a.js} +2 -2
- package/dist/client/assets/{lua-CohtP6J8.js → lua-CeAbwjz7.js} +2 -2
- package/dist/client/assets/{marko-C4V2zw1t.js → marko-D2oCpzm8.js} +2 -2
- package/dist/client/assets/{mdc-q1ZKjwYt.js → mdc-BN4ff7dr.js} +2 -2
- package/dist/client/assets/{nginx-D2TdEIZ3.js → nginx-TigpgX3q.js} +2 -2
- package/dist/client/assets/{nim-BtWKpYQs.js → nim-BbkIxZP5.js} +2 -2
- package/dist/client/assets/{perl-Ci4Pso9h.js → perl-Dq3UNlmA.js} +2 -2
- package/dist/client/assets/{php-CMzpcAju.js → php-CDBbIDUr.js} +2 -2
- package/dist/client/assets/{pug-D0sbFhP6.js → pug-X1om8_0C.js} +2 -2
- package/dist/client/assets/{qml-BfWXHzNc.js → qml-C_aweCjl.js} +2 -2
- package/dist/client/assets/{r-BN4rQDnz.js → r-BH02pZo7.js} +2 -2
- package/dist/client/assets/{razor-D5iw68yr.js → razor-DFd9duVt.js} +2 -2
- package/dist/client/assets/{regexp-Ba0WieCb.js → regexp-BYgWQcZZ.js} +2 -2
- package/dist/client/assets/{rst-DQqlRfnq.js → rst-DS4ajG1_.js} +2 -2
- package/dist/client/assets/{ruby-CReh9Gta.js → ruby-rrP3E-yX.js} +2 -2
- package/dist/client/assets/{sas-CJ-Cpd8L.js → sas-qOp3AbMp.js} +2 -2
- package/dist/client/assets/{scss-CZp-cH6d.js → scss-BePdknOf.js} +2 -2
- package/dist/client/assets/{shellscript-hCSuhhJZ.js → shellscript-Di7DOIUv.js} +2 -2
- package/dist/client/assets/{shellsession-BnDNcCDQ.js → shellsession-DS-qEJoT.js} +2 -2
- package/dist/client/assets/{soy-BXarcWJY.js → soy-CT_pCK5o.js} +2 -2
- package/dist/client/assets/{sql-ADQCXhYd.js → sql-CVynNG6j.js} +2 -2
- package/dist/client/assets/{stata-DSS4IpqF.js → stata-B2fKxQHN.js} +2 -2
- package/dist/client/assets/{surrealql-DuYcftNm.js → surrealql-BOcosx9e.js} +2 -2
- package/dist/client/assets/{svelte-CAXOFInl.js → svelte-BQBQ3ysY.js} +2 -2
- package/dist/client/assets/{templ-BvTIEIah.js → templ-cinsfbYE.js} +2 -2
- package/dist/client/assets/{tex-DpnVRTMM.js → tex-PiJ0CI8J.js} +2 -2
- package/dist/client/assets/textarea-DV6gvhvo.js +46 -0
- package/dist/client/assets/{ts-tags-4wlHlovT.js → ts-tags-jRjqE2jk.js} +2 -2
- package/dist/client/assets/{tsx-CHDgELdy.js → tsx-DqzTbomV.js} +2 -2
- package/dist/client/assets/{twig-DPmx2UHF.js → twig-5U1Vd9zl.js} +2 -2
- package/dist/client/assets/{typescript-CHO8bW-H.js → typescript-DmYNCX8k.js} +2 -2
- package/dist/client/assets/{vue-CM19I03n.js → vue-BOE7yHne.js} +2 -2
- package/dist/client/assets/{vue-html-ginfkxGQ.js → vue-html-roHe5KnN.js} +2 -2
- package/dist/client/assets/{vue-vine-CGdZxUwf.js → vue-vine-YC-R0SEx.js} +2 -2
- package/dist/client/assets/{xml-B8CY16OL.js → xml-B-QQaugn.js} +2 -2
- package/dist/client/assets/{xsl-COHh7n8v.js → xsl-CyPKxPW7.js} +2 -2
- package/dist/client/assets/{yaml-DXx-djat.js → yaml-Bq2pe1E4.js} +2 -2
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/PreviewPanel-DOyykNL9.js +0 -2
- package/dist/client/assets/arrow-right-BqDszij1.js +0 -2
- package/dist/client/assets/index-CV_dK79d.js +0 -783
- package/dist/client/assets/index-Dol3ssFp.css +0 -1
- package/dist/client/assets/textarea-etKSOtDN.js +0 -46
package/dist/bin.mjs
CHANGED
|
@@ -233,7 +233,7 @@ const make$76 = () => {
|
|
|
233
233
|
const layer$72 = Layer.sync(NetService, make$76);
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region package.json
|
|
236
|
-
var version = "0.1.
|
|
236
|
+
var version = "0.1.21";
|
|
237
237
|
//#endregion
|
|
238
238
|
//#region src/config.ts
|
|
239
239
|
/**
|
|
@@ -1838,6 +1838,7 @@ const OrchestrationThread = Schema$1.Struct({
|
|
|
1838
1838
|
runtimeMode: RuntimeMode,
|
|
1839
1839
|
interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
|
|
1840
1840
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
1841
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
1841
1842
|
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
1842
1843
|
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
1843
1844
|
latestTurn: Schema$1.NullOr(OrchestrationLatestTurn),
|
|
@@ -1879,6 +1880,7 @@ const OrchestrationThreadShell = Schema$1.Struct({
|
|
|
1879
1880
|
runtimeMode: RuntimeMode,
|
|
1880
1881
|
interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
|
|
1881
1882
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
1883
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
1882
1884
|
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
1883
1885
|
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
1884
1886
|
latestTurn: Schema$1.NullOr(OrchestrationLatestTurn),
|
|
@@ -2002,6 +2004,7 @@ const ThreadCreateCommand = Schema$1.Struct({
|
|
|
2002
2004
|
runtimeMode: RuntimeMode,
|
|
2003
2005
|
interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
|
|
2004
2006
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2007
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2005
2008
|
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
2006
2009
|
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
2007
2010
|
createdAt: IsoDateTime
|
|
@@ -2075,6 +2078,18 @@ const ThreadCompressModeSetCommand = Schema$1.Struct({
|
|
|
2075
2078
|
compressMode: CompressMode,
|
|
2076
2079
|
createdAt: IsoDateTime
|
|
2077
2080
|
});
|
|
2081
|
+
/**
|
|
2082
|
+
* No decoding default on the value, matching the compress command beside it: a
|
|
2083
|
+
* command is an instruction someone issued, and defaulting the very field it
|
|
2084
|
+
* exists to carry would turn a malformed dispatch into a silent write.
|
|
2085
|
+
*/
|
|
2086
|
+
const ThreadUnpromptedSubagentsSetCommand = Schema$1.Struct({
|
|
2087
|
+
type: Schema$1.Literal("thread.unprompted-subagents.set"),
|
|
2088
|
+
commandId: CommandId,
|
|
2089
|
+
threadId: ThreadId,
|
|
2090
|
+
unpromptedSubagents: Schema$1.Boolean,
|
|
2091
|
+
createdAt: IsoDateTime
|
|
2092
|
+
});
|
|
2078
2093
|
const ThreadTurnStartBootstrapCreateThread = Schema$1.Struct({
|
|
2079
2094
|
projectId: ProjectId,
|
|
2080
2095
|
title: TrimmedNonEmptyString,
|
|
@@ -2082,6 +2097,7 @@ const ThreadTurnStartBootstrapCreateThread = Schema$1.Struct({
|
|
|
2082
2097
|
runtimeMode: RuntimeMode,
|
|
2083
2098
|
interactionMode: ProviderInteractionMode,
|
|
2084
2099
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2100
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2085
2101
|
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
2086
2102
|
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
2087
2103
|
createdAt: IsoDateTime
|
|
@@ -2112,6 +2128,7 @@ const ThreadTurnStartCommand = Schema$1.Struct({
|
|
|
2112
2128
|
runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
|
|
2113
2129
|
interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
|
|
2114
2130
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2131
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2115
2132
|
bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
|
|
2116
2133
|
sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
|
|
2117
2134
|
createdAt: IsoDateTime
|
|
@@ -2131,6 +2148,7 @@ const ClientThreadTurnStartCommand = Schema$1.Struct({
|
|
|
2131
2148
|
runtimeMode: RuntimeMode,
|
|
2132
2149
|
interactionMode: ProviderInteractionMode,
|
|
2133
2150
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2151
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2134
2152
|
bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
|
|
2135
2153
|
sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
|
|
2136
2154
|
createdAt: IsoDateTime
|
|
@@ -2187,6 +2205,7 @@ const DispatchableClientOrchestrationCommand = Schema$1.Union([
|
|
|
2187
2205
|
ThreadRuntimeModeSetCommand,
|
|
2188
2206
|
ThreadInteractionModeSetCommand,
|
|
2189
2207
|
ThreadCompressModeSetCommand,
|
|
2208
|
+
ThreadUnpromptedSubagentsSetCommand,
|
|
2190
2209
|
ThreadTurnStartCommand,
|
|
2191
2210
|
ThreadTurnInterruptCommand,
|
|
2192
2211
|
ThreadApprovalRespondCommand,
|
|
@@ -2210,6 +2229,7 @@ const ClientOrchestrationCommand = Schema$1.Union([
|
|
|
2210
2229
|
ThreadRuntimeModeSetCommand,
|
|
2211
2230
|
ThreadInteractionModeSetCommand,
|
|
2212
2231
|
ThreadCompressModeSetCommand,
|
|
2232
|
+
ThreadUnpromptedSubagentsSetCommand,
|
|
2213
2233
|
ClientThreadTurnStartCommand,
|
|
2214
2234
|
ThreadTurnInterruptCommand,
|
|
2215
2235
|
ThreadApprovalRespondCommand,
|
|
@@ -2301,6 +2321,7 @@ const OrchestrationEventType = Schema$1.Literals([
|
|
|
2301
2321
|
"thread.runtime-mode-set",
|
|
2302
2322
|
"thread.interaction-mode-set",
|
|
2303
2323
|
"thread.compress-mode-set",
|
|
2324
|
+
"thread.unprompted-subagents-set",
|
|
2304
2325
|
"thread.message-sent",
|
|
2305
2326
|
"thread.turn-start-requested",
|
|
2306
2327
|
"thread.turn-interrupt-requested",
|
|
@@ -2351,6 +2372,7 @@ const ThreadCreatedPayload$1 = Schema$1.Struct({
|
|
|
2351
2372
|
runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
|
|
2352
2373
|
interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
|
|
2353
2374
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2375
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2354
2376
|
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
2355
2377
|
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
2356
2378
|
createdAt: IsoDateTime,
|
|
@@ -2413,6 +2435,16 @@ const ThreadCompressModeSetPayload$1 = Schema$1.Struct({
|
|
|
2413
2435
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2414
2436
|
updatedAt: IsoDateTime
|
|
2415
2437
|
});
|
|
2438
|
+
/**
|
|
2439
|
+
* One thread's subagent policy changed. Its own event rather than a field on
|
|
2440
|
+
* the compress one, for the reason every event here is narrow: replaying a
|
|
2441
|
+
* combined event would rewrite whichever half the user did not touch.
|
|
2442
|
+
*/
|
|
2443
|
+
const ThreadUnpromptedSubagentsSetPayload$1 = Schema$1.Struct({
|
|
2444
|
+
threadId: ThreadId,
|
|
2445
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2446
|
+
updatedAt: IsoDateTime
|
|
2447
|
+
});
|
|
2416
2448
|
const ThreadMessageSentPayload = Schema$1.Struct({
|
|
2417
2449
|
threadId: ThreadId,
|
|
2418
2450
|
messageId: MessageId,
|
|
@@ -2432,6 +2464,7 @@ const ThreadTurnStartRequestedPayload = Schema$1.Struct({
|
|
|
2432
2464
|
runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
|
|
2433
2465
|
interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
|
|
2434
2466
|
compressMode: CompressMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_COMPRESS_MODE))),
|
|
2467
|
+
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
2435
2468
|
sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
|
|
2436
2469
|
createdAt: IsoDateTime
|
|
2437
2470
|
});
|
|
@@ -2581,6 +2614,11 @@ const OrchestrationEvent = Schema$1.Union([
|
|
|
2581
2614
|
type: Schema$1.Literal("thread.compress-mode-set"),
|
|
2582
2615
|
payload: ThreadCompressModeSetPayload$1
|
|
2583
2616
|
}),
|
|
2617
|
+
Schema$1.Struct({
|
|
2618
|
+
...EventBaseFields,
|
|
2619
|
+
type: Schema$1.Literal("thread.unprompted-subagents-set"),
|
|
2620
|
+
payload: ThreadUnpromptedSubagentsSetPayload$1
|
|
2621
|
+
}),
|
|
2584
2622
|
Schema$1.Struct({
|
|
2585
2623
|
...EventBaseFields,
|
|
2586
2624
|
type: Schema$1.Literal("thread.message-sent"),
|
|
@@ -4587,7 +4625,8 @@ const ProviderSessionStartInput = Schema$1.Struct({
|
|
|
4587
4625
|
approvalPolicy: Schema$1.optional(ProviderApprovalPolicy),
|
|
4588
4626
|
sandboxMode: Schema$1.optional(ProviderSandboxMode),
|
|
4589
4627
|
runtimeMode: RuntimeMode,
|
|
4590
|
-
compressMode: Schema$1.optional(CompressMode)
|
|
4628
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
4629
|
+
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean)
|
|
4591
4630
|
});
|
|
4592
4631
|
const ProviderSendTurnInput = Schema$1.Struct({
|
|
4593
4632
|
threadId: ThreadId,
|
|
@@ -4595,7 +4634,8 @@ const ProviderSendTurnInput = Schema$1.Struct({
|
|
|
4595
4634
|
attachments: Schema$1.optional(Schema$1.Array(ChatAttachment).check(Schema$1.isMaxLength(8))),
|
|
4596
4635
|
modelSelection: Schema$1.optional(ModelSelection),
|
|
4597
4636
|
interactionMode: Schema$1.optional(ProviderInteractionMode),
|
|
4598
|
-
compressMode: Schema$1.optional(CompressMode)
|
|
4637
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
4638
|
+
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean)
|
|
4599
4639
|
});
|
|
4600
4640
|
Schema$1.Struct({
|
|
4601
4641
|
threadId: ThreadId,
|
|
@@ -5853,6 +5893,45 @@ const ServerSettings = Schema$1.Struct({
|
|
|
5853
5893
|
* instruction can override an earlier general one.
|
|
5854
5894
|
*/
|
|
5855
5895
|
enableUnpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
5896
|
+
/**
|
|
5897
|
+
* Whether the board offers the scoping agent: the Scope action on a task and
|
|
5898
|
+
* the panel that drafts a new one by interview.
|
|
5899
|
+
*
|
|
5900
|
+
* A switch rather than a permanent fixture because the discipline it imposes
|
|
5901
|
+
* — one question at a time, no file edits, a task body with verifiable
|
|
5902
|
+
* criteria — is a way of working, not a fact about the product. Someone who
|
|
5903
|
+
* writes their own tasks wants the button gone, not ignored.
|
|
5904
|
+
*
|
|
5905
|
+
* Off hides the entry points. It does not delete the skill file already
|
|
5906
|
+
* written into a repository, and it does not stop anyone invoking the same
|
|
5907
|
+
* discipline by name from an ordinary chat: that is what {@link disabledSkills}
|
|
5908
|
+
* is for.
|
|
5909
|
+
*/
|
|
5910
|
+
enableScopingAgent: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
5911
|
+
/**
|
|
5912
|
+
* Whether the board offers the plan phase: splitting one scoped task into
|
|
5913
|
+
* ordered subtasks. Separate from {@link enableScopingAgent} because the two
|
|
5914
|
+
* are useful apart — scoping a task by interview says nothing about wanting
|
|
5915
|
+
* it decomposed, and a board that plans everything into five rows is a board
|
|
5916
|
+
* nobody reads.
|
|
5917
|
+
*/
|
|
5918
|
+
enablePlanPhase: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
5919
|
+
/**
|
|
5920
|
+
* Whether the scoping and planning flows write their generated `SKILL.md`
|
|
5921
|
+
* into the repository they run in.
|
|
5922
|
+
*
|
|
5923
|
+
* Its own switch because it is the only one of the three with a side effect
|
|
5924
|
+
* outside p4code: the file lands in the working tree and shows up in
|
|
5925
|
+
* `git status`, which is unwelcome in a repo whose `.claude/` is checked in
|
|
5926
|
+
* and shared. Off does not cost the discipline — the thread seed carries the
|
|
5927
|
+
* rules inline instead, exactly as the create flow already does for providers
|
|
5928
|
+
* with no notion of a skill — it costs only invoking it by name later from an
|
|
5929
|
+
* ordinary chat.
|
|
5930
|
+
*
|
|
5931
|
+
* Files already written are left alone. Nothing here deletes anything from a
|
|
5932
|
+
* user's repository.
|
|
5933
|
+
*/
|
|
5934
|
+
writeGeneratedSkillsToRepo: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
5856
5935
|
automaticGitFetchInterval: Schema$1.DurationFromMillis.pipe(Schema$1.withDecodingDefault(Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)))),
|
|
5857
5936
|
defaultThreadEnvMode: ThreadEnvMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("local"))),
|
|
5858
5937
|
newWorktreesStartFromOrigin: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
@@ -5988,6 +6067,9 @@ const ServerSettingsPatch = Schema$1.Struct({
|
|
|
5988
6067
|
enableProviderUpdateChecks: Schema$1.optionalKey(Schema$1.Boolean),
|
|
5989
6068
|
enableToolCallNarration: Schema$1.optionalKey(Schema$1.Boolean),
|
|
5990
6069
|
enableUnpromptedSubagents: Schema$1.optionalKey(Schema$1.Boolean),
|
|
6070
|
+
enableScopingAgent: Schema$1.optionalKey(Schema$1.Boolean),
|
|
6071
|
+
enablePlanPhase: Schema$1.optionalKey(Schema$1.Boolean),
|
|
6072
|
+
writeGeneratedSkillsToRepo: Schema$1.optionalKey(Schema$1.Boolean),
|
|
5991
6073
|
automaticGitFetchInterval: Schema$1.optionalKey(Schema$1.DurationFromMillis),
|
|
5992
6074
|
defaultThreadEnvMode: Schema$1.optionalKey(ThreadEnvMode),
|
|
5993
6075
|
newWorktreesStartFromOrigin: Schema$1.optionalKey(Schema$1.Boolean),
|
|
@@ -7263,6 +7345,21 @@ const TaskExternalRef = Schema$1.Struct({
|
|
|
7263
7345
|
url: TrimmedNonEmptyString
|
|
7264
7346
|
});
|
|
7265
7347
|
const ExternalRefField = TaskExternalRef.pipe(Schema$1.annotateEncoded({ description: "The tracker issue this task mirrors, e.g. a Linear ticket. Null for a task that originated on this board." }));
|
|
7348
|
+
/**
|
|
7349
|
+
* The task this one was split out of.
|
|
7350
|
+
*
|
|
7351
|
+
* Carried as a field rather than as a sentence in the body because the plan
|
|
7352
|
+
* phase writes it and the board reads it: a parent chip, a subtask list and
|
|
7353
|
+
* "does this still have unfinished children" are all questions about the graph,
|
|
7354
|
+
* and prose cannot be asked them. Stored as the parent's UUID, but accepted as
|
|
7355
|
+
* either a UUID or a readable id on the way in - a planning agent works in
|
|
7356
|
+
* `P4-12`, and making it look up a UUID first is a round trip for nothing.
|
|
7357
|
+
*
|
|
7358
|
+
* One level is all this promises. Nothing forbids a subtask of a subtask, and
|
|
7359
|
+
* nothing walks the chain either; the board renders the direct children of a
|
|
7360
|
+
* task and stops.
|
|
7361
|
+
*/
|
|
7362
|
+
const ParentTaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "The task this one is a subtask of - the parent's UUID or readable id (e.g. P4-12). Null for a task that is not part of a larger one." }));
|
|
7266
7363
|
const LabelsField = Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)).pipe(Schema$1.annotateEncoded({ description: "Free-form tags used to group and filter tasks." }));
|
|
7267
7364
|
const TaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "Id of the task to act on - the UUID or the readable id (e.g. P4-12)." }));
|
|
7268
7365
|
const Task = Schema$1.Struct({
|
|
@@ -7311,6 +7408,13 @@ const Task = Schema$1.Struct({
|
|
|
7311
7408
|
* `null` rather than failing the whole board.
|
|
7312
7409
|
*/
|
|
7313
7410
|
externalRef: Schema$1.NullOr(TaskExternalRef).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
7411
|
+
/**
|
|
7412
|
+
* The task this one was split out of, always as a UUID once stored - a
|
|
7413
|
+
* readable id given on the way in is resolved before the row is written, so
|
|
7414
|
+
* the link survives a board where readable ids were allocated elsewhere.
|
|
7415
|
+
* Nullable-with-default for the same version-skew reason as `readableId`.
|
|
7416
|
+
*/
|
|
7417
|
+
parentTaskId: Schema$1.NullOr(TaskId).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
7314
7418
|
title: TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_TITLE_MAX_LENGTH)),
|
|
7315
7419
|
body: Schema$1.String,
|
|
7316
7420
|
status: StoredStatusField,
|
|
@@ -7325,6 +7429,7 @@ const TaskCreateInput = Schema$1.Struct({
|
|
|
7325
7429
|
repositoryKey: Schema$1.optional(RepositoryKeyField),
|
|
7326
7430
|
threadId: Schema$1.optional(ThreadIdField),
|
|
7327
7431
|
externalRef: Schema$1.optional(ExternalRefField),
|
|
7432
|
+
parentTaskId: Schema$1.optional(ParentTaskIdField),
|
|
7328
7433
|
title: TitleField,
|
|
7329
7434
|
body: Schema$1.optional(BodyField),
|
|
7330
7435
|
status: Schema$1.optional(StatusField),
|
|
@@ -7364,6 +7469,7 @@ const TaskUpdateInput = Schema$1.Struct({
|
|
|
7364
7469
|
repositoryKey: Schema$1.optional(Schema$1.NullOr(RepositoryKeyField)),
|
|
7365
7470
|
threadId: Schema$1.optional(Schema$1.NullOr(ThreadIdField)),
|
|
7366
7471
|
externalRef: Schema$1.optional(Schema$1.NullOr(ExternalRefField)),
|
|
7472
|
+
parentTaskId: Schema$1.optional(Schema$1.NullOr(ParentTaskIdField)),
|
|
7367
7473
|
title: Schema$1.optional(TitleField),
|
|
7368
7474
|
body: Schema$1.optional(BodyField),
|
|
7369
7475
|
status: Schema$1.optional(StatusField),
|
|
@@ -7783,22 +7889,44 @@ var TicketResolveError = class extends Schema$1.TaggedErrorClass()("TicketResolv
|
|
|
7783
7889
|
//#endregion
|
|
7784
7890
|
//#region ../../packages/contracts/src/threadControl.ts
|
|
7785
7891
|
/**
|
|
7786
|
-
* Thread control over MCP - the
|
|
7787
|
-
* the
|
|
7788
|
-
*
|
|
7789
|
-
*
|
|
7790
|
-
*
|
|
7791
|
-
*
|
|
7792
|
-
*
|
|
7793
|
-
*
|
|
7794
|
-
*
|
|
7795
|
-
*
|
|
7796
|
-
*
|
|
7797
|
-
*
|
|
7892
|
+
* Thread control over MCP - the toolkit an agent uses to act on its own thread
|
|
7893
|
+
* and on the threads it started.
|
|
7894
|
+
*
|
|
7895
|
+
* **The scope rule.** A per-session bearer token is the only thing guarding
|
|
7896
|
+
* `/mcp` on a remote-reachable server, so reach is granted, never assumed. A
|
|
7897
|
+
* session may address its own thread and the threads it created through
|
|
7898
|
+
* `thread_spawn`, and nothing else. A leaked token therefore still reaches only
|
|
7899
|
+
* work that token itself began. The grant is held on the credential rather than
|
|
7900
|
+
* derived from the board, so it cannot widen because someone edited a task.
|
|
7901
|
+
*
|
|
7902
|
+
* **Delete is not exposed, and that is the decision rather than an omission.**
|
|
7903
|
+
* It is the one irreversible action here and a thread carries its whole
|
|
7904
|
+
* transcript. The task toolkit gave the same answer for the same reason, and a
|
|
7905
|
+
* thread is the more expensive thing to lose. An agent that wants a thread gone
|
|
7906
|
+
* settles it and says so; a person deletes it.
|
|
7907
|
+
*
|
|
7908
|
+
* **Spawning is one level deep.** `thread_spawn` seeds a first message and
|
|
7909
|
+
* starts the turn, because a create that cannot say anything is not the feature
|
|
7910
|
+
* anyone asked for. A thread that was itself spawned may not spawn: the depth
|
|
7911
|
+
* cap is what a runaway loop runs into, and one level is enough for "split this
|
|
7912
|
+
* task and start the pieces" while still being a structural bound rather than a
|
|
7913
|
+
* prompt asking nicely.
|
|
7914
|
+
*
|
|
7915
|
+
* **Settle is queued rather than refused.** The decider refuses `thread.settle`
|
|
7916
|
+
* while the target's session is starting or running, and an MCP call from a
|
|
7917
|
+
* thread's own session always arrives while that session is alive - so the
|
|
7918
|
+
* self-addressed case could never succeed as an immediate command. It is
|
|
7919
|
+
* recorded instead and applied when the session stops, which is what an agent
|
|
7920
|
+
* settling its own thread means anyway: finish, then close.
|
|
7798
7921
|
*
|
|
7799
7922
|
* Quick-append to memory lives here too: it is the other thing an agent wants
|
|
7800
7923
|
* to do *from* a thread, and it shares the toolkit's capability and scope.
|
|
7801
7924
|
*
|
|
7925
|
+
* Compressing an asset joins them for a different reason. It is a write the
|
|
7926
|
+
* agent could perform with its ordinary file tools, and the point of routing it
|
|
7927
|
+
* through a tool instead is that the server gets to check the rewrite before it
|
|
7928
|
+
* lands - and to keep the original somewhere the rewrite cannot reach.
|
|
7929
|
+
*
|
|
7802
7930
|
* @module contracts/threadControl
|
|
7803
7931
|
*/
|
|
7804
7932
|
const ThreadRenameInput = Schema$1.Struct({ title: TrimmedNonEmptyString.annotate({ description: "The new thread title. Replaces the current one." }) });
|
|
@@ -7807,6 +7935,64 @@ const ThreadRenameResult = Schema$1.Struct({
|
|
|
7807
7935
|
title: TrimmedNonEmptyString
|
|
7808
7936
|
});
|
|
7809
7937
|
/**
|
|
7938
|
+
* Which thread a control tool acts on.
|
|
7939
|
+
*
|
|
7940
|
+
* Optional, and absent means the caller's own thread. That default is what
|
|
7941
|
+
* keeps the common case honest: an agent adjusting itself never has to name an
|
|
7942
|
+
* id, so it cannot name the wrong one, and every tool below reads the same way
|
|
7943
|
+
* whether or not it is addressed elsewhere.
|
|
7944
|
+
*/
|
|
7945
|
+
const ControlTargetThreadId = Schema$1.optional(ThreadId.annotate({ description: "The thread to act on. Omit for this session's own thread. Only threads this session started with thread_spawn can be named here; any other id is refused." }));
|
|
7946
|
+
const ThreadSpawnInput = Schema$1.Struct({
|
|
7947
|
+
title: TrimmedNonEmptyString.annotate({ description: "Title for the new thread. Shown on the board and in the sidebar." }),
|
|
7948
|
+
prompt: TrimmedNonEmptyString.annotate({ description: "The first message to send. The thread starts its turn immediately, so this is the whole brief the new agent gets." }),
|
|
7949
|
+
projectId: Schema$1.optional(ProjectId.annotate({ description: "Project to start the thread in. Defaults to this session's own project." })),
|
|
7950
|
+
runtimeMode: Schema$1.optional(RuntimeMode.annotate({ description: "Permission mode for the new thread. Defaults to this session's own." })),
|
|
7951
|
+
interactionMode: Schema$1.optional(ProviderInteractionMode),
|
|
7952
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
7953
|
+
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean)
|
|
7954
|
+
});
|
|
7955
|
+
const ThreadSpawnResult = Schema$1.Struct({
|
|
7956
|
+
/** Watchable with `thread_watch_events`, and addressable by every tool here. */
|
|
7957
|
+
threadId: ThreadId,
|
|
7958
|
+
projectId: ProjectId,
|
|
7959
|
+
title: TrimmedNonEmptyString
|
|
7960
|
+
});
|
|
7961
|
+
const ThreadConfigureInput = Schema$1.Struct({
|
|
7962
|
+
threadId: ControlTargetThreadId,
|
|
7963
|
+
title: Schema$1.optional(TrimmedNonEmptyString),
|
|
7964
|
+
modelSelection: Schema$1.optional(ModelSelection),
|
|
7965
|
+
runtimeMode: Schema$1.optional(RuntimeMode),
|
|
7966
|
+
interactionMode: Schema$1.optional(ProviderInteractionMode),
|
|
7967
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
7968
|
+
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean)
|
|
7969
|
+
});
|
|
7970
|
+
const ThreadConfigureResult = Schema$1.Struct({
|
|
7971
|
+
threadId: ThreadId,
|
|
7972
|
+
/** The settings that were actually changed, in the order they were applied. */
|
|
7973
|
+
applied: Schema$1.Array(Schema$1.String)
|
|
7974
|
+
});
|
|
7975
|
+
const ThreadSettleInput = Schema$1.Struct({ threadId: ControlTargetThreadId });
|
|
7976
|
+
/**
|
|
7977
|
+
* Settled now, or recorded to settle when the session stops.
|
|
7978
|
+
*
|
|
7979
|
+
* Two outcomes rather than one because the difference is the agent's to know: a
|
|
7980
|
+
* queued settle has not happened yet, and a thread that goes on to do more work
|
|
7981
|
+
* still settles at the end of it.
|
|
7982
|
+
*/
|
|
7983
|
+
const ThreadSettleResult = Schema$1.Struct({
|
|
7984
|
+
threadId: ThreadId,
|
|
7985
|
+
outcome: Schema$1.Literals(["settled", "queued"])
|
|
7986
|
+
});
|
|
7987
|
+
const ThreadSnoozeInput = Schema$1.Struct({
|
|
7988
|
+
threadId: ControlTargetThreadId,
|
|
7989
|
+
snoozedUntil: IsoDateTime.annotate({ description: "When the thread should come back, as an ISO 8601 timestamp." })
|
|
7990
|
+
});
|
|
7991
|
+
const ThreadSnoozeResult = Schema$1.Struct({
|
|
7992
|
+
threadId: ThreadId,
|
|
7993
|
+
snoozedUntil: IsoDateTime
|
|
7994
|
+
});
|
|
7995
|
+
/**
|
|
7810
7996
|
* Which memory file an append lands in.
|
|
7811
7997
|
*
|
|
7812
7998
|
* A closed union rather than a filename, because the target directory also
|
|
@@ -7825,6 +8011,23 @@ const MemoryAppendResult = Schema$1.Struct({
|
|
|
7825
8011
|
/** Absolute path the append landed in, so the agent can cite or inspect it. */
|
|
7826
8012
|
path: Schema$1.String
|
|
7827
8013
|
});
|
|
8014
|
+
const AssetCompressInput = Schema$1.Struct({
|
|
8015
|
+
path: TrimmedNonEmptyString.annotate({ description: "Absolute path of the asset file to overwrite. Must sit inside one of this machine's agent asset directories: user skills, the provider config directory that holds the memory files, or the subagent definitions directory." }),
|
|
8016
|
+
content: Schema$1.String.check(Schema$1.isNonEmpty()).annotate({ description: "The compressed file, whole. Headings, fenced code blocks, inline code, URLs, paths and any YAML frontmatter must come back unchanged; only the prose around them may be rewritten." })
|
|
8017
|
+
});
|
|
8018
|
+
const AssetCompressResult = Schema$1.Struct({
|
|
8019
|
+
/** Absolute path that was rewritten. */
|
|
8020
|
+
path: Schema$1.String,
|
|
8021
|
+
/** Where the pre-compression original was kept, outside every synced directory. */
|
|
8022
|
+
backupPath: Schema$1.String,
|
|
8023
|
+
charsBefore: Schema$1.Number,
|
|
8024
|
+
charsAfter: Schema$1.Number,
|
|
8025
|
+
/** Estimated at four characters per token, not measured by a provider tokenizer. */
|
|
8026
|
+
estimatedTokensBefore: Schema$1.Number,
|
|
8027
|
+
estimatedTokensAfter: Schema$1.Number,
|
|
8028
|
+
/** Differences worth reporting that were not worth refusing the write over. */
|
|
8029
|
+
warnings: Schema$1.Array(Schema$1.String)
|
|
8030
|
+
});
|
|
7828
8031
|
var ThreadToolUnavailableError = class extends Schema$1.TaggedErrorClass()("ThreadToolUnavailableError", {
|
|
7829
8032
|
capability: Schema$1.Literal("threads"),
|
|
7830
8033
|
environmentId: EnvironmentId,
|
|
@@ -7853,10 +8056,77 @@ var MemoryAppendFailedError = class extends Schema$1.TaggedErrorClass()("MemoryA
|
|
|
7853
8056
|
return `Appending to ${this.file} failed: ${this.detail}`;
|
|
7854
8057
|
}
|
|
7855
8058
|
};
|
|
8059
|
+
/**
|
|
8060
|
+
* The compression will not be attempted, or the file could not be read.
|
|
8061
|
+
*
|
|
8062
|
+
* Separate from the validation failure below because they want opposite
|
|
8063
|
+
* responses. A refusal is final - the path is not an asset, the name looks like
|
|
8064
|
+
* a credential, the file is not there - and retrying it produces the same
|
|
8065
|
+
* answer. A validation failure is a rewrite that can be corrected.
|
|
8066
|
+
*/
|
|
8067
|
+
var AssetCompressRefusedError = class extends Schema$1.TaggedErrorClass()("AssetCompressRefusedError", {
|
|
8068
|
+
path: Schema$1.String,
|
|
8069
|
+
detail: Schema$1.String
|
|
8070
|
+
}) {
|
|
8071
|
+
get message() {
|
|
8072
|
+
return `Compressing ${this.path} was refused: ${this.detail}`;
|
|
8073
|
+
}
|
|
8074
|
+
};
|
|
8075
|
+
/**
|
|
8076
|
+
* The rewrite lost something, and nothing was written.
|
|
8077
|
+
*
|
|
8078
|
+
* Carries the checks that failed rather than a summary, because the agent's
|
|
8079
|
+
* next move is to restore exactly those things from the original and call
|
|
8080
|
+
* again. The file on disk is untouched, so a second failure costs nothing more.
|
|
8081
|
+
*/
|
|
8082
|
+
var AssetCompressValidationFailedError = class extends Schema$1.TaggedErrorClass()("AssetCompressValidationFailedError", {
|
|
8083
|
+
path: Schema$1.String,
|
|
8084
|
+
errors: Schema$1.Array(Schema$1.String)
|
|
8085
|
+
}) {
|
|
8086
|
+
get message() {
|
|
8087
|
+
return `The compressed ${this.path} did not validate, so nothing was written: ${this.errors.join(" ")}`;
|
|
8088
|
+
}
|
|
8089
|
+
};
|
|
8090
|
+
/**
|
|
8091
|
+
* The named thread is outside this session's grant.
|
|
8092
|
+
*
|
|
8093
|
+
* Deliberately the same answer whether the thread does not exist or exists and
|
|
8094
|
+
* belongs to someone else: a caller that could tell those apart could map the
|
|
8095
|
+
* whole board one id at a time.
|
|
8096
|
+
*/
|
|
8097
|
+
var ThreadControlNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadControlNotPermittedError", { threadId: ThreadId }) {
|
|
8098
|
+
get message() {
|
|
8099
|
+
return `This session cannot act on thread ${this.threadId}. A session may act on its own thread and on threads it started with thread_spawn.`;
|
|
8100
|
+
}
|
|
8101
|
+
};
|
|
8102
|
+
/** A spawned thread asked to spawn. One level is the whole allowance. */
|
|
8103
|
+
var ThreadSpawnNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadSpawnNotPermittedError", {
|
|
8104
|
+
threadId: ThreadId,
|
|
8105
|
+
detail: Schema$1.String
|
|
8106
|
+
}) {
|
|
8107
|
+
get message() {
|
|
8108
|
+
return `Thread ${this.threadId} cannot start another thread: ${this.detail}`;
|
|
8109
|
+
}
|
|
8110
|
+
};
|
|
8111
|
+
/** The orchestration engine declined or failed a thread control command. */
|
|
8112
|
+
var ThreadControlRejectedError = class extends Schema$1.TaggedErrorClass()("ThreadControlRejectedError", {
|
|
8113
|
+
threadId: ThreadId,
|
|
8114
|
+
commandType: Schema$1.String,
|
|
8115
|
+
detail: Schema$1.String
|
|
8116
|
+
}) {
|
|
8117
|
+
get message() {
|
|
8118
|
+
return `${this.commandType} on thread ${this.threadId} failed: ${this.detail}`;
|
|
8119
|
+
}
|
|
8120
|
+
};
|
|
7856
8121
|
const ThreadControlToolError = Schema$1.Union([
|
|
7857
8122
|
ThreadToolUnavailableError,
|
|
8123
|
+
ThreadControlNotPermittedError,
|
|
8124
|
+
ThreadSpawnNotPermittedError,
|
|
8125
|
+
ThreadControlRejectedError,
|
|
7858
8126
|
ThreadRenameRejectedError,
|
|
7859
|
-
MemoryAppendFailedError
|
|
8127
|
+
MemoryAppendFailedError,
|
|
8128
|
+
AssetCompressRefusedError,
|
|
8129
|
+
AssetCompressValidationFailedError
|
|
7860
8130
|
]);
|
|
7861
8131
|
const ThreadWatchEventsInput = Schema$1.Struct({
|
|
7862
8132
|
threadId: ThreadId.annotate({ description: "The watched thread to read events from. Must be explicitly granted." }),
|
|
@@ -12660,6 +12930,53 @@ var _040_TaskExternalRef_default = Effect.gen(function* () {
|
|
|
12660
12930
|
`;
|
|
12661
12931
|
});
|
|
12662
12932
|
//#endregion
|
|
12933
|
+
//#region src/persistence/Migrations/041_TaskParentTaskId.ts
|
|
12934
|
+
/**
|
|
12935
|
+
* The task a task was split out of, for the plan phase's subtasks.
|
|
12936
|
+
*
|
|
12937
|
+
* A plain column rather than a join table: a task has at most one parent, and
|
|
12938
|
+
* the question asked of it is always "children of this row", which one indexed
|
|
12939
|
+
* column answers.
|
|
12940
|
+
*
|
|
12941
|
+
* No foreign key. Tasks are synced between machines and a child can arrive
|
|
12942
|
+
* before its parent does, or outlive it - a constraint would reject the write
|
|
12943
|
+
* rather than leave a link that resolves later. The board treats a parent it
|
|
12944
|
+
* cannot find as no parent at all.
|
|
12945
|
+
*
|
|
12946
|
+
* The index is non-unique and partial: many children per parent, and the many
|
|
12947
|
+
* rows that are nobody's child stay out of it.
|
|
12948
|
+
*/
|
|
12949
|
+
var _041_TaskParentTaskId_default = Effect.gen(function* () {
|
|
12950
|
+
const sql = yield* SqlClient.SqlClient;
|
|
12951
|
+
yield* sql`ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`;
|
|
12952
|
+
yield* sql`
|
|
12953
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
12954
|
+
ON tasks(parent_task_id)
|
|
12955
|
+
WHERE parent_task_id IS NOT NULL
|
|
12956
|
+
`;
|
|
12957
|
+
});
|
|
12958
|
+
//#endregion
|
|
12959
|
+
//#region src/persistence/Migrations/042_ProjectionThreadsUnpromptedSubagents.ts
|
|
12960
|
+
/**
|
|
12961
|
+
* Whether a thread lets the model spawn subagents without being asked, moved
|
|
12962
|
+
* from a machine-wide setting onto the thread itself.
|
|
12963
|
+
*
|
|
12964
|
+
* Stored as an integer beside `has_actionable_proposed_plan` rather than as a
|
|
12965
|
+
* SQLite boolean, which does not exist; the projection reads it back as
|
|
12966
|
+
* `> 0`.
|
|
12967
|
+
*
|
|
12968
|
+
* The default is `1` — subagents allowed — because that is what every existing
|
|
12969
|
+
* thread has been doing under the old server setting. Anything else would
|
|
12970
|
+
* change the behaviour of live threads on upgrade, which is not a migration's
|
|
12971
|
+
* business.
|
|
12972
|
+
*/
|
|
12973
|
+
var _042_ProjectionThreadsUnpromptedSubagents_default = Effect.gen(function* () {
|
|
12974
|
+
yield* (yield* SqlClient.SqlClient)`
|
|
12975
|
+
ALTER TABLE projection_threads
|
|
12976
|
+
ADD COLUMN unprompted_subagents INTEGER NOT NULL DEFAULT 1
|
|
12977
|
+
`;
|
|
12978
|
+
});
|
|
12979
|
+
//#endregion
|
|
12663
12980
|
//#region src/persistence/Migrations.ts
|
|
12664
12981
|
/**
|
|
12665
12982
|
* MigrationsLive - Migration runner with inline loader
|
|
@@ -12880,6 +13197,16 @@ const migrationEntries = [
|
|
|
12880
13197
|
40,
|
|
12881
13198
|
"TaskExternalRef",
|
|
12882
13199
|
_040_TaskExternalRef_default
|
|
13200
|
+
],
|
|
13201
|
+
[
|
|
13202
|
+
41,
|
|
13203
|
+
"TaskParentTaskId",
|
|
13204
|
+
_041_TaskParentTaskId_default
|
|
13205
|
+
],
|
|
13206
|
+
[
|
|
13207
|
+
42,
|
|
13208
|
+
"ProjectionThreadsUnpromptedSubagents",
|
|
13209
|
+
_042_ProjectionThreadsUnpromptedSubagents_default
|
|
12883
13210
|
]
|
|
12884
13211
|
];
|
|
12885
13212
|
const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -14511,6 +14838,7 @@ const TASK_COLUMNS = `
|
|
|
14511
14838
|
repository_key AS "repositoryKey",
|
|
14512
14839
|
thread_id AS "threadId",
|
|
14513
14840
|
external_ref_json AS "externalRef",
|
|
14841
|
+
parent_task_id AS "parentTaskId",
|
|
14514
14842
|
title,
|
|
14515
14843
|
body,
|
|
14516
14844
|
status,
|
|
@@ -14535,6 +14863,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14535
14863
|
repository_key,
|
|
14536
14864
|
thread_id,
|
|
14537
14865
|
external_ref_json,
|
|
14866
|
+
parent_task_id,
|
|
14538
14867
|
title,
|
|
14539
14868
|
body,
|
|
14540
14869
|
status,
|
|
@@ -14552,6 +14881,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14552
14881
|
${row.repositoryKey},
|
|
14553
14882
|
${row.threadId},
|
|
14554
14883
|
${row.externalRef === null ? null : JSON.stringify(row.externalRef)},
|
|
14884
|
+
${row.parentTaskId},
|
|
14555
14885
|
${row.title},
|
|
14556
14886
|
${row.body},
|
|
14557
14887
|
${row.status},
|
|
@@ -14569,6 +14899,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14569
14899
|
repository_key = excluded.repository_key,
|
|
14570
14900
|
thread_id = excluded.thread_id,
|
|
14571
14901
|
external_ref_json = excluded.external_ref_json,
|
|
14902
|
+
parent_task_id = excluded.parent_task_id,
|
|
14572
14903
|
title = excluded.title,
|
|
14573
14904
|
body = excluded.body,
|
|
14574
14905
|
status = excluded.status,
|
|
@@ -14668,6 +14999,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14668
14999
|
...input.repositoryKey !== void 0 ? { repositoryKey: input.repositoryKey } : {},
|
|
14669
15000
|
...input.threadId !== void 0 ? { threadId: input.threadId } : {},
|
|
14670
15001
|
...input.externalRef !== void 0 ? { externalRef: input.externalRef } : {},
|
|
15002
|
+
...input.parentTaskId !== void 0 ? { parentTaskId: input.parentTaskId } : {},
|
|
14671
15003
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
14672
15004
|
...input.body !== void 0 ? { body: input.body } : {},
|
|
14673
15005
|
...input.status !== void 0 ? { status: input.status } : {},
|
|
@@ -14706,6 +15038,8 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14706
15038
|
...input.patch.projectId !== void 0 ? { projectId: input.patch.projectId } : {},
|
|
14707
15039
|
...input.patch.repositoryKey !== void 0 ? { repositoryKey: input.patch.repositoryKey } : {},
|
|
14708
15040
|
...input.patch.threadId !== void 0 ? { threadId: input.patch.threadId } : {},
|
|
15041
|
+
...input.patch.externalRef !== void 0 ? { externalRef: input.patch.externalRef } : {},
|
|
15042
|
+
...input.patch.parentTaskId !== void 0 ? { parentTaskId: input.patch.parentTaskId } : {},
|
|
14709
15043
|
...input.patch.title !== void 0 ? { title: input.patch.title } : {},
|
|
14710
15044
|
...input.patch.body !== void 0 ? { body: input.patch.body } : {},
|
|
14711
15045
|
...input.patch.status !== void 0 ? { status: input.patch.status } : {},
|
|
@@ -14840,6 +15174,7 @@ const createTaskRoute = HttpRouter.add("POST", "/tasks", respondToHubFailures(Ef
|
|
|
14840
15174
|
repositoryKey: input.repositoryKey ?? null,
|
|
14841
15175
|
threadId: input.threadId ?? null,
|
|
14842
15176
|
externalRef: input.externalRef ?? null,
|
|
15177
|
+
parentTaskId: input.parentTaskId ?? null,
|
|
14843
15178
|
title: input.title,
|
|
14844
15179
|
body: input.body ?? "",
|
|
14845
15180
|
status: input.status ?? "backlog",
|
|
@@ -15159,6 +15494,22 @@ var _006_TaskExternalRef_default = Effect.gen(function* () {
|
|
|
15159
15494
|
`;
|
|
15160
15495
|
});
|
|
15161
15496
|
//#endregion
|
|
15497
|
+
//#region src/hub/Migrations/007_TaskParentTaskId.ts
|
|
15498
|
+
/**
|
|
15499
|
+
* The task a task was split out of, mirroring the environment server's
|
|
15500
|
+
* migration 041. The hub carries the column so a plan filed on one machine
|
|
15501
|
+
* still reads as a plan on another, rather than as a handful of loose tasks.
|
|
15502
|
+
*/
|
|
15503
|
+
var _007_TaskParentTaskId_default = Effect.gen(function* () {
|
|
15504
|
+
const sql = yield* SqlClient.SqlClient;
|
|
15505
|
+
yield* sql`ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`;
|
|
15506
|
+
yield* sql`
|
|
15507
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
15508
|
+
ON tasks(parent_task_id)
|
|
15509
|
+
WHERE parent_task_id IS NOT NULL
|
|
15510
|
+
`;
|
|
15511
|
+
});
|
|
15512
|
+
//#endregion
|
|
15162
15513
|
//#region src/hub/Migrations.ts
|
|
15163
15514
|
/**
|
|
15164
15515
|
* Hub migrations.
|
|
@@ -15199,6 +15550,11 @@ const hubMigrationEntries = [
|
|
|
15199
15550
|
6,
|
|
15200
15551
|
"TaskExternalRef",
|
|
15201
15552
|
_006_TaskExternalRef_default
|
|
15553
|
+
],
|
|
15554
|
+
[
|
|
15555
|
+
7,
|
|
15556
|
+
"TaskParentTaskId",
|
|
15557
|
+
_007_TaskParentTaskId_default
|
|
15202
15558
|
]
|
|
15203
15559
|
];
|
|
15204
15560
|
const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -16714,7 +17070,7 @@ const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith
|
|
|
16714
17070
|
* Exported so the single-file kinds ask it the same way this one does, rather
|
|
16715
17071
|
* than each keeping a copy that could drift into a different answer.
|
|
16716
17072
|
*/
|
|
16717
|
-
const looksBinary$
|
|
17073
|
+
const looksBinary$2 = (content) => content.includes("\0");
|
|
16718
17074
|
/**
|
|
16719
17075
|
* Read one directory as an asset's files, or say why it cannot be one.
|
|
16720
17076
|
*
|
|
@@ -16746,7 +17102,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
|
|
|
16746
17102
|
if (files.length >= 512) return "too-many-files";
|
|
16747
17103
|
const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
|
|
16748
17104
|
if (content === void 0) return "unreadable";
|
|
16749
|
-
if (looksBinary$
|
|
17105
|
+
if (looksBinary$2(content)) return "binary-file";
|
|
16750
17106
|
if (content.length > 524288) return "file-too-large";
|
|
16751
17107
|
files.push({
|
|
16752
17108
|
path: relative,
|
|
@@ -16870,7 +17226,7 @@ const readAgentDefinitions = Effect.fn("agentDefinitionFiles.read")(function* (a
|
|
|
16870
17226
|
});
|
|
16871
17227
|
continue;
|
|
16872
17228
|
}
|
|
16873
|
-
if (looksBinary$
|
|
17229
|
+
if (looksBinary$2(content)) {
|
|
16874
17230
|
refused.push({
|
|
16875
17231
|
name: entry,
|
|
16876
17232
|
reason: "binary-file"
|
|
@@ -17392,7 +17748,7 @@ const isMemoryDirectoryName = (name) => MEMORY_DIRECTORY_NAMES.includes(name);
|
|
|
17392
17748
|
*/
|
|
17393
17749
|
const isMemoryAssetName = (name) => isMemoryFileName(name) || isMemoryDirectoryName(name);
|
|
17394
17750
|
/** The same test the skill reader uses: a NUL is what breaks a JSON text column. */
|
|
17395
|
-
const looksBinary = (content) => content.includes("\0");
|
|
17751
|
+
const looksBinary$1 = (content) => content.includes("\0");
|
|
17396
17752
|
/**
|
|
17397
17753
|
* Read whichever memory files exist in this machine's config directory.
|
|
17398
17754
|
*
|
|
@@ -17416,7 +17772,7 @@ const readMemoryFiles = Effect.fn("memoryFile.readMemoryFiles")(function* (memor
|
|
|
17416
17772
|
});
|
|
17417
17773
|
continue;
|
|
17418
17774
|
}
|
|
17419
|
-
if (looksBinary(content)) {
|
|
17775
|
+
if (looksBinary$1(content)) {
|
|
17420
17776
|
refused.push({
|
|
17421
17777
|
name,
|
|
17422
17778
|
reason: "binary-file"
|
|
@@ -18529,6 +18885,8 @@ Never drop not/never/no/only/except: flipping the meaning costs more than any to
|
|
|
18529
18885
|
|
|
18530
18886
|
Tool calls: fire direct. No plan, no progress note between calls, never announce the next call. If the harness asks for a short lead-in before tool calls, write that one line and nothing more. Otherwise text before a call only to clarify, warn about a security or irreversible action, or resolve ambiguity.
|
|
18531
18887
|
|
|
18888
|
+
Subagent and tool output is raw material, not the answer. Never relay a subagent report, a file listing, or command output verbatim - state the conclusion it supports and cite the file and line that back it. Its length is its own and is never a licence to answer at that length. Quote from it only where the exact wording is the point.
|
|
18889
|
+
|
|
18532
18890
|
Preserve the user's dominant language: reply in the language the user writes, whatever language the examples or the surrounding context use. Compress the style, not the language. Every emitted line in that language, lead-in lines included, not just the final reply. Always keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim unless the user explicitly asks for translation.
|
|
18533
18891
|
|
|
18534
18892
|
Dropping articles applies to article languages only. Where small markers carry case or role (particles, postpositions), keep them: grammar, not filler. Compress politeness and filler instead.
|
|
@@ -18622,6 +18980,16 @@ function compressTurnReminderFor(mode) {
|
|
|
18622
18980
|
*/
|
|
18623
18981
|
const PRESET_MAX_RESULT_ROWS = 20;
|
|
18624
18982
|
/**
|
|
18983
|
+
* The tool calls a preset run is allowed.
|
|
18984
|
+
*
|
|
18985
|
+
* Fifteen because a located answer typically takes six to ten, and the runs
|
|
18986
|
+
* that overshoot do not overshoot slightly: measured against real transcripts,
|
|
18987
|
+
* the long tail sits between sixty and a hundred and thirty calls and carries
|
|
18988
|
+
* nearly all of the cost. A cap that a good run never reaches is a cap that only
|
|
18989
|
+
* ever bites the runs that had stopped making progress.
|
|
18990
|
+
*/
|
|
18991
|
+
const PRESET_MAX_TOOL_CALLS = 15;
|
|
18992
|
+
/**
|
|
18625
18993
|
* Named `p4-*` on purpose.
|
|
18626
18994
|
*
|
|
18627
18995
|
* The directory is shared with whatever the user has written by hand, and
|
|
@@ -18651,6 +19019,34 @@ names a path that is not there, answer with the BLOCKED line below. Guessing at
|
|
|
18651
19019
|
the missing half of a request produces an answer that reads as confident and is
|
|
18652
19020
|
not checkable, which is worse than no answer.`;
|
|
18653
19021
|
/**
|
|
19022
|
+
* What every preset says about the run in between.
|
|
19023
|
+
*
|
|
19024
|
+
* Three rules, each aimed at a measured failure. The cap bounds the quadratic.
|
|
19025
|
+
* The ban on nesting stops a bounded run from spawning an unbounded one, which
|
|
19026
|
+
* is the same bill again with nothing watching it. The stop-at-the-cap clause is
|
|
19027
|
+
* what makes the number safe to enforce: without somewhere to put a partial
|
|
19028
|
+
* answer, a capped subagent either grinds past the cap or throws away what it
|
|
19029
|
+
* had, and both are worse than a short table with a named gap.
|
|
19030
|
+
*/
|
|
19031
|
+
const WORK_BUDGET = `## What the run costs
|
|
19032
|
+
|
|
19033
|
+
At most ${PRESET_MAX_TOOL_CALLS} tool calls. Most answers of this kind take six
|
|
19034
|
+
to ten, so treat the cap as the point where something has gone wrong rather than
|
|
19035
|
+
as a target to spend.
|
|
19036
|
+
|
|
19037
|
+
Spend them on calls that can fail usefully: before each one, know what a hit and
|
|
19038
|
+
a miss would each tell you. Batch searches into one call separated by echoed
|
|
19039
|
+
markers rather than issuing them one at a time, bound every output with a head
|
|
19040
|
+
or a line count, and read a file only after a search has told you which lines
|
|
19041
|
+
matter, and read those lines. Never repeat a call you have already made.
|
|
19042
|
+
|
|
19043
|
+
Do not spawn a subagent of your own. You are the bounded run; one that starts
|
|
19044
|
+
another has spent the budget twice with nothing watching the second half.
|
|
19045
|
+
|
|
19046
|
+
Reaching the cap ends the run. Return the rows you have verified, and add a
|
|
19047
|
+
final line naming the search you would have run next. With no verified rows at
|
|
19048
|
+
all, that is the BLOCKED line.`;
|
|
19049
|
+
/**
|
|
18654
19050
|
* What every preset says about the answer.
|
|
18655
19051
|
*
|
|
18656
19052
|
* The two sentinels matter as much as the table. Without `NONE` a clean run
|
|
@@ -18669,7 +19065,8 @@ const outputContract = (row, extra) => [
|
|
|
18669
19065
|
` ${row}`,
|
|
18670
19066
|
"",
|
|
18671
19067
|
"Nothing to report: the single word NONE.",
|
|
18672
|
-
"Request insufficient
|
|
19068
|
+
"Request insufficient, or the budget spent before anything was verified:",
|
|
19069
|
+
"BLOCKED - <what is missing>, on one line.",
|
|
18673
19070
|
"",
|
|
18674
19071
|
"Paths are repository-relative and carry a line number. A row without one is",
|
|
18675
19072
|
"a row you have not verified: open the file and get it.",
|
|
@@ -18681,6 +19078,11 @@ const outputContract = (row, extra) => [
|
|
|
18681
19078
|
* The compression ruleset goes last so the contract above it is read first.
|
|
18682
19079
|
* Both are instructions to the same model and neither overrides the other:
|
|
18683
19080
|
* the contract fixes the shape, the ruleset fixes the prose inside it.
|
|
19081
|
+
*
|
|
19082
|
+
* `model` is optional, and omitting it inherits the session's. Naming a cheaper
|
|
19083
|
+
* tier is worth it only where the job is mechanical enough that the tier is not
|
|
19084
|
+
* what makes the answer right, which is true of locating a symbol and is not
|
|
19085
|
+
* true of deciding whether a line is a bug.
|
|
18684
19086
|
*/
|
|
18685
19087
|
const buildPreset = (input) => {
|
|
18686
19088
|
const name = `${PRESET_NAME_PREFIX}${input.name}`;
|
|
@@ -18689,6 +19091,7 @@ const buildPreset = (input) => {
|
|
|
18689
19091
|
`name: ${name}`,
|
|
18690
19092
|
`description: ${JSON.stringify(input.description)}`,
|
|
18691
19093
|
`tools: ${input.tools}`,
|
|
19094
|
+
...input.model === void 0 ? [] : [`model: ${input.model}`],
|
|
18692
19095
|
"---",
|
|
18693
19096
|
"",
|
|
18694
19097
|
input.body.trim(),
|
|
@@ -18711,12 +19114,15 @@ const AGENT_PRESETS = [buildPreset({
|
|
|
18711
19114
|
name: "investigator",
|
|
18712
19115
|
description: "Find where something lives in the codebase. Use when the answer is a set of locations rather than a change. Returns a fixed table of path:line rows and nothing else.",
|
|
18713
19116
|
tools: "Read, Grep, Glob, Bash",
|
|
19117
|
+
model: "sonnet",
|
|
18714
19118
|
summary: "Locates code and answers with a path:line table.",
|
|
18715
19119
|
body: `You locate code. You do not change it, and you say no more about any location
|
|
18716
19120
|
than the one clause that explains why it is in the list.
|
|
18717
19121
|
|
|
18718
19122
|
${INPUT_CONTRACT}
|
|
18719
19123
|
|
|
19124
|
+
${WORK_BUDGET}
|
|
19125
|
+
|
|
18720
19126
|
${outputContract("path:line - symbol - one clause on why it matters", [
|
|
18721
19127
|
"",
|
|
18722
19128
|
"Symbol is the function, type or constant the line belongs to, not a paraphrase",
|
|
@@ -18734,6 +19140,8 @@ approval is a list that has to be re-read to find the three rows that matter.
|
|
|
18734
19140
|
|
|
18735
19141
|
${INPUT_CONTRACT}
|
|
18736
19142
|
|
|
19143
|
+
${WORK_BUDGET}
|
|
19144
|
+
|
|
18737
19145
|
${outputContract("path:line - severity - what is wrong, and what it breaks", [
|
|
18738
19146
|
"",
|
|
18739
19147
|
"Severity is one of three words:",
|
|
@@ -19788,6 +20196,7 @@ const ThreadMetaUpdatedPayload = ThreadMetaUpdatedPayload$1;
|
|
|
19788
20196
|
const ThreadRuntimeModeSetPayload = ThreadRuntimeModeSetPayload$1;
|
|
19789
20197
|
const ThreadInteractionModeSetPayload = ThreadInteractionModeSetPayload$1;
|
|
19790
20198
|
const ThreadCompressModeSetPayload = ThreadCompressModeSetPayload$1;
|
|
20199
|
+
const ThreadUnpromptedSubagentsSetPayload = ThreadUnpromptedSubagentsSetPayload$1;
|
|
19791
20200
|
const ThreadDeletedPayload = ThreadDeletedPayload$1;
|
|
19792
20201
|
const ThreadUnarchivedPayload = ThreadUnarchivedPayload$1;
|
|
19793
20202
|
const ThreadUnsettledPayload = ThreadUnsettledPayload$1;
|
|
@@ -19928,6 +20337,7 @@ function projectEvent(model, event) {
|
|
|
19928
20337
|
runtimeMode: payload.runtimeMode,
|
|
19929
20338
|
interactionMode: payload.interactionMode,
|
|
19930
20339
|
compressMode: payload.compressMode,
|
|
20340
|
+
unpromptedSubagents: payload.unpromptedSubagents,
|
|
19931
20341
|
branch: payload.branch,
|
|
19932
20342
|
worktreePath: payload.worktreePath,
|
|
19933
20343
|
latestTurn: null,
|
|
@@ -20034,6 +20444,13 @@ function projectEvent(model, event) {
|
|
|
20034
20444
|
updatedAt: payload.updatedAt
|
|
20035
20445
|
})
|
|
20036
20446
|
})));
|
|
20447
|
+
case "thread.unprompted-subagents-set": return decodeForEvent(ThreadUnpromptedSubagentsSetPayload, event.payload, event.type, "payload").pipe(Effect.map((payload) => ({
|
|
20448
|
+
...nextBase,
|
|
20449
|
+
threads: updateThread(nextBase.threads, payload.threadId, {
|
|
20450
|
+
unpromptedSubagents: payload.unpromptedSubagents,
|
|
20451
|
+
updatedAt: payload.updatedAt
|
|
20452
|
+
})
|
|
20453
|
+
})));
|
|
20037
20454
|
case "thread.message-sent": return Effect.gen(function* () {
|
|
20038
20455
|
const payload = yield* decodeForEvent(MessageSentPayloadSchema, event.payload, event.type, "payload");
|
|
20039
20456
|
const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
|
|
@@ -20400,6 +20817,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
|
|
|
20400
20817
|
runtimeMode: command.runtimeMode,
|
|
20401
20818
|
interactionMode: command.interactionMode,
|
|
20402
20819
|
compressMode: command.compressMode,
|
|
20820
|
+
unpromptedSubagents: command.unpromptedSubagents,
|
|
20403
20821
|
branch: command.branch,
|
|
20404
20822
|
worktreePath: command.worktreePath,
|
|
20405
20823
|
createdAt: command.createdAt,
|
|
@@ -20679,6 +21097,28 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
|
|
|
20679
21097
|
}
|
|
20680
21098
|
};
|
|
20681
21099
|
}
|
|
21100
|
+
case "thread.unprompted-subagents.set": {
|
|
21101
|
+
yield* requireThread({
|
|
21102
|
+
readModel,
|
|
21103
|
+
command,
|
|
21104
|
+
threadId: command.threadId
|
|
21105
|
+
});
|
|
21106
|
+
const occurredAt = yield* nowIso$8;
|
|
21107
|
+
return {
|
|
21108
|
+
...yield* withEventBase({
|
|
21109
|
+
aggregateKind: "thread",
|
|
21110
|
+
aggregateId: command.threadId,
|
|
21111
|
+
occurredAt,
|
|
21112
|
+
commandId: command.commandId
|
|
21113
|
+
}),
|
|
21114
|
+
type: "thread.unprompted-subagents-set",
|
|
21115
|
+
payload: {
|
|
21116
|
+
threadId: command.threadId,
|
|
21117
|
+
unpromptedSubagents: command.unpromptedSubagents,
|
|
21118
|
+
updatedAt: occurredAt
|
|
21119
|
+
}
|
|
21120
|
+
};
|
|
21121
|
+
}
|
|
20682
21122
|
case "thread.turn.start": {
|
|
20683
21123
|
const targetThread = yield* requireThread({
|
|
20684
21124
|
readModel,
|
|
@@ -20737,6 +21177,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
|
|
|
20737
21177
|
runtimeMode: targetThread.runtimeMode,
|
|
20738
21178
|
interactionMode: targetThread.interactionMode,
|
|
20739
21179
|
compressMode: targetThread.compressMode,
|
|
21180
|
+
unpromptedSubagents: targetThread.unpromptedSubagents,
|
|
20740
21181
|
...sourceProposedPlan !== void 0 ? { sourceProposedPlan } : {},
|
|
20741
21182
|
createdAt: command.createdAt
|
|
20742
21183
|
}
|
|
@@ -21503,6 +21944,8 @@ const ProjectionThread = Schema$1.Struct({
|
|
|
21503
21944
|
runtimeMode: RuntimeMode,
|
|
21504
21945
|
interactionMode: ProviderInteractionMode,
|
|
21505
21946
|
compressMode: CompressMode,
|
|
21947
|
+
/** 0 or 1: SQLite has no boolean, matching `hasActionableProposedPlan`. */
|
|
21948
|
+
unpromptedSubagents: NonNegativeInt,
|
|
21506
21949
|
branch: Schema$1.NullOr(Schema$1.String),
|
|
21507
21950
|
worktreePath: Schema$1.NullOr(Schema$1.String),
|
|
21508
21951
|
latestTurnId: Schema$1.NullOr(TurnId),
|
|
@@ -22391,6 +22834,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22391
22834
|
runtime_mode,
|
|
22392
22835
|
interaction_mode,
|
|
22393
22836
|
compress_mode,
|
|
22837
|
+
unprompted_subagents,
|
|
22394
22838
|
branch,
|
|
22395
22839
|
worktree_path,
|
|
22396
22840
|
latest_turn_id,
|
|
@@ -22415,6 +22859,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22415
22859
|
${row.runtimeMode},
|
|
22416
22860
|
${row.interactionMode},
|
|
22417
22861
|
${row.compressMode},
|
|
22862
|
+
${row.unpromptedSubagents},
|
|
22418
22863
|
${row.branch},
|
|
22419
22864
|
${row.worktreePath},
|
|
22420
22865
|
${row.latestTurnId},
|
|
@@ -22439,6 +22884,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22439
22884
|
runtime_mode = excluded.runtime_mode,
|
|
22440
22885
|
interaction_mode = excluded.interaction_mode,
|
|
22441
22886
|
compress_mode = excluded.compress_mode,
|
|
22887
|
+
unprompted_subagents = excluded.unprompted_subagents,
|
|
22442
22888
|
branch = excluded.branch,
|
|
22443
22889
|
worktree_path = excluded.worktree_path,
|
|
22444
22890
|
latest_turn_id = excluded.latest_turn_id,
|
|
@@ -22468,6 +22914,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22468
22914
|
runtime_mode AS "runtimeMode",
|
|
22469
22915
|
interaction_mode AS "interactionMode",
|
|
22470
22916
|
compress_mode AS "compressMode",
|
|
22917
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
22471
22918
|
branch,
|
|
22472
22919
|
worktree_path AS "worktreePath",
|
|
22473
22920
|
latest_turn_id AS "latestTurnId",
|
|
@@ -22499,6 +22946,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22499
22946
|
runtime_mode AS "runtimeMode",
|
|
22500
22947
|
interaction_mode AS "interactionMode",
|
|
22501
22948
|
compress_mode AS "compressMode",
|
|
22949
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
22502
22950
|
branch,
|
|
22503
22951
|
worktree_path AS "worktreePath",
|
|
22504
22952
|
latest_turn_id AS "latestTurnId",
|
|
@@ -22966,6 +23414,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
22966
23414
|
runtimeMode: event.payload.runtimeMode,
|
|
22967
23415
|
interactionMode: event.payload.interactionMode,
|
|
22968
23416
|
compressMode: event.payload.compressMode,
|
|
23417
|
+
unpromptedSubagents: event.payload.unpromptedSubagents ? 1 : 0,
|
|
22969
23418
|
branch: event.payload.branch,
|
|
22970
23419
|
worktreePath: event.payload.worktreePath,
|
|
22971
23420
|
latestTurnId: null,
|
|
@@ -23090,6 +23539,16 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
23090
23539
|
});
|
|
23091
23540
|
return;
|
|
23092
23541
|
}
|
|
23542
|
+
case "thread.unprompted-subagents-set": {
|
|
23543
|
+
const existingRow = yield* projectionThreadRepository.getById({ threadId: event.payload.threadId });
|
|
23544
|
+
if (Option.isNone(existingRow)) return;
|
|
23545
|
+
yield* projectionThreadRepository.upsert({
|
|
23546
|
+
...existingRow.value,
|
|
23547
|
+
unpromptedSubagents: event.payload.unpromptedSubagents ? 1 : 0,
|
|
23548
|
+
updatedAt: event.payload.updatedAt
|
|
23549
|
+
});
|
|
23550
|
+
return;
|
|
23551
|
+
}
|
|
23093
23552
|
case "thread.deleted": {
|
|
23094
23553
|
attachmentSideEffects.deletedThreadIds.add(event.payload.threadId);
|
|
23095
23554
|
const existingRow = yield* projectionThreadRepository.getById({ threadId: event.payload.threadId });
|
|
@@ -24412,6 +24871,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24412
24871
|
runtime_mode AS "runtimeMode",
|
|
24413
24872
|
interaction_mode AS "interactionMode",
|
|
24414
24873
|
compress_mode AS "compressMode",
|
|
24874
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24415
24875
|
branch,
|
|
24416
24876
|
worktree_path AS "worktreePath",
|
|
24417
24877
|
latest_turn_id AS "latestTurnId",
|
|
@@ -24443,6 +24903,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24443
24903
|
runtime_mode AS "runtimeMode",
|
|
24444
24904
|
interaction_mode AS "interactionMode",
|
|
24445
24905
|
compress_mode AS "compressMode",
|
|
24906
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24446
24907
|
branch,
|
|
24447
24908
|
worktree_path AS "worktreePath",
|
|
24448
24909
|
latest_turn_id AS "latestTurnId",
|
|
@@ -24476,6 +24937,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24476
24937
|
runtime_mode AS "runtimeMode",
|
|
24477
24938
|
interaction_mode AS "interactionMode",
|
|
24478
24939
|
compress_mode AS "compressMode",
|
|
24940
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24479
24941
|
branch,
|
|
24480
24942
|
worktree_path AS "worktreePath",
|
|
24481
24943
|
latest_turn_id AS "latestTurnId",
|
|
@@ -24809,6 +25271,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24809
25271
|
runtime_mode AS "runtimeMode",
|
|
24810
25272
|
interaction_mode AS "interactionMode",
|
|
24811
25273
|
compress_mode AS "compressMode",
|
|
25274
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24812
25275
|
branch,
|
|
24813
25276
|
worktree_path AS "worktreePath",
|
|
24814
25277
|
latest_turn_id AS "latestTurnId",
|
|
@@ -25111,6 +25574,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25111
25574
|
runtimeMode: row.runtimeMode,
|
|
25112
25575
|
interactionMode: row.interactionMode,
|
|
25113
25576
|
compressMode: row.compressMode,
|
|
25577
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25114
25578
|
branch: row.branch,
|
|
25115
25579
|
worktreePath: row.worktreePath,
|
|
25116
25580
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25223,6 +25687,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25223
25687
|
runtimeMode: row.runtimeMode,
|
|
25224
25688
|
interactionMode: row.interactionMode,
|
|
25225
25689
|
compressMode: row.compressMode,
|
|
25690
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25226
25691
|
branch: row.branch,
|
|
25227
25692
|
worktreePath: row.worktreePath,
|
|
25228
25693
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25282,6 +25747,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25282
25747
|
runtimeMode: row.runtimeMode,
|
|
25283
25748
|
interactionMode: row.interactionMode,
|
|
25284
25749
|
compressMode: row.compressMode,
|
|
25750
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25285
25751
|
branch: row.branch,
|
|
25286
25752
|
worktreePath: row.worktreePath,
|
|
25287
25753
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25337,6 +25803,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25337
25803
|
runtimeMode: row.runtimeMode,
|
|
25338
25804
|
interactionMode: row.interactionMode,
|
|
25339
25805
|
compressMode: row.compressMode,
|
|
25806
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25340
25807
|
branch: row.branch,
|
|
25341
25808
|
worktreePath: row.worktreePath,
|
|
25342
25809
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25428,6 +25895,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25428
25895
|
runtimeMode: threadRow.value.runtimeMode,
|
|
25429
25896
|
interactionMode: threadRow.value.interactionMode,
|
|
25430
25897
|
compressMode: threadRow.value.compressMode,
|
|
25898
|
+
unpromptedSubagents: threadRow.value.unpromptedSubagents > 0,
|
|
25431
25899
|
branch: threadRow.value.branch,
|
|
25432
25900
|
worktreePath: threadRow.value.worktreePath,
|
|
25433
25901
|
latestTurn: Option.isSome(latestTurnRow) ? mapLatestTurn(latestTurnRow.value) : null,
|
|
@@ -25464,6 +25932,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25464
25932
|
runtimeMode: threadRow.value.runtimeMode,
|
|
25465
25933
|
interactionMode: threadRow.value.interactionMode,
|
|
25466
25934
|
compressMode: threadRow.value.compressMode,
|
|
25935
|
+
unpromptedSubagents: threadRow.value.unpromptedSubagents > 0,
|
|
25467
25936
|
branch: threadRow.value.branch,
|
|
25468
25937
|
worktreePath: threadRow.value.worktreePath,
|
|
25469
25938
|
latestTurn: Option.isSome(latestTurnRow) ? mapLatestTurn(latestTurnRow.value) : null,
|
|
@@ -28873,6 +29342,7 @@ const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
|
|
|
28873
29342
|
modelSelection: nextProjectDefaultModelSelection,
|
|
28874
29343
|
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
|
|
28875
29344
|
compressMode: DEFAULT_COMPRESS_MODE,
|
|
29345
|
+
unpromptedSubagents: true,
|
|
28876
29346
|
runtimeMode: "full-access",
|
|
28877
29347
|
branch: null,
|
|
28878
29348
|
worktreePath: null,
|
|
@@ -34856,6 +35326,7 @@ const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
|
|
|
34856
35326
|
repositoryKey: repositoryKey ?? input.repositoryKey ?? null,
|
|
34857
35327
|
threadId: input.threadId ?? null,
|
|
34858
35328
|
externalRef: input.externalRef ?? null,
|
|
35329
|
+
parentTaskId: input.parentTaskId ?? null,
|
|
34859
35330
|
title: input.title,
|
|
34860
35331
|
body: input.body ?? "",
|
|
34861
35332
|
status: input.status ?? "backlog",
|
|
@@ -38261,6 +38732,38 @@ const requireThreadCapability = Effect.fn("mcp.requireThreadCapability")(functio
|
|
|
38261
38732
|
});
|
|
38262
38733
|
return invocation;
|
|
38263
38734
|
});
|
|
38735
|
+
/**
|
|
38736
|
+
* The thread a control tool may act on, given what it was asked to act on.
|
|
38737
|
+
*
|
|
38738
|
+
* Two checks like `requireWatchCapability`, and for the same reason: the
|
|
38739
|
+
* capability says this session controls threads at all, the grant says which.
|
|
38740
|
+
* An omitted id resolves to the session's own thread, which is always in the
|
|
38741
|
+
* grant and is why the common call needs no id and so cannot name a wrong one.
|
|
38742
|
+
*/
|
|
38743
|
+
const requireThreadControlTarget = Effect.fn("mcp.requireThreadControlTarget")(function* (requested) {
|
|
38744
|
+
const invocation = yield* requireThreadCapability();
|
|
38745
|
+
if (requested === void 0 || requested === invocation.threadId) return {
|
|
38746
|
+
invocation,
|
|
38747
|
+
threadId: invocation.threadId
|
|
38748
|
+
};
|
|
38749
|
+
if (!invocation.controlThreadIds?.has(requested)) return yield* new ThreadControlNotPermittedError({ threadId: requested });
|
|
38750
|
+
return {
|
|
38751
|
+
invocation,
|
|
38752
|
+
threadId: requested
|
|
38753
|
+
};
|
|
38754
|
+
});
|
|
38755
|
+
/**
|
|
38756
|
+
* The guard on starting a thread, which is a different question from acting on
|
|
38757
|
+
* one: it is not "which thread" but "may this session make more of them".
|
|
38758
|
+
*/
|
|
38759
|
+
const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* () {
|
|
38760
|
+
const invocation = yield* requireThreadCapability();
|
|
38761
|
+
if (invocation.mayCreateThreads !== true) return yield* new ThreadSpawnNotPermittedError({
|
|
38762
|
+
threadId: invocation.threadId,
|
|
38763
|
+
detail: "this thread was itself started by an agent, and spawning goes one level deep so a runaway loop has a bound"
|
|
38764
|
+
});
|
|
38765
|
+
return invocation;
|
|
38766
|
+
});
|
|
38264
38767
|
//#endregion
|
|
38265
38768
|
//#region src/mcp/PreviewAutomationBroker.ts
|
|
38266
38769
|
var PreviewAutomationBroker = class extends Context.Service()("@p4code/cli/mcp/PreviewAutomationBroker") {};
|
|
@@ -45693,6 +46196,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
45693
46196
|
runtimeMode: bootstrap.createThread.runtimeMode,
|
|
45694
46197
|
interactionMode: bootstrap.createThread.interactionMode,
|
|
45695
46198
|
compressMode: bootstrap.createThread.compressMode,
|
|
46199
|
+
unpromptedSubagents: bootstrap.createThread.unpromptedSubagents,
|
|
45696
46200
|
branch: bootstrap.createThread.branch,
|
|
45697
46201
|
worktreePath: bootstrap.createThread.worktreePath,
|
|
45698
46202
|
createdAt: bootstrap.createThread.createdAt
|
|
@@ -45957,6 +46461,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
45957
46461
|
runtimeMode: DEFAULT_RUNTIME_MODE$2,
|
|
45958
46462
|
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
|
|
45959
46463
|
compressMode: DEFAULT_COMPRESS_MODE,
|
|
46464
|
+
unpromptedSubagents: true,
|
|
45960
46465
|
bootstrap: { createThread: {
|
|
45961
46466
|
projectId: plan.projectId,
|
|
45962
46467
|
title: threadTitle,
|
|
@@ -45964,6 +46469,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
45964
46469
|
runtimeMode: DEFAULT_RUNTIME_MODE$2,
|
|
45965
46470
|
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
|
|
45966
46471
|
compressMode: DEFAULT_COMPRESS_MODE,
|
|
46472
|
+
unpromptedSubagents: true,
|
|
45967
46473
|
branch: null,
|
|
45968
46474
|
worktreePath: null,
|
|
45969
46475
|
createdAt
|
|
@@ -46374,6 +46880,8 @@ const makeRemoteTaskRepository = Effect.fn("makeRemoteTaskRepository")(function*
|
|
|
46374
46880
|
...row.projectId === null ? {} : { projectId: row.projectId },
|
|
46375
46881
|
...row.repositoryKey === null ? {} : { repositoryKey: row.repositoryKey },
|
|
46376
46882
|
...row.threadId === null ? {} : { threadId: row.threadId },
|
|
46883
|
+
...row.externalRef === null ? {} : { externalRef: row.externalRef },
|
|
46884
|
+
...row.parentTaskId === null ? {} : { parentTaskId: row.parentTaskId },
|
|
46377
46885
|
title: row.title,
|
|
46378
46886
|
body: row.body,
|
|
46379
46887
|
status: row.status,
|
|
@@ -47261,7 +47769,10 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47261
47769
|
const crypto = yield* Crypto.Crypto;
|
|
47262
47770
|
const environmentId = yield* (yield* ServerEnvironment).getEnvironmentId;
|
|
47263
47771
|
const httpServer = yield* HttpServer.HttpServer;
|
|
47264
|
-
const state = yield* SynchronizedRef.make({
|
|
47772
|
+
const state = yield* SynchronizedRef.make({
|
|
47773
|
+
records: /* @__PURE__ */ new Map(),
|
|
47774
|
+
spawnedThreadIds: /* @__PURE__ */ new Set()
|
|
47775
|
+
});
|
|
47265
47776
|
const currentTimeMillis = options.now ? Effect.sync(options.now) : Clock.currentTimeMillis;
|
|
47266
47777
|
const livenessWindowMs = options.livenessWindowMs ?? DEFAULT_LIVENESS_WINDOW_MS;
|
|
47267
47778
|
const endpoint = httpServer.address._tag === "TcpAddress" ? `http://${getHttpMcpEndpointHost(httpServer.address.hostname)}:${httpServer.address.port}/mcp` : "http://127.0.0.1/mcp";
|
|
@@ -47282,29 +47793,35 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47282
47793
|
"threads"
|
|
47283
47794
|
]);
|
|
47284
47795
|
if (watchThreadIds.size > 0) capabilities.add("watch");
|
|
47285
|
-
const
|
|
47796
|
+
const threadId = ThreadId.make(request.threadId);
|
|
47797
|
+
const scopeWith = (mayCreateThreads) => ({
|
|
47286
47798
|
environmentId,
|
|
47287
|
-
threadId
|
|
47799
|
+
threadId,
|
|
47288
47800
|
providerSessionId,
|
|
47289
47801
|
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
47290
47802
|
capabilities,
|
|
47291
47803
|
...watchThreadIds.size > 0 ? { watchThreadIds } : {},
|
|
47804
|
+
mayCreateThreads,
|
|
47292
47805
|
issuedAt
|
|
47293
|
-
};
|
|
47294
|
-
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
47806
|
+
});
|
|
47807
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47295
47808
|
const next = new Map(pruneDead(records, issuedAt));
|
|
47296
47809
|
next.set(tokenHash, {
|
|
47297
47810
|
tokenHash,
|
|
47298
|
-
scope,
|
|
47811
|
+
scope: scopeWith(!spawnedThreadIds.has(threadId)),
|
|
47812
|
+
controlThreadIds: /* @__PURE__ */ new Set(),
|
|
47299
47813
|
lastAliveAt: issuedAt
|
|
47300
47814
|
});
|
|
47301
|
-
return {
|
|
47815
|
+
return {
|
|
47816
|
+
records: next,
|
|
47817
|
+
spawnedThreadIds
|
|
47818
|
+
};
|
|
47302
47819
|
});
|
|
47303
47820
|
return { config: {
|
|
47304
47821
|
environmentId,
|
|
47305
|
-
threadId
|
|
47822
|
+
threadId,
|
|
47306
47823
|
providerSessionId,
|
|
47307
|
-
providerInstanceId:
|
|
47824
|
+
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
47308
47825
|
endpoint,
|
|
47309
47826
|
authorizationHeader: `Bearer ${rawToken}`
|
|
47310
47827
|
} };
|
|
@@ -47313,42 +47830,76 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47313
47830
|
if (rawToken.length === 0) return void 0;
|
|
47314
47831
|
const tokenHash = yield* hashToken(rawToken);
|
|
47315
47832
|
const timestamp = yield* currentTimeMillis;
|
|
47316
|
-
return yield* SynchronizedRef.modify(state, ({ records }) => {
|
|
47833
|
+
return yield* SynchronizedRef.modify(state, ({ records, spawnedThreadIds }) => {
|
|
47317
47834
|
const current = pruneDead(records, timestamp);
|
|
47318
47835
|
const record = current.get(tokenHash);
|
|
47319
|
-
if (!record) return [void 0, {
|
|
47836
|
+
if (!record) return [void 0, {
|
|
47837
|
+
records: current,
|
|
47838
|
+
spawnedThreadIds
|
|
47839
|
+
}];
|
|
47320
47840
|
const next = new Map(current);
|
|
47321
47841
|
next.set(tokenHash, {
|
|
47322
47842
|
...record,
|
|
47323
47843
|
lastAliveAt: timestamp
|
|
47324
47844
|
});
|
|
47325
|
-
return [record.scope
|
|
47845
|
+
return [record.controlThreadIds.size === 0 ? record.scope : {
|
|
47846
|
+
...record.scope,
|
|
47847
|
+
capabilities: /* @__PURE__ */ new Set([...record.scope.capabilities, "watch"]),
|
|
47848
|
+
controlThreadIds: record.controlThreadIds,
|
|
47849
|
+
watchThreadIds: /* @__PURE__ */ new Set([...record.scope.watchThreadIds ?? [], ...record.controlThreadIds])
|
|
47850
|
+
}, {
|
|
47851
|
+
records: next,
|
|
47852
|
+
spawnedThreadIds
|
|
47853
|
+
}];
|
|
47326
47854
|
});
|
|
47327
47855
|
});
|
|
47328
47856
|
const touch = Effect.fn("McpSessionRegistry.touch")(function* (threadId) {
|
|
47329
47857
|
const timestamp = yield* currentTimeMillis;
|
|
47330
|
-
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
47858
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47331
47859
|
const current = pruneDead(records, timestamp);
|
|
47332
47860
|
const next = new Map(current);
|
|
47333
47861
|
for (const [tokenHash, record] of current) if (record.scope.threadId === threadId) next.set(tokenHash, {
|
|
47334
47862
|
...record,
|
|
47335
47863
|
lastAliveAt: timestamp
|
|
47336
47864
|
});
|
|
47337
|
-
return {
|
|
47865
|
+
return {
|
|
47866
|
+
records: next,
|
|
47867
|
+
spawnedThreadIds
|
|
47868
|
+
};
|
|
47869
|
+
});
|
|
47870
|
+
});
|
|
47871
|
+
const recordSpawnedThread = Effect.fn("McpSessionRegistry.recordSpawnedThread")(function* (input) {
|
|
47872
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47873
|
+
const next = new Map(records);
|
|
47874
|
+
for (const [tokenHash, record] of records) if (record.scope.providerSessionId === input.providerSessionId) next.set(tokenHash, {
|
|
47875
|
+
...record,
|
|
47876
|
+
controlThreadIds: /* @__PURE__ */ new Set([...record.controlThreadIds, input.threadId])
|
|
47877
|
+
});
|
|
47878
|
+
return {
|
|
47879
|
+
records: next,
|
|
47880
|
+
spawnedThreadIds: /* @__PURE__ */ new Set([...spawnedThreadIds, input.threadId])
|
|
47881
|
+
};
|
|
47338
47882
|
});
|
|
47339
47883
|
});
|
|
47340
|
-
const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records }) => ({
|
|
47884
|
+
const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => ({
|
|
47885
|
+
records: new Map(Array.from(records).filter(([, record]) => !predicate(record))),
|
|
47886
|
+
spawnedThreadIds
|
|
47887
|
+
}));
|
|
47341
47888
|
return McpSessionRegistry.of({
|
|
47342
47889
|
issue,
|
|
47343
47890
|
resolve,
|
|
47344
47891
|
touch,
|
|
47892
|
+
recordSpawnedThread,
|
|
47345
47893
|
revokeProviderSession: Effect.fn("McpSessionRegistry.revokeProviderSession")(function* (providerSessionId) {
|
|
47346
47894
|
yield* revokeWhere((record) => record.scope.providerSessionId === providerSessionId);
|
|
47347
47895
|
}),
|
|
47348
47896
|
revokeThread: Effect.fn("McpSessionRegistry.revokeThread")(function* (threadId) {
|
|
47349
47897
|
yield* revokeWhere((record) => record.scope.threadId === threadId);
|
|
47350
47898
|
}),
|
|
47351
|
-
revokeAll: SynchronizedRef.
|
|
47899
|
+
revokeAll: SynchronizedRef.update(state, ({ spawnedThreadIds }) => ({
|
|
47900
|
+
records: /* @__PURE__ */ new Map(),
|
|
47901
|
+
spawnedThreadIds
|
|
47902
|
+
}))
|
|
47352
47903
|
});
|
|
47353
47904
|
});
|
|
47354
47905
|
let activeMcpSessionRegistry;
|
|
@@ -52502,7 +53053,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
52502
53053
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
52503
53054
|
const externalMcpServers = options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers;
|
|
52504
53055
|
const narrateBeforeTools = options?.resolveToolCallNarration === void 0 ? DEFAULT_SERVER_SETTINGS.enableToolCallNarration : yield* options.resolveToolCallNarration;
|
|
52505
|
-
const unpromptedSubagents = options?.resolveUnpromptedSubagents === void 0 ? DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents : yield* options.resolveUnpromptedSubagents;
|
|
53056
|
+
const unpromptedSubagents = input.unpromptedSubagents !== void 0 ? input.unpromptedSubagents : options?.resolveUnpromptedSubagents === void 0 ? DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents : yield* options.resolveUnpromptedSubagents;
|
|
52506
53057
|
const compressRuleset = compressRulesetFor(input.compressMode ?? "off");
|
|
52507
53058
|
const systemPromptAppend = [
|
|
52508
53059
|
...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [],
|
|
@@ -52601,6 +53152,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
52601
53152
|
startedAt,
|
|
52602
53153
|
basePermissionMode: permissionMode,
|
|
52603
53154
|
currentApiModelId: apiModelId,
|
|
53155
|
+
currentUnpromptedSubagents: unpromptedSubagents,
|
|
52604
53156
|
resumeSessionId: sessionId,
|
|
52605
53157
|
pendingApprovals,
|
|
52606
53158
|
pendingUserInputs,
|
|
@@ -52672,7 +53224,13 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
52672
53224
|
const context = yield* requireSession(input.threadId);
|
|
52673
53225
|
const modelSelection = input.modelSelection !== void 0 && input.modelSelection.instanceId === boundInstanceId ? input.modelSelection : void 0;
|
|
52674
53226
|
if (context.turnState?.synthetic === true) yield* completeTurn(context, "completed");
|
|
52675
|
-
const
|
|
53227
|
+
const turnSubagentsPrompt = input.unpromptedSubagents !== void 0 && input.unpromptedSubagents !== context.currentUnpromptedSubagents ? input.unpromptedSubagents ? SUBAGENTS_ALLOWED_PROMPT : SUBAGENTS_ON_REQUEST_PROMPT : void 0;
|
|
53228
|
+
if (input.unpromptedSubagents !== void 0) context.currentUnpromptedSubagents = input.unpromptedSubagents;
|
|
53229
|
+
const turnInput = turnSubagentsPrompt === void 0 ? input : {
|
|
53230
|
+
...input,
|
|
53231
|
+
input: input.input === void 0 ? turnSubagentsPrompt : `${turnSubagentsPrompt}\n\n${input.input}`
|
|
53232
|
+
};
|
|
53233
|
+
const message = yield* buildUserMessageEffect(turnInput, {
|
|
52676
53234
|
fileSystem,
|
|
52677
53235
|
attachmentsDir: serverConfig.attachmentsDir,
|
|
52678
53236
|
boundInstanceId
|
|
@@ -85945,6 +86503,28 @@ const findTask = (taskId) => Effect.gen(function* () {
|
|
|
85945
86503
|
const tasks = yield* TaskRepository;
|
|
85946
86504
|
return isReadableTaskId(taskId) ? yield* tasks.getByReadableId(taskId) : yield* tasks.getById({ taskId });
|
|
85947
86505
|
});
|
|
86506
|
+
/**
|
|
86507
|
+
* Resolve a parent reference to the UUID the column stores.
|
|
86508
|
+
*
|
|
86509
|
+
* A planning agent works in readable ids - it has just read `P4-12` off the
|
|
86510
|
+
* board - but the link is stored as the row key, because readable ids are
|
|
86511
|
+
* allocated per store and the one it quotes may have been minted on another
|
|
86512
|
+
* machine. Absent stays absent and an explicit null stays null: those mean
|
|
86513
|
+
* "leave it" and "clear it", neither of which is a reference to resolve.
|
|
86514
|
+
*
|
|
86515
|
+
* A readable id that names no task fails rather than being stored raw. The
|
|
86516
|
+
* opposite would file a subtask whose parent link can never resolve, which
|
|
86517
|
+
* reads on the board as a task that is part of nothing. A UUID is taken on
|
|
86518
|
+
* trust, matching the column's lack of a foreign key: a parent may legitimately
|
|
86519
|
+
* arrive after its child on a board synced from elsewhere.
|
|
86520
|
+
*/
|
|
86521
|
+
const resolveParentTaskId = (parentTaskId, operation) => Effect.gen(function* () {
|
|
86522
|
+
if (parentTaskId === void 0 || parentTaskId === null) return parentTaskId;
|
|
86523
|
+
if (!isReadableTaskId(parentTaskId)) return parentTaskId;
|
|
86524
|
+
const parent = yield* findTask(parentTaskId).pipe(Effect.catch(toTaskStoreError(operation)));
|
|
86525
|
+
if (Option.isNone(parent)) return yield* new TaskNotFoundError({ taskId: parentTaskId });
|
|
86526
|
+
return parent.value.taskId;
|
|
86527
|
+
});
|
|
85948
86528
|
const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
85949
86529
|
task_list: (input) => Effect.gen(function* () {
|
|
85950
86530
|
yield* requireTaskCapability();
|
|
@@ -85972,7 +86552,11 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
85972
86552
|
const timestamp = yield* nowIso$1;
|
|
85973
86553
|
const repositoryKey = yield* resolveTaskRepositoryKey(input.projectId);
|
|
85974
86554
|
const readableIdPrefix = yield* resolveTaskReadableIdPrefix(input.projectId);
|
|
85975
|
-
const
|
|
86555
|
+
const parentTaskId = yield* resolveParentTaskId(input.parentTaskId, "task_create");
|
|
86556
|
+
const row = buildTaskRow(parentTaskId === void 0 || parentTaskId === null ? input : {
|
|
86557
|
+
...input,
|
|
86558
|
+
parentTaskId
|
|
86559
|
+
}, taskId, timestamp, repositoryKey);
|
|
85976
86560
|
return yield* tasks.create(row, readableIdPrefix).pipe(Effect.catch(toTaskStoreError("task_create")));
|
|
85977
86561
|
}),
|
|
85978
86562
|
task_update: (input) => Effect.gen(function* () {
|
|
@@ -85981,13 +86565,12 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
85981
86565
|
const timestamp = yield* nowIso$1;
|
|
85982
86566
|
const taskId = isReadableTaskId(input.taskId) ? Option.getOrNull(yield* findTask(input.taskId).pipe(Effect.catch(toTaskStoreError("task_update"))))?.taskId : input.taskId;
|
|
85983
86567
|
if (taskId === void 0) return yield* new TaskNotFoundError({ taskId: input.taskId });
|
|
85984
|
-
const
|
|
85985
|
-
|
|
85986
|
-
taskId
|
|
85987
|
-
} : {
|
|
86568
|
+
const parentTaskId = yield* resolveParentTaskId(input.parentTaskId, "task_update");
|
|
86569
|
+
const patch = {
|
|
85988
86570
|
...input,
|
|
85989
86571
|
taskId,
|
|
85990
|
-
|
|
86572
|
+
...parentTaskId === void 0 ? {} : { parentTaskId },
|
|
86573
|
+
...input.projectId === void 0 ? {} : { repositoryKey: yield* resolveTaskRepositoryKey(input.projectId) }
|
|
85991
86574
|
};
|
|
85992
86575
|
const patched = yield* tasks.patch(patch, timestamp).pipe(Effect.catch(toTaskStoreError("task_update")));
|
|
85993
86576
|
if (Option.isNone(patched)) return yield* new TaskNotFoundError({ taskId: input.taskId });
|
|
@@ -85999,14 +86582,295 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
85999
86582
|
})
|
|
86000
86583
|
});
|
|
86001
86584
|
//#endregion
|
|
86585
|
+
//#region src/orchestration/pendingThreadSettles.ts
|
|
86586
|
+
/**
|
|
86587
|
+
* Threads that asked to settle while their session was still running.
|
|
86588
|
+
*
|
|
86589
|
+
* The decider refuses `thread.settle` for a thread whose session is starting or
|
|
86590
|
+
* running, and an agent settling its own thread always calls from inside that
|
|
86591
|
+
* session - so the command it wants can never be the command it sends. The
|
|
86592
|
+
* intent is recorded here instead and replayed when the session stops, which is
|
|
86593
|
+
* what "settle this thread" meant from the agent's side anyway: finish, then
|
|
86594
|
+
* close.
|
|
86595
|
+
*
|
|
86596
|
+
* A module-level set rather than a service because it has to be reachable from
|
|
86597
|
+
* two layer trees that do not otherwise meet - an MCP tool handler on one side,
|
|
86598
|
+
* the provider command reactor on the other - and threading a new service
|
|
86599
|
+
* through both to hold a set of ids is more wiring than the thing is worth.
|
|
86600
|
+
* `McpProviderSession` and the active-registry handle are the same shape for
|
|
86601
|
+
* the same reason. It is intentionally not durable: a queued settle belongs to
|
|
86602
|
+
* a session, and a server restart has already ended every session there was.
|
|
86603
|
+
*/
|
|
86604
|
+
const pending = /* @__PURE__ */ new Set();
|
|
86605
|
+
const queueThreadSettle = (threadId) => {
|
|
86606
|
+
pending.add(threadId);
|
|
86607
|
+
};
|
|
86608
|
+
/**
|
|
86609
|
+
* Removes the intent and reports whether there was one, so a caller cannot
|
|
86610
|
+
* settle the same thread twice by reading and then forgetting to clear.
|
|
86611
|
+
*/
|
|
86612
|
+
const takeQueuedThreadSettle = (threadId) => pending.delete(threadId);
|
|
86613
|
+
//#endregion
|
|
86614
|
+
//#region src/sync/assetCompression.ts
|
|
86615
|
+
/**
|
|
86616
|
+
* Checking that a compressed asset still says what the original said.
|
|
86617
|
+
*
|
|
86618
|
+
* Compression here is a rewrite of prose an agent reads before it does anything
|
|
86619
|
+
* else: a skill, a memory file, a subagent definition. Rewriting that prose is
|
|
86620
|
+
* cheap to get almost right and expensive to get slightly wrong, because the
|
|
86621
|
+
* damage is silent. A dropped URL, a mangled command, a heading that no longer
|
|
86622
|
+
* matches the sentence pointing at it - none of that fails loudly. It just makes
|
|
86623
|
+
* the agent behave a little differently on every machine that syncs the file.
|
|
86624
|
+
*
|
|
86625
|
+
* So the write is gated on a comparison rather than on the model's word for it.
|
|
86626
|
+
* The rules are ported from caveman's `validate.py`, with the ones this task
|
|
86627
|
+
* calls out - headings, code blocks, URLs, paths, bullet counts - promoted to
|
|
86628
|
+
* refusals rather than warnings. A refusal costs one retry; a bad write costs
|
|
86629
|
+
* whatever the agent does next, on every machine, until someone notices.
|
|
86630
|
+
*
|
|
86631
|
+
* Pure on purpose. Everything here is a string comparison, so every rule can be
|
|
86632
|
+
* tested without a filesystem, a provider or a thread, and the tool that calls
|
|
86633
|
+
* it is left with only the parts that genuinely touch disk.
|
|
86634
|
+
*
|
|
86635
|
+
* @module sync/assetCompression
|
|
86636
|
+
*/
|
|
86637
|
+
/**
|
|
86638
|
+
* How far the bullet count may move before the rewrite stops being compression.
|
|
86639
|
+
*
|
|
86640
|
+
* Not zero. Compression legitimately folds a two-line bullet into one, and a
|
|
86641
|
+
* rule that forbade that would forbid the feature. Caveman's own threshold,
|
|
86642
|
+
* kept because it is the one their receipts were measured against.
|
|
86643
|
+
*/
|
|
86644
|
+
const BULLET_COUNT_TOLERANCE = .15;
|
|
86645
|
+
/**
|
|
86646
|
+
* Characters per token, for the before/after figures.
|
|
86647
|
+
*
|
|
86648
|
+
* An estimate, and labelled as one everywhere it surfaces. The exact count
|
|
86649
|
+
* belongs to whichever provider tokenizer will read the file, and asking one
|
|
86650
|
+
* would mean a network call inside a validation path that is otherwise pure.
|
|
86651
|
+
* Four is the usual English-prose approximation and is good enough to answer
|
|
86652
|
+
* the only question anyone asks here: did this get meaningfully smaller.
|
|
86653
|
+
*/
|
|
86654
|
+
const CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
86655
|
+
const URL_REGEX = /https?:\/\/[^\s)]+/gu;
|
|
86656
|
+
/**
|
|
86657
|
+
* Sentence punctuation swept up by the URL match.
|
|
86658
|
+
*
|
|
86659
|
+
* `See https://svgl.app for the catalogue.` and `Catalogue: https://svgl.app.`
|
|
86660
|
+
* name the same URL, and a comparison that disagreed would refuse every rewrite
|
|
86661
|
+
* that moved a link to the end of a sentence - which is most of them.
|
|
86662
|
+
*/
|
|
86663
|
+
const URL_TRAILING_PUNCTUATION_REGEX = /[.,;:!?'"\])]+$/u;
|
|
86664
|
+
const FENCE_LINE_REGEX = /^(\s{0,3})(`{3,}|~{3,})(.*)$/u;
|
|
86665
|
+
const HEADING_REGEX = /^(#{1,6})\s+(.*)$/gmu;
|
|
86666
|
+
const BULLET_REGEX = /^\s*[-*+]\s+/gmu;
|
|
86667
|
+
const INLINE_CODE_REGEX = /`([^`]+)`/gu;
|
|
86668
|
+
/**
|
|
86669
|
+
* Crude, and deliberately so.
|
|
86670
|
+
*
|
|
86671
|
+
* Either a path prefix (`./`, `../`, `/`, a drive letter) or a separator inside
|
|
86672
|
+
* the match. It over-matches - `and/or` is a "path" by this rule - and the
|
|
86673
|
+
* over-match is the safe direction: a false positive refuses a rewrite that
|
|
86674
|
+
* dropped the phrase, and a false negative lets a real path go missing.
|
|
86675
|
+
*/
|
|
86676
|
+
const PATH_REGEX = /(?:\.\/|\.\.\/|\/|[A-Za-z]:\\)[\w\-/\\.]+|[\w\-.]+[/\\][\w\-/\\.]+/gu;
|
|
86677
|
+
/** YAML frontmatter: `---` on the first line, `---` on a line of its own after. */
|
|
86678
|
+
const FRONTMATTER_REGEX = /^---\r?\n[\s\S]*?\r?\n---\r?\n/u;
|
|
86679
|
+
/**
|
|
86680
|
+
* Filenames that almost certainly hold secrets, and the directories that do.
|
|
86681
|
+
*
|
|
86682
|
+
* Compressing a file means handing its bytes to a model, so this is checked
|
|
86683
|
+
* before the read rather than after it. The asset roots should never contain
|
|
86684
|
+
* any of these, which is exactly why the check is cheap to keep: it costs
|
|
86685
|
+
* nothing in the ordinary case and is the only thing standing between a
|
|
86686
|
+
* misplaced `credentials.md` and a provider's API.
|
|
86687
|
+
*/
|
|
86688
|
+
const SENSITIVE_BASENAME_REGEX = /^(\.env(\..+)?|\.netrc|credentials(\..+)?|secrets?(\..+)?|passwords?(\..+)?|id_(rsa|dsa|ecdsa|ed25519)(\.pub)?|authorized_keys|known_hosts|.*\.(pem|key|p12|pfx|crt|cer|jks|keystore|asc|gpg))$/iu;
|
|
86689
|
+
const SENSITIVE_PATH_SEGMENTS = /* @__PURE__ */ new Set([
|
|
86690
|
+
".ssh",
|
|
86691
|
+
".aws",
|
|
86692
|
+
".gnupg",
|
|
86693
|
+
".kube",
|
|
86694
|
+
".docker"
|
|
86695
|
+
]);
|
|
86696
|
+
const SENSITIVE_NAME_TOKENS = [
|
|
86697
|
+
"secret",
|
|
86698
|
+
"credential",
|
|
86699
|
+
"password",
|
|
86700
|
+
"passwd",
|
|
86701
|
+
"apikey",
|
|
86702
|
+
"accesskey",
|
|
86703
|
+
"token",
|
|
86704
|
+
"privatekey"
|
|
86705
|
+
];
|
|
86706
|
+
const segmentsOf = (filePath) => filePath.split(/[/\\]/u).filter((segment) => segment.length > 0);
|
|
86707
|
+
/**
|
|
86708
|
+
* Whether this path looks like something that must not be sent to a model.
|
|
86709
|
+
*
|
|
86710
|
+
* Name-based, because that is all that is knowable before the read. Separators
|
|
86711
|
+
* inside the name are normalized away first so `api-key.md` and `api_key.md`
|
|
86712
|
+
* are the same answer.
|
|
86713
|
+
*/
|
|
86714
|
+
const isSensitiveAssetPath = (filePath) => {
|
|
86715
|
+
const segments = segmentsOf(filePath);
|
|
86716
|
+
const name = segments.at(-1) ?? "";
|
|
86717
|
+
if (SENSITIVE_BASENAME_REGEX.test(name)) return true;
|
|
86718
|
+
if (segments.some((segment) => SENSITIVE_PATH_SEGMENTS.has(segment.toLowerCase()))) return true;
|
|
86719
|
+
const flattened = name.toLowerCase().replace(/[_\-\s.]/gu, "");
|
|
86720
|
+
return SENSITIVE_NAME_TOKENS.some((token) => flattened.includes(token));
|
|
86721
|
+
};
|
|
86722
|
+
/** The frontmatter block including its delimiters, or the empty string. */
|
|
86723
|
+
const frontmatterOf = (text) => FRONTMATTER_REGEX.exec(text)?.[0] ?? "";
|
|
86724
|
+
const headingsOf = (text) => [...text.matchAll(HEADING_REGEX)].map((match) => `${match[1] ?? ""} ${(match[2] ?? "").trim()}`);
|
|
86725
|
+
/**
|
|
86726
|
+
* Fenced code blocks, as whole strings, fences included.
|
|
86727
|
+
*
|
|
86728
|
+
* Line-based rather than one regex, so a closing fence is only a closing fence
|
|
86729
|
+
* when CommonMark says it is: same character, at least as long as the opener,
|
|
86730
|
+
* nothing after it. That is what lets a four-backtick block wrap three-backtick
|
|
86731
|
+
* content without the extractor stopping in the middle of it.
|
|
86732
|
+
*
|
|
86733
|
+
* An unclosed fence is skipped rather than reported. It means the markdown was
|
|
86734
|
+
* already malformed, and including it would fail every compression of a file
|
|
86735
|
+
* that was broken before anyone touched it.
|
|
86736
|
+
*/
|
|
86737
|
+
const codeBlocksOf = (text) => {
|
|
86738
|
+
const lines = text.split("\n");
|
|
86739
|
+
const blocks = [];
|
|
86740
|
+
let index = 0;
|
|
86741
|
+
while (index < lines.length) {
|
|
86742
|
+
const opening = FENCE_LINE_REGEX.exec(lines[index] ?? "");
|
|
86743
|
+
if (opening === null) {
|
|
86744
|
+
index += 1;
|
|
86745
|
+
continue;
|
|
86746
|
+
}
|
|
86747
|
+
const fence = opening[2] ?? "";
|
|
86748
|
+
const collected = [lines[index] ?? ""];
|
|
86749
|
+
index += 1;
|
|
86750
|
+
let closed = false;
|
|
86751
|
+
while (index < lines.length) {
|
|
86752
|
+
const line = lines[index] ?? "";
|
|
86753
|
+
const closing = FENCE_LINE_REGEX.exec(line);
|
|
86754
|
+
const closingFence = closing?.[2] ?? "";
|
|
86755
|
+
if (closing !== null && closingFence[0] === fence[0] && closingFence.length >= fence.length && (closing[3] ?? "").trim().length === 0) {
|
|
86756
|
+
collected.push(line);
|
|
86757
|
+
closed = true;
|
|
86758
|
+
index += 1;
|
|
86759
|
+
break;
|
|
86760
|
+
}
|
|
86761
|
+
collected.push(line);
|
|
86762
|
+
index += 1;
|
|
86763
|
+
}
|
|
86764
|
+
if (closed) blocks.push(collected.join("\n"));
|
|
86765
|
+
}
|
|
86766
|
+
return blocks;
|
|
86767
|
+
};
|
|
86768
|
+
const withoutCodeBlocks = (text) => codeBlocksOf(text).reduce((rest, block) => rest.replace(block, ""), text);
|
|
86769
|
+
const inlineCodeCounts = (text) => {
|
|
86770
|
+
const counts = /* @__PURE__ */ new Map();
|
|
86771
|
+
for (const match of withoutCodeBlocks(text).matchAll(INLINE_CODE_REGEX)) {
|
|
86772
|
+
const span = match[1] ?? "";
|
|
86773
|
+
counts.set(span, (counts.get(span) ?? 0) + 1);
|
|
86774
|
+
}
|
|
86775
|
+
return counts;
|
|
86776
|
+
};
|
|
86777
|
+
const urlsOf = (text) => new Set([...text.matchAll(URL_REGEX)].map((match) => match[0].replace(URL_TRAILING_PUNCTUATION_REGEX, "")));
|
|
86778
|
+
/**
|
|
86779
|
+
* Paths, with URLs taken out of the text first.
|
|
86780
|
+
*
|
|
86781
|
+
* `//svgl.app` inside `https://svgl.app` is a path by the pattern's reckoning,
|
|
86782
|
+
* so leaving the URLs in would have every link contribute a phantom path whose
|
|
86783
|
+
* spelling changes with the punctuation after it.
|
|
86784
|
+
*/
|
|
86785
|
+
const pathsOf = (text) => new Set([...text.replace(URL_REGEX, " ").matchAll(PATH_REGEX)].map((match) => match[0]));
|
|
86786
|
+
const countOf = (text, pattern) => [...text.matchAll(pattern)].length;
|
|
86787
|
+
const missingFrom = (original, compressed) => [...original].filter((value) => !compressed.has(value));
|
|
86788
|
+
const addedTo = (original, compressed) => [...compressed].filter((value) => !original.has(value));
|
|
86789
|
+
const quoteList = (values) => values.map((value) => `'${value}'`).join(", ");
|
|
86790
|
+
/**
|
|
86791
|
+
* Compare a compressed rewrite against the original it came from.
|
|
86792
|
+
*
|
|
86793
|
+
* Errors are the things whose loss changes what the file instructs: a code
|
|
86794
|
+
* block, a URL, a path, an inline span, a heading, the frontmatter. Additions
|
|
86795
|
+
* are warnings rather than errors throughout, because a rewrite that names one
|
|
86796
|
+
* more path than the original is doing something odd but is not losing
|
|
86797
|
+
* anything, and a refusal there would be a refusal nobody can act on.
|
|
86798
|
+
*/
|
|
86799
|
+
const validateCompression = (original, compressed) => {
|
|
86800
|
+
const errors = [];
|
|
86801
|
+
const warnings = [];
|
|
86802
|
+
if (frontmatterOf(original) !== frontmatterOf(compressed)) errors.push("The YAML frontmatter changed. It is metadata the provider parses, not prose; return it byte-identical.");
|
|
86803
|
+
const originalHeadings = headingsOf(original);
|
|
86804
|
+
const compressedHeadings = headingsOf(compressed);
|
|
86805
|
+
if (originalHeadings.length !== compressedHeadings.length || originalHeadings.some((heading, at) => heading !== compressedHeadings[at])) errors.push(`Headings changed: ${originalHeadings.length} in the original, ${compressedHeadings.length} in the rewrite. Keep every heading, in order, with its exact text.`);
|
|
86806
|
+
const originalBlocks = codeBlocksOf(original);
|
|
86807
|
+
const compressedBlocks = codeBlocksOf(compressed);
|
|
86808
|
+
if (originalBlocks.length !== compressedBlocks.length || originalBlocks.some((block, at) => block !== compressedBlocks[at])) errors.push(`Code blocks are not byte-identical: ${originalBlocks.length} in the original, ${compressedBlocks.length} in the rewrite. Copy them through unchanged.`);
|
|
86809
|
+
const originalUrls = urlsOf(original);
|
|
86810
|
+
const compressedUrls = urlsOf(compressed);
|
|
86811
|
+
const lostUrls = missingFrom(originalUrls, compressedUrls);
|
|
86812
|
+
if (lostUrls.length > 0) errors.push(`URLs missing from the rewrite: ${quoteList(lostUrls)}.`);
|
|
86813
|
+
const addedUrls = addedTo(originalUrls, compressedUrls);
|
|
86814
|
+
if (addedUrls.length > 0) warnings.push(`URLs the original did not have: ${quoteList(addedUrls)}.`);
|
|
86815
|
+
const originalPaths = pathsOf(original);
|
|
86816
|
+
const compressedPaths = pathsOf(compressed);
|
|
86817
|
+
const lostPaths = missingFrom(originalPaths, compressedPaths);
|
|
86818
|
+
if (lostPaths.length > 0) errors.push(`Paths missing from the rewrite: ${quoteList(lostPaths)}.`);
|
|
86819
|
+
const addedPaths = addedTo(originalPaths, compressedPaths);
|
|
86820
|
+
if (addedPaths.length > 0) warnings.push(`Paths the original did not have: ${quoteList(addedPaths)}.`);
|
|
86821
|
+
const originalInline = inlineCodeCounts(original);
|
|
86822
|
+
const compressedInline = inlineCodeCounts(compressed);
|
|
86823
|
+
const lostInline = [...originalInline].filter(([span, count]) => (compressedInline.get(span) ?? 0) < count);
|
|
86824
|
+
if (lostInline.length > 0) errors.push(`Inline code spans missing from the rewrite: ${quoteList(lostInline.map(([span]) => span))}.`);
|
|
86825
|
+
const originalBullets = countOf(original, BULLET_REGEX);
|
|
86826
|
+
const compressedBullets = countOf(compressed, BULLET_REGEX);
|
|
86827
|
+
if (originalBullets > 0) {
|
|
86828
|
+
if (Math.abs(originalBullets - compressedBullets) / originalBullets > BULLET_COUNT_TOLERANCE) errors.push(`Bullet count moved from ${originalBullets} to ${compressedBullets}, past the ${Math.round(BULLET_COUNT_TOLERANCE * 100)}% tolerance. Compress the wording of each bullet rather than merging or dropping bullets.`);
|
|
86829
|
+
}
|
|
86830
|
+
return {
|
|
86831
|
+
errors,
|
|
86832
|
+
warnings
|
|
86833
|
+
};
|
|
86834
|
+
};
|
|
86835
|
+
/** Roughly how many tokens this text costs to read. Estimated, never measured. */
|
|
86836
|
+
const estimateTokens = (text) => Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
|
|
86837
|
+
/**
|
|
86838
|
+
* The name a backup of this file is kept under.
|
|
86839
|
+
*
|
|
86840
|
+
* Timestamped rather than fixed. Caveman refuses to compress when a backup
|
|
86841
|
+
* already exists, which protects the first original at the cost of blocking
|
|
86842
|
+
* every later compression; keeping one backup per write protects every version
|
|
86843
|
+
* instead, including the edits someone made between two compressions.
|
|
86844
|
+
*
|
|
86845
|
+
* The parent directory's name is carried in the path rather than the file name
|
|
86846
|
+
* so that `global-memory/feedback_svgl.md` and `svgl/SKILL.md` cannot collide.
|
|
86847
|
+
*/
|
|
86848
|
+
const backupFileName = (input) => `${input.fileName}.${input.atIso.replace(/[:.]/gu, "-")}.original`;
|
|
86849
|
+
//#endregion
|
|
86002
86850
|
//#region src/mcp/toolkits/threads/tools.ts
|
|
86003
86851
|
/**
|
|
86004
|
-
*
|
|
86005
|
-
*
|
|
86006
|
-
*
|
|
86007
|
-
*
|
|
86008
|
-
*
|
|
86852
|
+
* Scoped to what this session started, on purpose.
|
|
86853
|
+
*
|
|
86854
|
+
* A control tool may name a thread, but only one this session created with
|
|
86855
|
+
* `thread_spawn`; omitting the id means the session's own thread, which is the
|
|
86856
|
+
* common case and the one that cannot be got wrong. The grant lives on the
|
|
86857
|
+
* credential in `McpSessionRegistry`, so a leaked per-session token still
|
|
86858
|
+
* reaches only the work that token itself began, and its reach cannot widen
|
|
86859
|
+
* because something changed on the board.
|
|
86860
|
+
*
|
|
86861
|
+
* Delete and archive stay unexposed. Deletion is the one irreversible action in
|
|
86862
|
+
* this file and a thread carries its whole transcript, which is a worse thing
|
|
86863
|
+
* to lose than the task row the task toolkit withheld delete for.
|
|
86864
|
+
*
|
|
86865
|
+
* `thread_spawn` goes one level: a thread that was itself spawned is issued a
|
|
86866
|
+
* credential that cannot spawn, so a loop meets a bound rather than a request
|
|
86867
|
+
* not to loop. `thread_settle` on a live session is queued and applied when the
|
|
86868
|
+
* session stops, because the decider refuses an immediate settle there and a
|
|
86869
|
+
* call from a thread's own session always arrives while it is alive.
|
|
86870
|
+
*
|
|
86871
|
+
* @module mcp/toolkits/threads/tools
|
|
86009
86872
|
*/
|
|
86873
|
+
/** Self-only, and staying that way: `thread_configure` is where a title on another thread is set. */
|
|
86010
86874
|
const ThreadRenameTool = Tool.make("thread_rename", {
|
|
86011
86875
|
description: "Rename this agent session's own thread. Use it when the seeded title no longer matches what the thread turned out to be about. Only the current thread can be renamed; no other thread is reachable.",
|
|
86012
86876
|
parameters: ThreadRenameInput,
|
|
@@ -86018,6 +86882,54 @@ const ThreadRenameTool = Tool.make("thread_rename", {
|
|
|
86018
86882
|
Crypto.Crypto
|
|
86019
86883
|
]
|
|
86020
86884
|
}).annotate(Tool.Title, "Rename this thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86885
|
+
const ThreadSpawnTool = Tool.make("thread_spawn", {
|
|
86886
|
+
description: "Start a new agent thread and send it a first message, then return its id. Use it to hand a piece of work to a fresh thread - a subtask you just planned, a job that wants its own transcript. The new thread inherits this one's project, model and permission mode unless you say otherwise, and can then be watched with thread_watch_events and adjusted with thread_configure. A thread that was itself started this way cannot start another.",
|
|
86887
|
+
parameters: ThreadSpawnInput,
|
|
86888
|
+
success: ThreadSpawnResult,
|
|
86889
|
+
failure: ThreadControlToolError,
|
|
86890
|
+
dependencies: [
|
|
86891
|
+
McpInvocationContext,
|
|
86892
|
+
McpSessionRegistry,
|
|
86893
|
+
OrchestrationEngineService,
|
|
86894
|
+
ProjectionThreadRepository,
|
|
86895
|
+
Crypto.Crypto
|
|
86896
|
+
]
|
|
86897
|
+
}).annotate(Tool.Title, "Start a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
86898
|
+
const ThreadConfigureTool = Tool.make("thread_configure", {
|
|
86899
|
+
description: "Change a thread's settings: title, model, permission mode, interaction mode, compression, and whether it may use subagents without asking. Omit threadId to configure this session's own thread; name one only if this session started it. Every field is optional and an omitted field is left alone; the settings actually changed come back in the result.",
|
|
86900
|
+
parameters: ThreadConfigureInput,
|
|
86901
|
+
success: ThreadConfigureResult,
|
|
86902
|
+
failure: ThreadControlToolError,
|
|
86903
|
+
dependencies: [
|
|
86904
|
+
McpInvocationContext,
|
|
86905
|
+
OrchestrationEngineService,
|
|
86906
|
+
Crypto.Crypto
|
|
86907
|
+
]
|
|
86908
|
+
}).annotate(Tool.Title, "Configure a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86909
|
+
const ThreadSettleTool = Tool.make("thread_settle", {
|
|
86910
|
+
description: "Settle a thread, marking its work finished so it drops out of the active list. Omit threadId for this session's own thread; name one only if this session started it. A thread whose session is still running cannot be settled on the spot, so the request is recorded and applied when that session stops - the result says which of the two happened.",
|
|
86911
|
+
parameters: ThreadSettleInput,
|
|
86912
|
+
success: ThreadSettleResult,
|
|
86913
|
+
failure: ThreadControlToolError,
|
|
86914
|
+
dependencies: [
|
|
86915
|
+
McpInvocationContext,
|
|
86916
|
+
OrchestrationEngineService,
|
|
86917
|
+
ProjectionThreadRepository,
|
|
86918
|
+
ProjectionThreadSessionRepository,
|
|
86919
|
+
Crypto.Crypto
|
|
86920
|
+
]
|
|
86921
|
+
}).annotate(Tool.Title, "Settle a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86922
|
+
const ThreadSnoozeTool = Tool.make("thread_snooze", {
|
|
86923
|
+
description: "Snooze a thread until a given time, hiding it from the active list until then. Omit threadId for this session's own thread; name one only if this session started it.",
|
|
86924
|
+
parameters: ThreadSnoozeInput,
|
|
86925
|
+
success: ThreadSnoozeResult,
|
|
86926
|
+
failure: ThreadControlToolError,
|
|
86927
|
+
dependencies: [
|
|
86928
|
+
McpInvocationContext,
|
|
86929
|
+
OrchestrationEngineService,
|
|
86930
|
+
Crypto.Crypto
|
|
86931
|
+
]
|
|
86932
|
+
}).annotate(Tool.Title, "Snooze a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86021
86933
|
const MemoryAppendTool = Tool.make("memory_append", {
|
|
86022
86934
|
description: "Append a block of markdown to the user-scope memory file (CLAUDE.md by default, AGENTS.md on request). Use it to record a durable preference or fact the user asked to remember. Appends only - it never rewrites or removes existing memory.",
|
|
86023
86935
|
parameters: MemoryAppendInput,
|
|
@@ -86030,11 +86942,221 @@ const MemoryAppendTool = Tool.make("memory_append", {
|
|
|
86030
86942
|
Path.Path
|
|
86031
86943
|
]
|
|
86032
86944
|
}).annotate(Tool.Title, "Append to memory").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
86033
|
-
|
|
86945
|
+
/**
|
|
86946
|
+
* Overwrite an asset with a compressed version of itself, if it survives the
|
|
86947
|
+
* comparison.
|
|
86948
|
+
*
|
|
86949
|
+
* A tool rather than an instruction to use `Edit` because the guarantee is the
|
|
86950
|
+
* point: the original is copied out of every synced directory first, the
|
|
86951
|
+
* rewrite is compared against it, and a rewrite that lost a URL or a code block
|
|
86952
|
+
* is refused with the list of what it lost. An agent editing the file directly
|
|
86953
|
+
* can be asked to preserve those things but cannot be prevented from not.
|
|
86954
|
+
*/
|
|
86955
|
+
const AssetCompressTool = Tool.make("asset_compress", {
|
|
86956
|
+
description: "Overwrite one agent asset file - a skill, a memory file, a subagent definition - with a compressed rewrite of it, after checking the rewrite kept everything that carries meaning. Headings, fenced code blocks, inline code, URLs, paths and YAML frontmatter must come back unchanged; only the prose may be rewritten. The original is backed up outside every synced directory first. If the check fails nothing is written and the failed checks are returned, so a corrected rewrite can be sent as a second call.",
|
|
86957
|
+
parameters: AssetCompressInput,
|
|
86958
|
+
success: AssetCompressResult,
|
|
86959
|
+
failure: ThreadControlToolError,
|
|
86960
|
+
dependencies: [
|
|
86961
|
+
McpInvocationContext,
|
|
86962
|
+
ServerConfig$1,
|
|
86963
|
+
ServerSettingsService,
|
|
86964
|
+
FileSystem.FileSystem,
|
|
86965
|
+
Path.Path
|
|
86966
|
+
]
|
|
86967
|
+
}).annotate(Tool.Title, "Compress an asset").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.Idempotent, false);
|
|
86968
|
+
const ThreadToolkit = Toolkit.make(ThreadSpawnTool, ThreadConfigureTool, ThreadSettleTool, ThreadSnoozeTool, ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
|
|
86034
86969
|
//#endregion
|
|
86035
86970
|
//#region src/mcp/toolkits/threads/handlers.ts
|
|
86036
86971
|
const DEFAULT_MEMORY_APPEND_TARGET = "CLAUDE.md";
|
|
86972
|
+
/**
|
|
86973
|
+
* Where originals are kept.
|
|
86974
|
+
*
|
|
86975
|
+
* Under the server's state directory, which is the one place on this machine
|
|
86976
|
+
* that is neither an asset root nor inside one. A backup next to the file would
|
|
86977
|
+
* be published by the next sync and re-read by the next agent turn, which is
|
|
86978
|
+
* the opposite of what compressing the file was for.
|
|
86979
|
+
*/
|
|
86980
|
+
const COMPRESS_BACKUP_DIRNAME = "asset-compress-backups";
|
|
86981
|
+
/** Prose only. A skill may ship scripts, and a rewritten script is a broken one. */
|
|
86982
|
+
const COMPRESSIBLE_EXTENSION = ".md";
|
|
86983
|
+
/** The same test the asset readers use: a NUL is what breaks a JSON text column. */
|
|
86984
|
+
const looksBinary = (content) => content.includes("\0");
|
|
86985
|
+
/**
|
|
86986
|
+
* Every control command shares this: mint an id, dispatch, and turn a decider
|
|
86987
|
+
* refusal into one error the agent can read.
|
|
86988
|
+
*/
|
|
86989
|
+
const dispatchControl = Effect.fn("mcp.threads.dispatchControl")(function* (command, threadId) {
|
|
86990
|
+
yield* (yield* OrchestrationEngineService).dispatch(command).pipe(Effect.mapError((cause) => new ThreadControlRejectedError({
|
|
86991
|
+
threadId,
|
|
86992
|
+
commandType: command.type,
|
|
86993
|
+
detail: cause.message
|
|
86994
|
+
})));
|
|
86995
|
+
});
|
|
86996
|
+
const newCommandId = Effect.gen(function* () {
|
|
86997
|
+
const crypto = yield* Crypto.Crypto;
|
|
86998
|
+
return CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
86999
|
+
});
|
|
86037
87000
|
const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
87001
|
+
thread_spawn: (input) => Effect.gen(function* () {
|
|
87002
|
+
const invocation = yield* requireThreadSpawn();
|
|
87003
|
+
const registry = yield* McpSessionRegistry;
|
|
87004
|
+
const threads = yield* ProjectionThreadRepository;
|
|
87005
|
+
const crypto = yield* Crypto.Crypto;
|
|
87006
|
+
const parent = yield* threads.getById({ threadId: invocation.threadId }).pipe(Effect.mapError((cause) => new ThreadSpawnNotPermittedError({
|
|
87007
|
+
threadId: invocation.threadId,
|
|
87008
|
+
detail: `this thread could not be read to take its defaults from: ${cause.message}`
|
|
87009
|
+
})));
|
|
87010
|
+
if (Option.isNone(parent)) return yield* new ThreadSpawnNotPermittedError({
|
|
87011
|
+
threadId: invocation.threadId,
|
|
87012
|
+
detail: "this thread is not on the board, so there is nothing to start a thread beside"
|
|
87013
|
+
});
|
|
87014
|
+
const template = parent.value;
|
|
87015
|
+
const projectId = input.projectId ?? template.projectId;
|
|
87016
|
+
const runtimeMode = input.runtimeMode ?? template.runtimeMode;
|
|
87017
|
+
const interactionMode = input.interactionMode ?? template.interactionMode;
|
|
87018
|
+
const compressMode = input.compressMode ?? template.compressMode;
|
|
87019
|
+
const unpromptedSubagents = input.unpromptedSubagents ?? template.unpromptedSubagents === 1;
|
|
87020
|
+
const threadId = ThreadId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
87021
|
+
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
87022
|
+
yield* dispatchControl({
|
|
87023
|
+
type: "thread.create",
|
|
87024
|
+
commandId: yield* newCommandId,
|
|
87025
|
+
threadId,
|
|
87026
|
+
projectId,
|
|
87027
|
+
title: input.title,
|
|
87028
|
+
modelSelection: template.modelSelection,
|
|
87029
|
+
runtimeMode,
|
|
87030
|
+
interactionMode,
|
|
87031
|
+
compressMode,
|
|
87032
|
+
unpromptedSubagents,
|
|
87033
|
+
branch: null,
|
|
87034
|
+
worktreePath: null,
|
|
87035
|
+
createdAt
|
|
87036
|
+
}, threadId);
|
|
87037
|
+
yield* registry.recordSpawnedThread({
|
|
87038
|
+
providerSessionId: invocation.providerSessionId,
|
|
87039
|
+
threadId
|
|
87040
|
+
});
|
|
87041
|
+
yield* dispatchControl({
|
|
87042
|
+
type: "thread.turn.start",
|
|
87043
|
+
commandId: yield* newCommandId,
|
|
87044
|
+
threadId,
|
|
87045
|
+
message: {
|
|
87046
|
+
messageId: MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
|
|
87047
|
+
role: "user",
|
|
87048
|
+
text: input.prompt,
|
|
87049
|
+
attachments: []
|
|
87050
|
+
},
|
|
87051
|
+
modelSelection: template.modelSelection,
|
|
87052
|
+
titleSeed: input.title,
|
|
87053
|
+
runtimeMode,
|
|
87054
|
+
interactionMode,
|
|
87055
|
+
compressMode,
|
|
87056
|
+
unpromptedSubagents,
|
|
87057
|
+
createdAt
|
|
87058
|
+
}, threadId);
|
|
87059
|
+
return {
|
|
87060
|
+
threadId,
|
|
87061
|
+
projectId,
|
|
87062
|
+
title: input.title
|
|
87063
|
+
};
|
|
87064
|
+
}),
|
|
87065
|
+
thread_configure: (input) => Effect.gen(function* () {
|
|
87066
|
+
const { threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
87067
|
+
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
87068
|
+
const applied = [];
|
|
87069
|
+
if (input.title !== void 0 || input.modelSelection !== void 0) {
|
|
87070
|
+
yield* dispatchControl({
|
|
87071
|
+
type: "thread.meta.update",
|
|
87072
|
+
commandId: yield* newCommandId,
|
|
87073
|
+
threadId,
|
|
87074
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
87075
|
+
...input.modelSelection !== void 0 ? { modelSelection: input.modelSelection } : {}
|
|
87076
|
+
}, threadId);
|
|
87077
|
+
if (input.title !== void 0) applied.push("title");
|
|
87078
|
+
if (input.modelSelection !== void 0) applied.push("modelSelection");
|
|
87079
|
+
}
|
|
87080
|
+
if (input.runtimeMode !== void 0) {
|
|
87081
|
+
yield* dispatchControl({
|
|
87082
|
+
type: "thread.runtime-mode.set",
|
|
87083
|
+
commandId: yield* newCommandId,
|
|
87084
|
+
threadId,
|
|
87085
|
+
runtimeMode: input.runtimeMode,
|
|
87086
|
+
createdAt
|
|
87087
|
+
}, threadId);
|
|
87088
|
+
applied.push("runtimeMode");
|
|
87089
|
+
}
|
|
87090
|
+
if (input.interactionMode !== void 0) {
|
|
87091
|
+
yield* dispatchControl({
|
|
87092
|
+
type: "thread.interaction-mode.set",
|
|
87093
|
+
commandId: yield* newCommandId,
|
|
87094
|
+
threadId,
|
|
87095
|
+
interactionMode: input.interactionMode,
|
|
87096
|
+
createdAt
|
|
87097
|
+
}, threadId);
|
|
87098
|
+
applied.push("interactionMode");
|
|
87099
|
+
}
|
|
87100
|
+
if (input.compressMode !== void 0) {
|
|
87101
|
+
yield* dispatchControl({
|
|
87102
|
+
type: "thread.compress-mode.set",
|
|
87103
|
+
commandId: yield* newCommandId,
|
|
87104
|
+
threadId,
|
|
87105
|
+
compressMode: input.compressMode,
|
|
87106
|
+
createdAt
|
|
87107
|
+
}, threadId);
|
|
87108
|
+
applied.push("compressMode");
|
|
87109
|
+
}
|
|
87110
|
+
if (input.unpromptedSubagents !== void 0) {
|
|
87111
|
+
yield* dispatchControl({
|
|
87112
|
+
type: "thread.unprompted-subagents.set",
|
|
87113
|
+
commandId: yield* newCommandId,
|
|
87114
|
+
threadId,
|
|
87115
|
+
unpromptedSubagents: input.unpromptedSubagents,
|
|
87116
|
+
createdAt
|
|
87117
|
+
}, threadId);
|
|
87118
|
+
applied.push("unpromptedSubagents");
|
|
87119
|
+
}
|
|
87120
|
+
return {
|
|
87121
|
+
threadId,
|
|
87122
|
+
applied
|
|
87123
|
+
};
|
|
87124
|
+
}),
|
|
87125
|
+
thread_settle: (input) => Effect.gen(function* () {
|
|
87126
|
+
const { threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
87127
|
+
const threads = yield* ProjectionThreadRepository;
|
|
87128
|
+
const session = yield* (yield* ProjectionThreadSessionRepository).getByThreadId({ threadId }).pipe(Effect.catchCause(() => Effect.succeed(Option.none())));
|
|
87129
|
+
if (!(Option.isSome(session) && (session.value.status === "starting" || session.value.status === "running"))) {
|
|
87130
|
+
yield* dispatchControl({
|
|
87131
|
+
type: "thread.settle",
|
|
87132
|
+
commandId: yield* newCommandId,
|
|
87133
|
+
threadId
|
|
87134
|
+
}, threadId);
|
|
87135
|
+
return {
|
|
87136
|
+
threadId,
|
|
87137
|
+
outcome: "settled"
|
|
87138
|
+
};
|
|
87139
|
+
}
|
|
87140
|
+
queueThreadSettle(threadId);
|
|
87141
|
+
yield* threads.getById({ threadId }).pipe(Effect.orDie);
|
|
87142
|
+
return {
|
|
87143
|
+
threadId,
|
|
87144
|
+
outcome: "queued"
|
|
87145
|
+
};
|
|
87146
|
+
}),
|
|
87147
|
+
thread_snooze: (input) => Effect.gen(function* () {
|
|
87148
|
+
const { threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
87149
|
+
yield* dispatchControl({
|
|
87150
|
+
type: "thread.snooze",
|
|
87151
|
+
commandId: yield* newCommandId,
|
|
87152
|
+
threadId,
|
|
87153
|
+
snoozedUntil: input.snoozedUntil
|
|
87154
|
+
}, threadId);
|
|
87155
|
+
return {
|
|
87156
|
+
threadId,
|
|
87157
|
+
snoozedUntil: input.snoozedUntil
|
|
87158
|
+
};
|
|
87159
|
+
}),
|
|
86038
87160
|
thread_rename: (input) => Effect.gen(function* () {
|
|
86039
87161
|
const invocation = yield* requireThreadCapability();
|
|
86040
87162
|
const engine = yield* OrchestrationEngineService;
|
|
@@ -86083,6 +87205,68 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
86083
87205
|
file,
|
|
86084
87206
|
path: filePath
|
|
86085
87207
|
};
|
|
87208
|
+
}),
|
|
87209
|
+
asset_compress: (input) => Effect.gen(function* () {
|
|
87210
|
+
yield* requireThreadCapability();
|
|
87211
|
+
const path = yield* Path.Path;
|
|
87212
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
87213
|
+
const config = yield* ServerConfig$1;
|
|
87214
|
+
const claudeHome = { homePath: (yield* (yield* ServerSettingsService).getSettings.pipe(Effect.orElseSucceed(() => void 0)))?.providers.claudeAgent.homePath ?? "" };
|
|
87215
|
+
const refuse = (target, detail) => new AssetCompressRefusedError({
|
|
87216
|
+
path: target,
|
|
87217
|
+
detail
|
|
87218
|
+
});
|
|
87219
|
+
if (!path.isAbsolute(input.path)) return yield* refuse(input.path, "The path must be absolute.");
|
|
87220
|
+
const filePath = path.resolve(input.path);
|
|
87221
|
+
const fileName = path.basename(filePath);
|
|
87222
|
+
if (path.extname(fileName).toLowerCase() !== COMPRESSIBLE_EXTENSION) return yield* refuse(filePath, `Only ${COMPRESSIBLE_EXTENSION} files are compressed. Scripts and data files an asset ships alongside its prose are left alone.`);
|
|
87223
|
+
if (isSensitiveAssetPath(filePath)) return yield* refuse(filePath, "The name looks like credentials, keys or secrets, and compressing sends the file's contents to a model.");
|
|
87224
|
+
const configDir = yield* resolveClaudeConfigDirPath(claudeHome, process.env);
|
|
87225
|
+
const skillsDir = yield* resolveClaudeUserSkillsDir(claudeHome);
|
|
87226
|
+
const agentsDir = yield* resolveClaudeUserAgentsDir(claudeHome);
|
|
87227
|
+
const within = (root) => {
|
|
87228
|
+
const relative = path.relative(root, filePath);
|
|
87229
|
+
return relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
87230
|
+
};
|
|
87231
|
+
const memoryDirectoryName = path.relative(configDir, filePath).split(path.sep)[0] ?? "";
|
|
87232
|
+
if (!(within(skillsDir) || within(agentsDir) || path.dirname(filePath) === configDir && isMemoryFileName(fileName) || within(configDir) && isMemoryDirectoryName(memoryDirectoryName))) return yield* refuse(filePath, `Not an agent asset. Compressible files live under '${skillsDir}', under '${agentsDir}', or in '${configDir}' as a memory file or inside ${MEMORY_DIRECTORY_NAMES.join(" or ")}.`);
|
|
87233
|
+
const original = yield* fileSystem.readFileString(filePath).pipe(Effect.orElseSucceed(() => void 0));
|
|
87234
|
+
if (original === void 0) return yield* refuse(filePath, "The file could not be read.");
|
|
87235
|
+
if (looksBinary(original)) return yield* refuse(filePath, "The file is not text.");
|
|
87236
|
+
if (original.trim().length === 0) return yield* refuse(filePath, "The file is empty.");
|
|
87237
|
+
const compressed = input.content;
|
|
87238
|
+
if (compressed.trim().length === 0) return yield* refuse(filePath, "The rewrite is blank.");
|
|
87239
|
+
if (compressed.trim() === original.trim()) return yield* refuse(filePath, "The rewrite is the file that is already there. Nothing was written.");
|
|
87240
|
+
if (compressed.length >= original.length) return yield* refuse(filePath, `The rewrite is not smaller: ${original.length} characters became ${compressed.length}. Send the file's contents alone, with no commentary or wrapping fence.`);
|
|
87241
|
+
const validation = validateCompression(original, compressed);
|
|
87242
|
+
if (validation.errors.length > 0) return yield* new AssetCompressValidationFailedError({
|
|
87243
|
+
path: filePath,
|
|
87244
|
+
errors: validation.errors
|
|
87245
|
+
});
|
|
87246
|
+
const backupPath = path.join(config.stateDir, COMPRESS_BACKUP_DIRNAME, path.basename(path.dirname(filePath)), backupFileName({
|
|
87247
|
+
fileName,
|
|
87248
|
+
atIso: DateTime.formatIso(yield* DateTime.now)
|
|
87249
|
+
}));
|
|
87250
|
+
if (((yield* writeFileStringAtomically({
|
|
87251
|
+
filePath: backupPath,
|
|
87252
|
+
contents: original
|
|
87253
|
+
}).pipe(Effect.as(true), Effect.orElseSucceed(() => false))) ? yield* fileSystem.readFileString(backupPath).pipe(Effect.orElseSucceed(() => void 0)) : void 0) !== original) {
|
|
87254
|
+
yield* fileSystem.remove(backupPath, { force: true }).pipe(Effect.ignore);
|
|
87255
|
+
return yield* refuse(filePath, `The original could not be backed up to '${backupPath}', so it was left as it is.`);
|
|
87256
|
+
}
|
|
87257
|
+
yield* writeFileStringAtomically({
|
|
87258
|
+
filePath,
|
|
87259
|
+
contents: compressed
|
|
87260
|
+
}).pipe(Effect.mapError((cause) => refuse(filePath, `The write failed: ${cause.message}. The original is at '${backupPath}'.`)));
|
|
87261
|
+
return {
|
|
87262
|
+
path: filePath,
|
|
87263
|
+
backupPath,
|
|
87264
|
+
charsBefore: original.length,
|
|
87265
|
+
charsAfter: compressed.length,
|
|
87266
|
+
estimatedTokensBefore: estimateTokens(original),
|
|
87267
|
+
estimatedTokensAfter: estimateTokens(compressed),
|
|
87268
|
+
warnings: validation.warnings
|
|
87269
|
+
};
|
|
86086
87270
|
})
|
|
86087
87271
|
});
|
|
86088
87272
|
//#endregion
|
|
@@ -87533,13 +88717,34 @@ const make$2 = Effect.gen(function* () {
|
|
|
87533
88717
|
if (providerError) return providerError.detail;
|
|
87534
88718
|
return Cause.pretty(cause);
|
|
87535
88719
|
};
|
|
88720
|
+
/**
|
|
88721
|
+
* Applies a settle an agent asked for while its session was still running.
|
|
88722
|
+
*
|
|
88723
|
+
* The decider refuses `thread.settle` for a live session, so `thread_settle`
|
|
88724
|
+
* over MCP records the intent instead of failing. This is the moment it
|
|
88725
|
+
* becomes legal: the session has just been written as stopped, so the same
|
|
88726
|
+
* command the agent could not send now succeeds. Taken from the queue rather
|
|
88727
|
+
* than read, so a second stop cannot settle the thread twice.
|
|
88728
|
+
*/
|
|
88729
|
+
const flushQueuedSettle = Effect.fnUntraced(function* (threadId) {
|
|
88730
|
+
if (!takeQueuedThreadSettle(threadId)) return;
|
|
88731
|
+
const commandId = yield* serverCommandId("queued-thread-settle");
|
|
88732
|
+
yield* orchestrationEngine.dispatch({
|
|
88733
|
+
type: "thread.settle",
|
|
88734
|
+
commandId,
|
|
88735
|
+
threadId
|
|
88736
|
+
}).pipe(Effect.catchCause((cause) => Effect.logDebug("queued thread settle did not apply", {
|
|
88737
|
+
threadId,
|
|
88738
|
+
cause
|
|
88739
|
+
})));
|
|
88740
|
+
});
|
|
87536
88741
|
const setThreadSession = (input) => serverCommandId("provider-session-set").pipe(Effect.flatMap((commandId) => orchestrationEngine.dispatch({
|
|
87537
88742
|
type: "thread.session.set",
|
|
87538
88743
|
commandId,
|
|
87539
88744
|
threadId: input.threadId,
|
|
87540
88745
|
session: input.session,
|
|
87541
88746
|
createdAt: input.createdAt
|
|
87542
|
-
})));
|
|
88747
|
+
})), Effect.tap(() => input.session.status === "stopped" || input.session.status === "error" ? flushQueuedSettle(input.threadId) : Effect.void));
|
|
87543
88748
|
const setThreadSessionErrorOnTurnStartFailure = Effect.fnUntraced(function* (input) {
|
|
87544
88749
|
const thread = yield* resolveThread(input.threadId);
|
|
87545
88750
|
if (!thread) return;
|
|
@@ -87666,7 +88871,8 @@ const make$2 = Effect.gen(function* () {
|
|
|
87666
88871
|
modelSelection: desiredModelSelection,
|
|
87667
88872
|
...input?.resumeCursor !== void 0 ? { resumeCursor: input.resumeCursor } : {},
|
|
87668
88873
|
runtimeMode: desiredRuntimeMode,
|
|
87669
|
-
compressMode: thread.compressMode
|
|
88874
|
+
compressMode: thread.compressMode,
|
|
88875
|
+
unpromptedSubagents: thread.unpromptedSubagents
|
|
87670
88876
|
});
|
|
87671
88877
|
const bindSessionToThread = (session) => Effect.gen(function* () {
|
|
87672
88878
|
if (session.providerInstanceId === void 0) return yield* new ProviderAdapterRequestError({
|
|
@@ -87768,7 +88974,8 @@ const make$2 = Effect.gen(function* () {
|
|
|
87768
88974
|
...normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {},
|
|
87769
88975
|
...modelForTurn !== void 0 ? { modelSelection: modelForTurn } : {},
|
|
87770
88976
|
...input.interactionMode !== void 0 ? { interactionMode: input.interactionMode } : {},
|
|
87771
|
-
compressMode
|
|
88977
|
+
compressMode,
|
|
88978
|
+
unpromptedSubagents: thread.unpromptedSubagents
|
|
87772
88979
|
};
|
|
87773
88980
|
});
|
|
87774
88981
|
const maybeGenerateAndRenameWorktreeBranchForFirstTurn = Effect.fn("maybeGenerateAndRenameWorktreeBranchForFirstTurn")(function* (input) {
|