@p4code/cli 0.1.19 → 0.1.20
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 +703 -20
- package/dist/client/assets/{DiffPanel-CseUmWvE.js → DiffPanel-Di8WylWU.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-Dm7l5FFh.js → FilePreviewPanel-CSqqsbYX.js} +2 -2
- package/dist/client/assets/{PreviewPanel-DOyykNL9.js → PreviewPanel-BcRUQSMy.js} +2 -2
- package/dist/client/assets/{angular-html-YobKPLun.js → angular-html-CPSNyy71.js} +2 -2
- package/dist/client/assets/{angular-ts-Ac3I6TFV.js → angular-ts-BgndqOdx.js} +2 -2
- package/dist/client/assets/{apl-Dwl73GTS.js → apl-CFWK0bta.js} +2 -2
- package/dist/client/assets/arrow-right-wbRntSmq.js +2 -0
- package/dist/client/assets/{astro-KBgldnxZ.js → astro-D2uzatlp.js} +2 -2
- package/dist/client/assets/{blade-DTU2NGhh.js → blade-DfrK_8z3.js} +2 -2
- package/dist/client/assets/{c-COF4kWCp.js → c-DyORVYNJ.js} +2 -2
- package/dist/client/assets/{cobol-BA7JsdNr.js → cobol-BUWePdBc.js} +2 -2
- package/dist/client/assets/{coffee-PFN_w4qL.js → coffee-Ba8BYHli.js} +2 -2
- package/dist/client/assets/{cpp-CpT6w-g5.js → cpp-BX0iAGNA.js} +2 -2
- package/dist/client/assets/{crystal-BrlaRT0B.js → crystal-BDSdceoQ.js} +2 -2
- package/dist/client/assets/{css-C2LMO3MX.js → css-TKd0Sp86.js} +2 -2
- package/dist/client/assets/{edge-DBkQ-Ajd.js → edge-Bus7Aab8.js} +2 -2
- package/dist/client/assets/{elixir-EbSNDEx0.js → elixir-BlT2g4EX.js} +2 -2
- package/dist/client/assets/{elm-CSs0Wzy2.js → elm-DTGJZ36e.js} +2 -2
- package/dist/client/assets/{erb-BBmMQG9b.js → erb-BF4yZGxQ.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-CifvTzWP.js → fileCommentAnnotations-C5BlQ5s_.js} +2 -2
- package/dist/client/assets/{git-rebase-C09BpZDw.js → git-rebase-GAe0_ZI9.js} +2 -2
- package/dist/client/assets/{glimmer-js-D4Bqn6Yg.js → glimmer-js-GWmPSTNt.js} +2 -2
- package/dist/client/assets/{glimmer-ts-fLCHL8o6.js → glimmer-ts-CDLqKTZ9.js} +2 -2
- package/dist/client/assets/{glsl-17sV9oGm.js → glsl-Bv3E3r0O.js} +2 -2
- package/dist/client/assets/{graphql-BqFquulN.js → graphql-B5-LupiK.js} +2 -2
- package/dist/client/assets/{hack-D6Jpv-j9.js → hack-KTQ3DPKm.js} +2 -2
- package/dist/client/assets/{haml-D0eo8PEJ.js → haml-BvC-52XY.js} +2 -2
- package/dist/client/assets/{handlebars-HG0XNkEg.js → handlebars-CK9WCq20.js} +2 -2
- package/dist/client/assets/{html-Bh3padJ5.js → html-BGjxg0hO.js} +2 -2
- package/dist/client/assets/{html-derivative-DVHw4MNX.js → html-derivative-C0EaHUuc.js} +2 -2
- package/dist/client/assets/{http-CYCXaOA5.js → http-hvxfwUee.js} +2 -2
- package/dist/client/assets/{hurl-D4oK46oh.js → hurl-D5cuiBPB.js} +2 -2
- package/dist/client/assets/index-QC2FhlVn.js +836 -0
- package/dist/client/assets/{java-nvFYZOCN.js → java-DOkofowB.js} +2 -2
- package/dist/client/assets/{javascript-CMBxjf-M.js → javascript-dCesbfJ7.js} +2 -2
- package/dist/client/assets/{jinja-vsPdkFFj.js → jinja-BDrVqT_2.js} +2 -2
- package/dist/client/assets/{jison-uky2pPNE.js → jison-Dvzh0XRq.js} +2 -2
- package/dist/client/assets/{json-DOSqa2F1.js → json-CpV_dVb2.js} +2 -2
- package/dist/client/assets/{jsx-Ci7CVNQe.js → jsx-CbaKC498.js} +2 -2
- package/dist/client/assets/{julia-J2m7N3_R.js → julia-BKYdI_5W.js} +2 -2
- package/dist/client/assets/{just-BDy3oM_N.js → just-FrVMEGvg.js} +2 -2
- package/dist/client/assets/{latex-CMD49F7D.js → latex-BSmwt_7j.js} +2 -2
- package/dist/client/assets/{liquid-Dq6k2YW6.js → liquid-Bb2nFp-U.js} +2 -2
- package/dist/client/assets/{lua-CohtP6J8.js → lua-CEANnQ2c.js} +2 -2
- package/dist/client/assets/{marko-C4V2zw1t.js → marko-CjJZ1eHg.js} +2 -2
- package/dist/client/assets/{mdc-q1ZKjwYt.js → mdc-B3VSAYVT.js} +2 -2
- package/dist/client/assets/{nginx-D2TdEIZ3.js → nginx-BkYJUm4E.js} +2 -2
- package/dist/client/assets/{nim-BtWKpYQs.js → nim-D8xT6_xt.js} +2 -2
- package/dist/client/assets/{perl-Ci4Pso9h.js → perl-cazo7YHC.js} +2 -2
- package/dist/client/assets/{php-CMzpcAju.js → php-C0LwuOo3.js} +2 -2
- package/dist/client/assets/{pug-D0sbFhP6.js → pug-OP6SwNFw.js} +2 -2
- package/dist/client/assets/{qml-BfWXHzNc.js → qml-C1-WQIQ-.js} +2 -2
- package/dist/client/assets/{r-BN4rQDnz.js → r-PkhwsIgx.js} +2 -2
- package/dist/client/assets/{razor-D5iw68yr.js → razor-WONkd3aR.js} +2 -2
- package/dist/client/assets/{regexp-Ba0WieCb.js → regexp-SDnpa4md.js} +2 -2
- package/dist/client/assets/{rst-DQqlRfnq.js → rst-DulobrTh.js} +2 -2
- package/dist/client/assets/{ruby-CReh9Gta.js → ruby-RYHm8H3x.js} +2 -2
- package/dist/client/assets/{sas-CJ-Cpd8L.js → sas-BBwb67VZ.js} +2 -2
- package/dist/client/assets/{scss-CZp-cH6d.js → scss-y2IZtjKJ.js} +2 -2
- package/dist/client/assets/{shellscript-hCSuhhJZ.js → shellscript-B3vxtpJZ.js} +2 -2
- package/dist/client/assets/{shellsession-BnDNcCDQ.js → shellsession-CvHfy0TS.js} +2 -2
- package/dist/client/assets/{soy-BXarcWJY.js → soy-DWpn3a9x.js} +2 -2
- package/dist/client/assets/{sql-ADQCXhYd.js → sql-CCfTsIvj.js} +2 -2
- package/dist/client/assets/{stata-DSS4IpqF.js → stata-Dr5CN-Tp.js} +2 -2
- package/dist/client/assets/{surrealql-DuYcftNm.js → surrealql-DgzXJo35.js} +2 -2
- package/dist/client/assets/{svelte-CAXOFInl.js → svelte-JQb5PCjP.js} +2 -2
- package/dist/client/assets/{templ-BvTIEIah.js → templ-DQt5AsQ7.js} +2 -2
- package/dist/client/assets/{tex-DpnVRTMM.js → tex-B8Qokwwv.js} +2 -2
- package/dist/client/assets/textarea-BlxAoGJe.js +46 -0
- package/dist/client/assets/{ts-tags-4wlHlovT.js → ts-tags-B-RrSBzw.js} +2 -2
- package/dist/client/assets/{tsx-CHDgELdy.js → tsx-C7Q0nn_B.js} +2 -2
- package/dist/client/assets/{twig-DPmx2UHF.js → twig-CzVGXsz7.js} +2 -2
- package/dist/client/assets/{typescript-CHO8bW-H.js → typescript-irwEvPgw.js} +2 -2
- package/dist/client/assets/{vue-CM19I03n.js → vue-BE-nMa-3.js} +2 -2
- package/dist/client/assets/{vue-html-ginfkxGQ.js → vue-html-SXHQOmAo.js} +2 -2
- package/dist/client/assets/{vue-vine-CGdZxUwf.js → vue-vine-D62pnm9I.js} +2 -2
- package/dist/client/assets/{xml-B8CY16OL.js → xml-BnssPo1i.js} +2 -2
- package/dist/client/assets/{xsl-COHh7n8v.js → xsl-Dg7cN1LO.js} +2 -2
- package/dist/client/assets/{yaml-DXx-djat.js → yaml-BAeQKrXU.js} +2 -2
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/dist/client/assets/arrow-right-BqDszij1.js +0 -2
- package/dist/client/assets/index-CV_dK79d.js +0 -783
- 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.20";
|
|
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),
|
|
@@ -7799,6 +7905,11 @@ var TicketResolveError = class extends Schema$1.TaggedErrorClass()("TicketResolv
|
|
|
7799
7905
|
* Quick-append to memory lives here too: it is the other thing an agent wants
|
|
7800
7906
|
* to do *from* a thread, and it shares the toolkit's capability and scope.
|
|
7801
7907
|
*
|
|
7908
|
+
* Compressing an asset joins them for a different reason. It is a write the
|
|
7909
|
+
* agent could perform with its ordinary file tools, and the point of routing it
|
|
7910
|
+
* through a tool instead is that the server gets to check the rewrite before it
|
|
7911
|
+
* lands - and to keep the original somewhere the rewrite cannot reach.
|
|
7912
|
+
*
|
|
7802
7913
|
* @module contracts/threadControl
|
|
7803
7914
|
*/
|
|
7804
7915
|
const ThreadRenameInput = Schema$1.Struct({ title: TrimmedNonEmptyString.annotate({ description: "The new thread title. Replaces the current one." }) });
|
|
@@ -7825,6 +7936,23 @@ const MemoryAppendResult = Schema$1.Struct({
|
|
|
7825
7936
|
/** Absolute path the append landed in, so the agent can cite or inspect it. */
|
|
7826
7937
|
path: Schema$1.String
|
|
7827
7938
|
});
|
|
7939
|
+
const AssetCompressInput = Schema$1.Struct({
|
|
7940
|
+
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." }),
|
|
7941
|
+
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." })
|
|
7942
|
+
});
|
|
7943
|
+
const AssetCompressResult = Schema$1.Struct({
|
|
7944
|
+
/** Absolute path that was rewritten. */
|
|
7945
|
+
path: Schema$1.String,
|
|
7946
|
+
/** Where the pre-compression original was kept, outside every synced directory. */
|
|
7947
|
+
backupPath: Schema$1.String,
|
|
7948
|
+
charsBefore: Schema$1.Number,
|
|
7949
|
+
charsAfter: Schema$1.Number,
|
|
7950
|
+
/** Estimated at four characters per token, not measured by a provider tokenizer. */
|
|
7951
|
+
estimatedTokensBefore: Schema$1.Number,
|
|
7952
|
+
estimatedTokensAfter: Schema$1.Number,
|
|
7953
|
+
/** Differences worth reporting that were not worth refusing the write over. */
|
|
7954
|
+
warnings: Schema$1.Array(Schema$1.String)
|
|
7955
|
+
});
|
|
7828
7956
|
var ThreadToolUnavailableError = class extends Schema$1.TaggedErrorClass()("ThreadToolUnavailableError", {
|
|
7829
7957
|
capability: Schema$1.Literal("threads"),
|
|
7830
7958
|
environmentId: EnvironmentId,
|
|
@@ -7853,10 +7981,43 @@ var MemoryAppendFailedError = class extends Schema$1.TaggedErrorClass()("MemoryA
|
|
|
7853
7981
|
return `Appending to ${this.file} failed: ${this.detail}`;
|
|
7854
7982
|
}
|
|
7855
7983
|
};
|
|
7984
|
+
/**
|
|
7985
|
+
* The compression will not be attempted, or the file could not be read.
|
|
7986
|
+
*
|
|
7987
|
+
* Separate from the validation failure below because they want opposite
|
|
7988
|
+
* responses. A refusal is final - the path is not an asset, the name looks like
|
|
7989
|
+
* a credential, the file is not there - and retrying it produces the same
|
|
7990
|
+
* answer. A validation failure is a rewrite that can be corrected.
|
|
7991
|
+
*/
|
|
7992
|
+
var AssetCompressRefusedError = class extends Schema$1.TaggedErrorClass()("AssetCompressRefusedError", {
|
|
7993
|
+
path: Schema$1.String,
|
|
7994
|
+
detail: Schema$1.String
|
|
7995
|
+
}) {
|
|
7996
|
+
get message() {
|
|
7997
|
+
return `Compressing ${this.path} was refused: ${this.detail}`;
|
|
7998
|
+
}
|
|
7999
|
+
};
|
|
8000
|
+
/**
|
|
8001
|
+
* The rewrite lost something, and nothing was written.
|
|
8002
|
+
*
|
|
8003
|
+
* Carries the checks that failed rather than a summary, because the agent's
|
|
8004
|
+
* next move is to restore exactly those things from the original and call
|
|
8005
|
+
* again. The file on disk is untouched, so a second failure costs nothing more.
|
|
8006
|
+
*/
|
|
8007
|
+
var AssetCompressValidationFailedError = class extends Schema$1.TaggedErrorClass()("AssetCompressValidationFailedError", {
|
|
8008
|
+
path: Schema$1.String,
|
|
8009
|
+
errors: Schema$1.Array(Schema$1.String)
|
|
8010
|
+
}) {
|
|
8011
|
+
get message() {
|
|
8012
|
+
return `The compressed ${this.path} did not validate, so nothing was written: ${this.errors.join(" ")}`;
|
|
8013
|
+
}
|
|
8014
|
+
};
|
|
7856
8015
|
const ThreadControlToolError = Schema$1.Union([
|
|
7857
8016
|
ThreadToolUnavailableError,
|
|
7858
8017
|
ThreadRenameRejectedError,
|
|
7859
|
-
MemoryAppendFailedError
|
|
8018
|
+
MemoryAppendFailedError,
|
|
8019
|
+
AssetCompressRefusedError,
|
|
8020
|
+
AssetCompressValidationFailedError
|
|
7860
8021
|
]);
|
|
7861
8022
|
const ThreadWatchEventsInput = Schema$1.Struct({
|
|
7862
8023
|
threadId: ThreadId.annotate({ description: "The watched thread to read events from. Must be explicitly granted." }),
|
|
@@ -12660,6 +12821,53 @@ var _040_TaskExternalRef_default = Effect.gen(function* () {
|
|
|
12660
12821
|
`;
|
|
12661
12822
|
});
|
|
12662
12823
|
//#endregion
|
|
12824
|
+
//#region src/persistence/Migrations/041_TaskParentTaskId.ts
|
|
12825
|
+
/**
|
|
12826
|
+
* The task a task was split out of, for the plan phase's subtasks.
|
|
12827
|
+
*
|
|
12828
|
+
* A plain column rather than a join table: a task has at most one parent, and
|
|
12829
|
+
* the question asked of it is always "children of this row", which one indexed
|
|
12830
|
+
* column answers.
|
|
12831
|
+
*
|
|
12832
|
+
* No foreign key. Tasks are synced between machines and a child can arrive
|
|
12833
|
+
* before its parent does, or outlive it - a constraint would reject the write
|
|
12834
|
+
* rather than leave a link that resolves later. The board treats a parent it
|
|
12835
|
+
* cannot find as no parent at all.
|
|
12836
|
+
*
|
|
12837
|
+
* The index is non-unique and partial: many children per parent, and the many
|
|
12838
|
+
* rows that are nobody's child stay out of it.
|
|
12839
|
+
*/
|
|
12840
|
+
var _041_TaskParentTaskId_default = Effect.gen(function* () {
|
|
12841
|
+
const sql = yield* SqlClient.SqlClient;
|
|
12842
|
+
yield* sql`ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`;
|
|
12843
|
+
yield* sql`
|
|
12844
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
12845
|
+
ON tasks(parent_task_id)
|
|
12846
|
+
WHERE parent_task_id IS NOT NULL
|
|
12847
|
+
`;
|
|
12848
|
+
});
|
|
12849
|
+
//#endregion
|
|
12850
|
+
//#region src/persistence/Migrations/042_ProjectionThreadsUnpromptedSubagents.ts
|
|
12851
|
+
/**
|
|
12852
|
+
* Whether a thread lets the model spawn subagents without being asked, moved
|
|
12853
|
+
* from a machine-wide setting onto the thread itself.
|
|
12854
|
+
*
|
|
12855
|
+
* Stored as an integer beside `has_actionable_proposed_plan` rather than as a
|
|
12856
|
+
* SQLite boolean, which does not exist; the projection reads it back as
|
|
12857
|
+
* `> 0`.
|
|
12858
|
+
*
|
|
12859
|
+
* The default is `1` — subagents allowed — because that is what every existing
|
|
12860
|
+
* thread has been doing under the old server setting. Anything else would
|
|
12861
|
+
* change the behaviour of live threads on upgrade, which is not a migration's
|
|
12862
|
+
* business.
|
|
12863
|
+
*/
|
|
12864
|
+
var _042_ProjectionThreadsUnpromptedSubagents_default = Effect.gen(function* () {
|
|
12865
|
+
yield* (yield* SqlClient.SqlClient)`
|
|
12866
|
+
ALTER TABLE projection_threads
|
|
12867
|
+
ADD COLUMN unprompted_subagents INTEGER NOT NULL DEFAULT 1
|
|
12868
|
+
`;
|
|
12869
|
+
});
|
|
12870
|
+
//#endregion
|
|
12663
12871
|
//#region src/persistence/Migrations.ts
|
|
12664
12872
|
/**
|
|
12665
12873
|
* MigrationsLive - Migration runner with inline loader
|
|
@@ -12880,6 +13088,16 @@ const migrationEntries = [
|
|
|
12880
13088
|
40,
|
|
12881
13089
|
"TaskExternalRef",
|
|
12882
13090
|
_040_TaskExternalRef_default
|
|
13091
|
+
],
|
|
13092
|
+
[
|
|
13093
|
+
41,
|
|
13094
|
+
"TaskParentTaskId",
|
|
13095
|
+
_041_TaskParentTaskId_default
|
|
13096
|
+
],
|
|
13097
|
+
[
|
|
13098
|
+
42,
|
|
13099
|
+
"ProjectionThreadsUnpromptedSubagents",
|
|
13100
|
+
_042_ProjectionThreadsUnpromptedSubagents_default
|
|
12883
13101
|
]
|
|
12884
13102
|
];
|
|
12885
13103
|
const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -14511,6 +14729,7 @@ const TASK_COLUMNS = `
|
|
|
14511
14729
|
repository_key AS "repositoryKey",
|
|
14512
14730
|
thread_id AS "threadId",
|
|
14513
14731
|
external_ref_json AS "externalRef",
|
|
14732
|
+
parent_task_id AS "parentTaskId",
|
|
14514
14733
|
title,
|
|
14515
14734
|
body,
|
|
14516
14735
|
status,
|
|
@@ -14535,6 +14754,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14535
14754
|
repository_key,
|
|
14536
14755
|
thread_id,
|
|
14537
14756
|
external_ref_json,
|
|
14757
|
+
parent_task_id,
|
|
14538
14758
|
title,
|
|
14539
14759
|
body,
|
|
14540
14760
|
status,
|
|
@@ -14552,6 +14772,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14552
14772
|
${row.repositoryKey},
|
|
14553
14773
|
${row.threadId},
|
|
14554
14774
|
${row.externalRef === null ? null : JSON.stringify(row.externalRef)},
|
|
14775
|
+
${row.parentTaskId},
|
|
14555
14776
|
${row.title},
|
|
14556
14777
|
${row.body},
|
|
14557
14778
|
${row.status},
|
|
@@ -14569,6 +14790,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14569
14790
|
repository_key = excluded.repository_key,
|
|
14570
14791
|
thread_id = excluded.thread_id,
|
|
14571
14792
|
external_ref_json = excluded.external_ref_json,
|
|
14793
|
+
parent_task_id = excluded.parent_task_id,
|
|
14572
14794
|
title = excluded.title,
|
|
14573
14795
|
body = excluded.body,
|
|
14574
14796
|
status = excluded.status,
|
|
@@ -14668,6 +14890,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14668
14890
|
...input.repositoryKey !== void 0 ? { repositoryKey: input.repositoryKey } : {},
|
|
14669
14891
|
...input.threadId !== void 0 ? { threadId: input.threadId } : {},
|
|
14670
14892
|
...input.externalRef !== void 0 ? { externalRef: input.externalRef } : {},
|
|
14893
|
+
...input.parentTaskId !== void 0 ? { parentTaskId: input.parentTaskId } : {},
|
|
14671
14894
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
14672
14895
|
...input.body !== void 0 ? { body: input.body } : {},
|
|
14673
14896
|
...input.status !== void 0 ? { status: input.status } : {},
|
|
@@ -14706,6 +14929,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14706
14929
|
...input.patch.projectId !== void 0 ? { projectId: input.patch.projectId } : {},
|
|
14707
14930
|
...input.patch.repositoryKey !== void 0 ? { repositoryKey: input.patch.repositoryKey } : {},
|
|
14708
14931
|
...input.patch.threadId !== void 0 ? { threadId: input.patch.threadId } : {},
|
|
14932
|
+
...input.patch.parentTaskId !== void 0 ? { parentTaskId: input.patch.parentTaskId } : {},
|
|
14709
14933
|
...input.patch.title !== void 0 ? { title: input.patch.title } : {},
|
|
14710
14934
|
...input.patch.body !== void 0 ? { body: input.patch.body } : {},
|
|
14711
14935
|
...input.patch.status !== void 0 ? { status: input.patch.status } : {},
|
|
@@ -14840,6 +15064,7 @@ const createTaskRoute = HttpRouter.add("POST", "/tasks", respondToHubFailures(Ef
|
|
|
14840
15064
|
repositoryKey: input.repositoryKey ?? null,
|
|
14841
15065
|
threadId: input.threadId ?? null,
|
|
14842
15066
|
externalRef: input.externalRef ?? null,
|
|
15067
|
+
parentTaskId: input.parentTaskId ?? null,
|
|
14843
15068
|
title: input.title,
|
|
14844
15069
|
body: input.body ?? "",
|
|
14845
15070
|
status: input.status ?? "backlog",
|
|
@@ -15159,6 +15384,22 @@ var _006_TaskExternalRef_default = Effect.gen(function* () {
|
|
|
15159
15384
|
`;
|
|
15160
15385
|
});
|
|
15161
15386
|
//#endregion
|
|
15387
|
+
//#region src/hub/Migrations/007_TaskParentTaskId.ts
|
|
15388
|
+
/**
|
|
15389
|
+
* The task a task was split out of, mirroring the environment server's
|
|
15390
|
+
* migration 041. The hub carries the column so a plan filed on one machine
|
|
15391
|
+
* still reads as a plan on another, rather than as a handful of loose tasks.
|
|
15392
|
+
*/
|
|
15393
|
+
var _007_TaskParentTaskId_default = Effect.gen(function* () {
|
|
15394
|
+
const sql = yield* SqlClient.SqlClient;
|
|
15395
|
+
yield* sql`ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`;
|
|
15396
|
+
yield* sql`
|
|
15397
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
15398
|
+
ON tasks(parent_task_id)
|
|
15399
|
+
WHERE parent_task_id IS NOT NULL
|
|
15400
|
+
`;
|
|
15401
|
+
});
|
|
15402
|
+
//#endregion
|
|
15162
15403
|
//#region src/hub/Migrations.ts
|
|
15163
15404
|
/**
|
|
15164
15405
|
* Hub migrations.
|
|
@@ -15199,6 +15440,11 @@ const hubMigrationEntries = [
|
|
|
15199
15440
|
6,
|
|
15200
15441
|
"TaskExternalRef",
|
|
15201
15442
|
_006_TaskExternalRef_default
|
|
15443
|
+
],
|
|
15444
|
+
[
|
|
15445
|
+
7,
|
|
15446
|
+
"TaskParentTaskId",
|
|
15447
|
+
_007_TaskParentTaskId_default
|
|
15202
15448
|
]
|
|
15203
15449
|
];
|
|
15204
15450
|
const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -16714,7 +16960,7 @@ const isIgnoredEntry = (entry) => IGNORED_ENTRIES.has(entry) || entry.startsWith
|
|
|
16714
16960
|
* Exported so the single-file kinds ask it the same way this one does, rather
|
|
16715
16961
|
* than each keeping a copy that could drift into a different answer.
|
|
16716
16962
|
*/
|
|
16717
|
-
const looksBinary$
|
|
16963
|
+
const looksBinary$2 = (content) => content.includes("\0");
|
|
16718
16964
|
/**
|
|
16719
16965
|
* Read one directory as an asset's files, or say why it cannot be one.
|
|
16720
16966
|
*
|
|
@@ -16746,7 +16992,7 @@ const readSkillFiles = Effect.fn("skillDirectory.readSkillFiles")(function* (roo
|
|
|
16746
16992
|
if (files.length >= 512) return "too-many-files";
|
|
16747
16993
|
const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
|
|
16748
16994
|
if (content === void 0) return "unreadable";
|
|
16749
|
-
if (looksBinary$
|
|
16995
|
+
if (looksBinary$2(content)) return "binary-file";
|
|
16750
16996
|
if (content.length > 524288) return "file-too-large";
|
|
16751
16997
|
files.push({
|
|
16752
16998
|
path: relative,
|
|
@@ -16870,7 +17116,7 @@ const readAgentDefinitions = Effect.fn("agentDefinitionFiles.read")(function* (a
|
|
|
16870
17116
|
});
|
|
16871
17117
|
continue;
|
|
16872
17118
|
}
|
|
16873
|
-
if (looksBinary$
|
|
17119
|
+
if (looksBinary$2(content)) {
|
|
16874
17120
|
refused.push({
|
|
16875
17121
|
name: entry,
|
|
16876
17122
|
reason: "binary-file"
|
|
@@ -17392,7 +17638,7 @@ const isMemoryDirectoryName = (name) => MEMORY_DIRECTORY_NAMES.includes(name);
|
|
|
17392
17638
|
*/
|
|
17393
17639
|
const isMemoryAssetName = (name) => isMemoryFileName(name) || isMemoryDirectoryName(name);
|
|
17394
17640
|
/** The same test the skill reader uses: a NUL is what breaks a JSON text column. */
|
|
17395
|
-
const looksBinary = (content) => content.includes("\0");
|
|
17641
|
+
const looksBinary$1 = (content) => content.includes("\0");
|
|
17396
17642
|
/**
|
|
17397
17643
|
* Read whichever memory files exist in this machine's config directory.
|
|
17398
17644
|
*
|
|
@@ -17416,7 +17662,7 @@ const readMemoryFiles = Effect.fn("memoryFile.readMemoryFiles")(function* (memor
|
|
|
17416
17662
|
});
|
|
17417
17663
|
continue;
|
|
17418
17664
|
}
|
|
17419
|
-
if (looksBinary(content)) {
|
|
17665
|
+
if (looksBinary$1(content)) {
|
|
17420
17666
|
refused.push({
|
|
17421
17667
|
name,
|
|
17422
17668
|
reason: "binary-file"
|
|
@@ -18529,6 +18775,8 @@ Never drop not/never/no/only/except: flipping the meaning costs more than any to
|
|
|
18529
18775
|
|
|
18530
18776
|
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
18777
|
|
|
18778
|
+
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.
|
|
18779
|
+
|
|
18532
18780
|
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
18781
|
|
|
18534
18782
|
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.
|
|
@@ -19788,6 +20036,7 @@ const ThreadMetaUpdatedPayload = ThreadMetaUpdatedPayload$1;
|
|
|
19788
20036
|
const ThreadRuntimeModeSetPayload = ThreadRuntimeModeSetPayload$1;
|
|
19789
20037
|
const ThreadInteractionModeSetPayload = ThreadInteractionModeSetPayload$1;
|
|
19790
20038
|
const ThreadCompressModeSetPayload = ThreadCompressModeSetPayload$1;
|
|
20039
|
+
const ThreadUnpromptedSubagentsSetPayload = ThreadUnpromptedSubagentsSetPayload$1;
|
|
19791
20040
|
const ThreadDeletedPayload = ThreadDeletedPayload$1;
|
|
19792
20041
|
const ThreadUnarchivedPayload = ThreadUnarchivedPayload$1;
|
|
19793
20042
|
const ThreadUnsettledPayload = ThreadUnsettledPayload$1;
|
|
@@ -19928,6 +20177,7 @@ function projectEvent(model, event) {
|
|
|
19928
20177
|
runtimeMode: payload.runtimeMode,
|
|
19929
20178
|
interactionMode: payload.interactionMode,
|
|
19930
20179
|
compressMode: payload.compressMode,
|
|
20180
|
+
unpromptedSubagents: payload.unpromptedSubagents,
|
|
19931
20181
|
branch: payload.branch,
|
|
19932
20182
|
worktreePath: payload.worktreePath,
|
|
19933
20183
|
latestTurn: null,
|
|
@@ -20034,6 +20284,13 @@ function projectEvent(model, event) {
|
|
|
20034
20284
|
updatedAt: payload.updatedAt
|
|
20035
20285
|
})
|
|
20036
20286
|
})));
|
|
20287
|
+
case "thread.unprompted-subagents-set": return decodeForEvent(ThreadUnpromptedSubagentsSetPayload, event.payload, event.type, "payload").pipe(Effect.map((payload) => ({
|
|
20288
|
+
...nextBase,
|
|
20289
|
+
threads: updateThread(nextBase.threads, payload.threadId, {
|
|
20290
|
+
unpromptedSubagents: payload.unpromptedSubagents,
|
|
20291
|
+
updatedAt: payload.updatedAt
|
|
20292
|
+
})
|
|
20293
|
+
})));
|
|
20037
20294
|
case "thread.message-sent": return Effect.gen(function* () {
|
|
20038
20295
|
const payload = yield* decodeForEvent(MessageSentPayloadSchema, event.payload, event.type, "payload");
|
|
20039
20296
|
const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
|
|
@@ -20400,6 +20657,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
|
|
|
20400
20657
|
runtimeMode: command.runtimeMode,
|
|
20401
20658
|
interactionMode: command.interactionMode,
|
|
20402
20659
|
compressMode: command.compressMode,
|
|
20660
|
+
unpromptedSubagents: command.unpromptedSubagents,
|
|
20403
20661
|
branch: command.branch,
|
|
20404
20662
|
worktreePath: command.worktreePath,
|
|
20405
20663
|
createdAt: command.createdAt,
|
|
@@ -20679,6 +20937,28 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
|
|
|
20679
20937
|
}
|
|
20680
20938
|
};
|
|
20681
20939
|
}
|
|
20940
|
+
case "thread.unprompted-subagents.set": {
|
|
20941
|
+
yield* requireThread({
|
|
20942
|
+
readModel,
|
|
20943
|
+
command,
|
|
20944
|
+
threadId: command.threadId
|
|
20945
|
+
});
|
|
20946
|
+
const occurredAt = yield* nowIso$8;
|
|
20947
|
+
return {
|
|
20948
|
+
...yield* withEventBase({
|
|
20949
|
+
aggregateKind: "thread",
|
|
20950
|
+
aggregateId: command.threadId,
|
|
20951
|
+
occurredAt,
|
|
20952
|
+
commandId: command.commandId
|
|
20953
|
+
}),
|
|
20954
|
+
type: "thread.unprompted-subagents-set",
|
|
20955
|
+
payload: {
|
|
20956
|
+
threadId: command.threadId,
|
|
20957
|
+
unpromptedSubagents: command.unpromptedSubagents,
|
|
20958
|
+
updatedAt: occurredAt
|
|
20959
|
+
}
|
|
20960
|
+
};
|
|
20961
|
+
}
|
|
20682
20962
|
case "thread.turn.start": {
|
|
20683
20963
|
const targetThread = yield* requireThread({
|
|
20684
20964
|
readModel,
|
|
@@ -20737,6 +21017,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
|
|
|
20737
21017
|
runtimeMode: targetThread.runtimeMode,
|
|
20738
21018
|
interactionMode: targetThread.interactionMode,
|
|
20739
21019
|
compressMode: targetThread.compressMode,
|
|
21020
|
+
unpromptedSubagents: targetThread.unpromptedSubagents,
|
|
20740
21021
|
...sourceProposedPlan !== void 0 ? { sourceProposedPlan } : {},
|
|
20741
21022
|
createdAt: command.createdAt
|
|
20742
21023
|
}
|
|
@@ -21503,6 +21784,8 @@ const ProjectionThread = Schema$1.Struct({
|
|
|
21503
21784
|
runtimeMode: RuntimeMode,
|
|
21504
21785
|
interactionMode: ProviderInteractionMode,
|
|
21505
21786
|
compressMode: CompressMode,
|
|
21787
|
+
/** 0 or 1: SQLite has no boolean, matching `hasActionableProposedPlan`. */
|
|
21788
|
+
unpromptedSubagents: NonNegativeInt,
|
|
21506
21789
|
branch: Schema$1.NullOr(Schema$1.String),
|
|
21507
21790
|
worktreePath: Schema$1.NullOr(Schema$1.String),
|
|
21508
21791
|
latestTurnId: Schema$1.NullOr(TurnId),
|
|
@@ -22391,6 +22674,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22391
22674
|
runtime_mode,
|
|
22392
22675
|
interaction_mode,
|
|
22393
22676
|
compress_mode,
|
|
22677
|
+
unprompted_subagents,
|
|
22394
22678
|
branch,
|
|
22395
22679
|
worktree_path,
|
|
22396
22680
|
latest_turn_id,
|
|
@@ -22415,6 +22699,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22415
22699
|
${row.runtimeMode},
|
|
22416
22700
|
${row.interactionMode},
|
|
22417
22701
|
${row.compressMode},
|
|
22702
|
+
${row.unpromptedSubagents},
|
|
22418
22703
|
${row.branch},
|
|
22419
22704
|
${row.worktreePath},
|
|
22420
22705
|
${row.latestTurnId},
|
|
@@ -22439,6 +22724,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22439
22724
|
runtime_mode = excluded.runtime_mode,
|
|
22440
22725
|
interaction_mode = excluded.interaction_mode,
|
|
22441
22726
|
compress_mode = excluded.compress_mode,
|
|
22727
|
+
unprompted_subagents = excluded.unprompted_subagents,
|
|
22442
22728
|
branch = excluded.branch,
|
|
22443
22729
|
worktree_path = excluded.worktree_path,
|
|
22444
22730
|
latest_turn_id = excluded.latest_turn_id,
|
|
@@ -22468,6 +22754,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22468
22754
|
runtime_mode AS "runtimeMode",
|
|
22469
22755
|
interaction_mode AS "interactionMode",
|
|
22470
22756
|
compress_mode AS "compressMode",
|
|
22757
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
22471
22758
|
branch,
|
|
22472
22759
|
worktree_path AS "worktreePath",
|
|
22473
22760
|
latest_turn_id AS "latestTurnId",
|
|
@@ -22499,6 +22786,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
22499
22786
|
runtime_mode AS "runtimeMode",
|
|
22500
22787
|
interaction_mode AS "interactionMode",
|
|
22501
22788
|
compress_mode AS "compressMode",
|
|
22789
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
22502
22790
|
branch,
|
|
22503
22791
|
worktree_path AS "worktreePath",
|
|
22504
22792
|
latest_turn_id AS "latestTurnId",
|
|
@@ -22966,6 +23254,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
22966
23254
|
runtimeMode: event.payload.runtimeMode,
|
|
22967
23255
|
interactionMode: event.payload.interactionMode,
|
|
22968
23256
|
compressMode: event.payload.compressMode,
|
|
23257
|
+
unpromptedSubagents: event.payload.unpromptedSubagents ? 1 : 0,
|
|
22969
23258
|
branch: event.payload.branch,
|
|
22970
23259
|
worktreePath: event.payload.worktreePath,
|
|
22971
23260
|
latestTurnId: null,
|
|
@@ -23090,6 +23379,16 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
23090
23379
|
});
|
|
23091
23380
|
return;
|
|
23092
23381
|
}
|
|
23382
|
+
case "thread.unprompted-subagents-set": {
|
|
23383
|
+
const existingRow = yield* projectionThreadRepository.getById({ threadId: event.payload.threadId });
|
|
23384
|
+
if (Option.isNone(existingRow)) return;
|
|
23385
|
+
yield* projectionThreadRepository.upsert({
|
|
23386
|
+
...existingRow.value,
|
|
23387
|
+
unpromptedSubagents: event.payload.unpromptedSubagents ? 1 : 0,
|
|
23388
|
+
updatedAt: event.payload.updatedAt
|
|
23389
|
+
});
|
|
23390
|
+
return;
|
|
23391
|
+
}
|
|
23093
23392
|
case "thread.deleted": {
|
|
23094
23393
|
attachmentSideEffects.deletedThreadIds.add(event.payload.threadId);
|
|
23095
23394
|
const existingRow = yield* projectionThreadRepository.getById({ threadId: event.payload.threadId });
|
|
@@ -24412,6 +24711,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24412
24711
|
runtime_mode AS "runtimeMode",
|
|
24413
24712
|
interaction_mode AS "interactionMode",
|
|
24414
24713
|
compress_mode AS "compressMode",
|
|
24714
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24415
24715
|
branch,
|
|
24416
24716
|
worktree_path AS "worktreePath",
|
|
24417
24717
|
latest_turn_id AS "latestTurnId",
|
|
@@ -24443,6 +24743,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24443
24743
|
runtime_mode AS "runtimeMode",
|
|
24444
24744
|
interaction_mode AS "interactionMode",
|
|
24445
24745
|
compress_mode AS "compressMode",
|
|
24746
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24446
24747
|
branch,
|
|
24447
24748
|
worktree_path AS "worktreePath",
|
|
24448
24749
|
latest_turn_id AS "latestTurnId",
|
|
@@ -24476,6 +24777,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24476
24777
|
runtime_mode AS "runtimeMode",
|
|
24477
24778
|
interaction_mode AS "interactionMode",
|
|
24478
24779
|
compress_mode AS "compressMode",
|
|
24780
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24479
24781
|
branch,
|
|
24480
24782
|
worktree_path AS "worktreePath",
|
|
24481
24783
|
latest_turn_id AS "latestTurnId",
|
|
@@ -24809,6 +25111,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
24809
25111
|
runtime_mode AS "runtimeMode",
|
|
24810
25112
|
interaction_mode AS "interactionMode",
|
|
24811
25113
|
compress_mode AS "compressMode",
|
|
25114
|
+
unprompted_subagents AS "unpromptedSubagents",
|
|
24812
25115
|
branch,
|
|
24813
25116
|
worktree_path AS "worktreePath",
|
|
24814
25117
|
latest_turn_id AS "latestTurnId",
|
|
@@ -25111,6 +25414,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25111
25414
|
runtimeMode: row.runtimeMode,
|
|
25112
25415
|
interactionMode: row.interactionMode,
|
|
25113
25416
|
compressMode: row.compressMode,
|
|
25417
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25114
25418
|
branch: row.branch,
|
|
25115
25419
|
worktreePath: row.worktreePath,
|
|
25116
25420
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25223,6 +25527,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25223
25527
|
runtimeMode: row.runtimeMode,
|
|
25224
25528
|
interactionMode: row.interactionMode,
|
|
25225
25529
|
compressMode: row.compressMode,
|
|
25530
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25226
25531
|
branch: row.branch,
|
|
25227
25532
|
worktreePath: row.worktreePath,
|
|
25228
25533
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25282,6 +25587,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25282
25587
|
runtimeMode: row.runtimeMode,
|
|
25283
25588
|
interactionMode: row.interactionMode,
|
|
25284
25589
|
compressMode: row.compressMode,
|
|
25590
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25285
25591
|
branch: row.branch,
|
|
25286
25592
|
worktreePath: row.worktreePath,
|
|
25287
25593
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25337,6 +25643,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25337
25643
|
runtimeMode: row.runtimeMode,
|
|
25338
25644
|
interactionMode: row.interactionMode,
|
|
25339
25645
|
compressMode: row.compressMode,
|
|
25646
|
+
unpromptedSubagents: row.unpromptedSubagents > 0,
|
|
25340
25647
|
branch: row.branch,
|
|
25341
25648
|
worktreePath: row.worktreePath,
|
|
25342
25649
|
latestTurn: latestTurnByThread.get(row.threadId) ?? null,
|
|
@@ -25428,6 +25735,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25428
25735
|
runtimeMode: threadRow.value.runtimeMode,
|
|
25429
25736
|
interactionMode: threadRow.value.interactionMode,
|
|
25430
25737
|
compressMode: threadRow.value.compressMode,
|
|
25738
|
+
unpromptedSubagents: threadRow.value.unpromptedSubagents > 0,
|
|
25431
25739
|
branch: threadRow.value.branch,
|
|
25432
25740
|
worktreePath: threadRow.value.worktreePath,
|
|
25433
25741
|
latestTurn: Option.isSome(latestTurnRow) ? mapLatestTurn(latestTurnRow.value) : null,
|
|
@@ -25464,6 +25772,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
25464
25772
|
runtimeMode: threadRow.value.runtimeMode,
|
|
25465
25773
|
interactionMode: threadRow.value.interactionMode,
|
|
25466
25774
|
compressMode: threadRow.value.compressMode,
|
|
25775
|
+
unpromptedSubagents: threadRow.value.unpromptedSubagents > 0,
|
|
25467
25776
|
branch: threadRow.value.branch,
|
|
25468
25777
|
worktreePath: threadRow.value.worktreePath,
|
|
25469
25778
|
latestTurn: Option.isSome(latestTurnRow) ? mapLatestTurn(latestTurnRow.value) : null,
|
|
@@ -28873,6 +29182,7 @@ const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
|
|
|
28873
29182
|
modelSelection: nextProjectDefaultModelSelection,
|
|
28874
29183
|
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
|
|
28875
29184
|
compressMode: DEFAULT_COMPRESS_MODE,
|
|
29185
|
+
unpromptedSubagents: true,
|
|
28876
29186
|
runtimeMode: "full-access",
|
|
28877
29187
|
branch: null,
|
|
28878
29188
|
worktreePath: null,
|
|
@@ -34856,6 +35166,7 @@ const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
|
|
|
34856
35166
|
repositoryKey: repositoryKey ?? input.repositoryKey ?? null,
|
|
34857
35167
|
threadId: input.threadId ?? null,
|
|
34858
35168
|
externalRef: input.externalRef ?? null,
|
|
35169
|
+
parentTaskId: input.parentTaskId ?? null,
|
|
34859
35170
|
title: input.title,
|
|
34860
35171
|
body: input.body ?? "",
|
|
34861
35172
|
status: input.status ?? "backlog",
|
|
@@ -45693,6 +46004,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
45693
46004
|
runtimeMode: bootstrap.createThread.runtimeMode,
|
|
45694
46005
|
interactionMode: bootstrap.createThread.interactionMode,
|
|
45695
46006
|
compressMode: bootstrap.createThread.compressMode,
|
|
46007
|
+
unpromptedSubagents: bootstrap.createThread.unpromptedSubagents,
|
|
45696
46008
|
branch: bootstrap.createThread.branch,
|
|
45697
46009
|
worktreePath: bootstrap.createThread.worktreePath,
|
|
45698
46010
|
createdAt: bootstrap.createThread.createdAt
|
|
@@ -45957,6 +46269,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
45957
46269
|
runtimeMode: DEFAULT_RUNTIME_MODE$2,
|
|
45958
46270
|
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
|
|
45959
46271
|
compressMode: DEFAULT_COMPRESS_MODE,
|
|
46272
|
+
unpromptedSubagents: true,
|
|
45960
46273
|
bootstrap: { createThread: {
|
|
45961
46274
|
projectId: plan.projectId,
|
|
45962
46275
|
title: threadTitle,
|
|
@@ -45964,6 +46277,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
45964
46277
|
runtimeMode: DEFAULT_RUNTIME_MODE$2,
|
|
45965
46278
|
interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,
|
|
45966
46279
|
compressMode: DEFAULT_COMPRESS_MODE,
|
|
46280
|
+
unpromptedSubagents: true,
|
|
45967
46281
|
branch: null,
|
|
45968
46282
|
worktreePath: null,
|
|
45969
46283
|
createdAt
|
|
@@ -46374,6 +46688,7 @@ const makeRemoteTaskRepository = Effect.fn("makeRemoteTaskRepository")(function*
|
|
|
46374
46688
|
...row.projectId === null ? {} : { projectId: row.projectId },
|
|
46375
46689
|
...row.repositoryKey === null ? {} : { repositoryKey: row.repositoryKey },
|
|
46376
46690
|
...row.threadId === null ? {} : { threadId: row.threadId },
|
|
46691
|
+
...row.parentTaskId === null ? {} : { parentTaskId: row.parentTaskId },
|
|
46377
46692
|
title: row.title,
|
|
46378
46693
|
body: row.body,
|
|
46379
46694
|
status: row.status,
|
|
@@ -52502,7 +52817,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
52502
52817
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
52503
52818
|
const externalMcpServers = options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers;
|
|
52504
52819
|
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;
|
|
52820
|
+
const unpromptedSubagents = input.unpromptedSubagents !== void 0 ? input.unpromptedSubagents : options?.resolveUnpromptedSubagents === void 0 ? DEFAULT_SERVER_SETTINGS.enableUnpromptedSubagents : yield* options.resolveUnpromptedSubagents;
|
|
52506
52821
|
const compressRuleset = compressRulesetFor(input.compressMode ?? "off");
|
|
52507
52822
|
const systemPromptAppend = [
|
|
52508
52823
|
...narrateBeforeTools ? [NARRATE_BEFORE_TOOLS_PROMPT] : [],
|
|
@@ -52601,6 +52916,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
52601
52916
|
startedAt,
|
|
52602
52917
|
basePermissionMode: permissionMode,
|
|
52603
52918
|
currentApiModelId: apiModelId,
|
|
52919
|
+
currentUnpromptedSubagents: unpromptedSubagents,
|
|
52604
52920
|
resumeSessionId: sessionId,
|
|
52605
52921
|
pendingApprovals,
|
|
52606
52922
|
pendingUserInputs,
|
|
@@ -52672,7 +52988,13 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
52672
52988
|
const context = yield* requireSession(input.threadId);
|
|
52673
52989
|
const modelSelection = input.modelSelection !== void 0 && input.modelSelection.instanceId === boundInstanceId ? input.modelSelection : void 0;
|
|
52674
52990
|
if (context.turnState?.synthetic === true) yield* completeTurn(context, "completed");
|
|
52675
|
-
const
|
|
52991
|
+
const turnSubagentsPrompt = input.unpromptedSubagents !== void 0 && input.unpromptedSubagents !== context.currentUnpromptedSubagents ? input.unpromptedSubagents ? SUBAGENTS_ALLOWED_PROMPT : SUBAGENTS_ON_REQUEST_PROMPT : void 0;
|
|
52992
|
+
if (input.unpromptedSubagents !== void 0) context.currentUnpromptedSubagents = input.unpromptedSubagents;
|
|
52993
|
+
const turnInput = turnSubagentsPrompt === void 0 ? input : {
|
|
52994
|
+
...input,
|
|
52995
|
+
input: input.input === void 0 ? turnSubagentsPrompt : `${turnSubagentsPrompt}\n\n${input.input}`
|
|
52996
|
+
};
|
|
52997
|
+
const message = yield* buildUserMessageEffect(turnInput, {
|
|
52676
52998
|
fileSystem,
|
|
52677
52999
|
attachmentsDir: serverConfig.attachmentsDir,
|
|
52678
53000
|
boundInstanceId
|
|
@@ -85945,6 +86267,28 @@ const findTask = (taskId) => Effect.gen(function* () {
|
|
|
85945
86267
|
const tasks = yield* TaskRepository;
|
|
85946
86268
|
return isReadableTaskId(taskId) ? yield* tasks.getByReadableId(taskId) : yield* tasks.getById({ taskId });
|
|
85947
86269
|
});
|
|
86270
|
+
/**
|
|
86271
|
+
* Resolve a parent reference to the UUID the column stores.
|
|
86272
|
+
*
|
|
86273
|
+
* A planning agent works in readable ids - it has just read `P4-12` off the
|
|
86274
|
+
* board - but the link is stored as the row key, because readable ids are
|
|
86275
|
+
* allocated per store and the one it quotes may have been minted on another
|
|
86276
|
+
* machine. Absent stays absent and an explicit null stays null: those mean
|
|
86277
|
+
* "leave it" and "clear it", neither of which is a reference to resolve.
|
|
86278
|
+
*
|
|
86279
|
+
* A readable id that names no task fails rather than being stored raw. The
|
|
86280
|
+
* opposite would file a subtask whose parent link can never resolve, which
|
|
86281
|
+
* reads on the board as a task that is part of nothing. A UUID is taken on
|
|
86282
|
+
* trust, matching the column's lack of a foreign key: a parent may legitimately
|
|
86283
|
+
* arrive after its child on a board synced from elsewhere.
|
|
86284
|
+
*/
|
|
86285
|
+
const resolveParentTaskId = (parentTaskId, operation) => Effect.gen(function* () {
|
|
86286
|
+
if (parentTaskId === void 0 || parentTaskId === null) return parentTaskId;
|
|
86287
|
+
if (!isReadableTaskId(parentTaskId)) return parentTaskId;
|
|
86288
|
+
const parent = yield* findTask(parentTaskId).pipe(Effect.catch(toTaskStoreError(operation)));
|
|
86289
|
+
if (Option.isNone(parent)) return yield* new TaskNotFoundError({ taskId: parentTaskId });
|
|
86290
|
+
return parent.value.taskId;
|
|
86291
|
+
});
|
|
85948
86292
|
const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
85949
86293
|
task_list: (input) => Effect.gen(function* () {
|
|
85950
86294
|
yield* requireTaskCapability();
|
|
@@ -85972,7 +86316,11 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
85972
86316
|
const timestamp = yield* nowIso$1;
|
|
85973
86317
|
const repositoryKey = yield* resolveTaskRepositoryKey(input.projectId);
|
|
85974
86318
|
const readableIdPrefix = yield* resolveTaskReadableIdPrefix(input.projectId);
|
|
85975
|
-
const
|
|
86319
|
+
const parentTaskId = yield* resolveParentTaskId(input.parentTaskId, "task_create");
|
|
86320
|
+
const row = buildTaskRow(parentTaskId === void 0 || parentTaskId === null ? input : {
|
|
86321
|
+
...input,
|
|
86322
|
+
parentTaskId
|
|
86323
|
+
}, taskId, timestamp, repositoryKey);
|
|
85976
86324
|
return yield* tasks.create(row, readableIdPrefix).pipe(Effect.catch(toTaskStoreError("task_create")));
|
|
85977
86325
|
}),
|
|
85978
86326
|
task_update: (input) => Effect.gen(function* () {
|
|
@@ -85981,13 +86329,12 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
85981
86329
|
const timestamp = yield* nowIso$1;
|
|
85982
86330
|
const taskId = isReadableTaskId(input.taskId) ? Option.getOrNull(yield* findTask(input.taskId).pipe(Effect.catch(toTaskStoreError("task_update"))))?.taskId : input.taskId;
|
|
85983
86331
|
if (taskId === void 0) return yield* new TaskNotFoundError({ taskId: input.taskId });
|
|
85984
|
-
const
|
|
85985
|
-
|
|
85986
|
-
taskId
|
|
85987
|
-
} : {
|
|
86332
|
+
const parentTaskId = yield* resolveParentTaskId(input.parentTaskId, "task_update");
|
|
86333
|
+
const patch = {
|
|
85988
86334
|
...input,
|
|
85989
86335
|
taskId,
|
|
85990
|
-
|
|
86336
|
+
...parentTaskId === void 0 ? {} : { parentTaskId },
|
|
86337
|
+
...input.projectId === void 0 ? {} : { repositoryKey: yield* resolveTaskRepositoryKey(input.projectId) }
|
|
85991
86338
|
};
|
|
85992
86339
|
const patched = yield* tasks.patch(patch, timestamp).pipe(Effect.catch(toTaskStoreError("task_update")));
|
|
85993
86340
|
if (Option.isNone(patched)) return yield* new TaskNotFoundError({ taskId: input.taskId });
|
|
@@ -85999,6 +86346,242 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
85999
86346
|
})
|
|
86000
86347
|
});
|
|
86001
86348
|
//#endregion
|
|
86349
|
+
//#region src/sync/assetCompression.ts
|
|
86350
|
+
/**
|
|
86351
|
+
* Checking that a compressed asset still says what the original said.
|
|
86352
|
+
*
|
|
86353
|
+
* Compression here is a rewrite of prose an agent reads before it does anything
|
|
86354
|
+
* else: a skill, a memory file, a subagent definition. Rewriting that prose is
|
|
86355
|
+
* cheap to get almost right and expensive to get slightly wrong, because the
|
|
86356
|
+
* damage is silent. A dropped URL, a mangled command, a heading that no longer
|
|
86357
|
+
* matches the sentence pointing at it - none of that fails loudly. It just makes
|
|
86358
|
+
* the agent behave a little differently on every machine that syncs the file.
|
|
86359
|
+
*
|
|
86360
|
+
* So the write is gated on a comparison rather than on the model's word for it.
|
|
86361
|
+
* The rules are ported from caveman's `validate.py`, with the ones this task
|
|
86362
|
+
* calls out - headings, code blocks, URLs, paths, bullet counts - promoted to
|
|
86363
|
+
* refusals rather than warnings. A refusal costs one retry; a bad write costs
|
|
86364
|
+
* whatever the agent does next, on every machine, until someone notices.
|
|
86365
|
+
*
|
|
86366
|
+
* Pure on purpose. Everything here is a string comparison, so every rule can be
|
|
86367
|
+
* tested without a filesystem, a provider or a thread, and the tool that calls
|
|
86368
|
+
* it is left with only the parts that genuinely touch disk.
|
|
86369
|
+
*
|
|
86370
|
+
* @module sync/assetCompression
|
|
86371
|
+
*/
|
|
86372
|
+
/**
|
|
86373
|
+
* How far the bullet count may move before the rewrite stops being compression.
|
|
86374
|
+
*
|
|
86375
|
+
* Not zero. Compression legitimately folds a two-line bullet into one, and a
|
|
86376
|
+
* rule that forbade that would forbid the feature. Caveman's own threshold,
|
|
86377
|
+
* kept because it is the one their receipts were measured against.
|
|
86378
|
+
*/
|
|
86379
|
+
const BULLET_COUNT_TOLERANCE = .15;
|
|
86380
|
+
/**
|
|
86381
|
+
* Characters per token, for the before/after figures.
|
|
86382
|
+
*
|
|
86383
|
+
* An estimate, and labelled as one everywhere it surfaces. The exact count
|
|
86384
|
+
* belongs to whichever provider tokenizer will read the file, and asking one
|
|
86385
|
+
* would mean a network call inside a validation path that is otherwise pure.
|
|
86386
|
+
* Four is the usual English-prose approximation and is good enough to answer
|
|
86387
|
+
* the only question anyone asks here: did this get meaningfully smaller.
|
|
86388
|
+
*/
|
|
86389
|
+
const CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
86390
|
+
const URL_REGEX = /https?:\/\/[^\s)]+/gu;
|
|
86391
|
+
/**
|
|
86392
|
+
* Sentence punctuation swept up by the URL match.
|
|
86393
|
+
*
|
|
86394
|
+
* `See https://svgl.app for the catalogue.` and `Catalogue: https://svgl.app.`
|
|
86395
|
+
* name the same URL, and a comparison that disagreed would refuse every rewrite
|
|
86396
|
+
* that moved a link to the end of a sentence - which is most of them.
|
|
86397
|
+
*/
|
|
86398
|
+
const URL_TRAILING_PUNCTUATION_REGEX = /[.,;:!?'"\])]+$/u;
|
|
86399
|
+
const FENCE_LINE_REGEX = /^(\s{0,3})(`{3,}|~{3,})(.*)$/u;
|
|
86400
|
+
const HEADING_REGEX = /^(#{1,6})\s+(.*)$/gmu;
|
|
86401
|
+
const BULLET_REGEX = /^\s*[-*+]\s+/gmu;
|
|
86402
|
+
const INLINE_CODE_REGEX = /`([^`]+)`/gu;
|
|
86403
|
+
/**
|
|
86404
|
+
* Crude, and deliberately so.
|
|
86405
|
+
*
|
|
86406
|
+
* Either a path prefix (`./`, `../`, `/`, a drive letter) or a separator inside
|
|
86407
|
+
* the match. It over-matches - `and/or` is a "path" by this rule - and the
|
|
86408
|
+
* over-match is the safe direction: a false positive refuses a rewrite that
|
|
86409
|
+
* dropped the phrase, and a false negative lets a real path go missing.
|
|
86410
|
+
*/
|
|
86411
|
+
const PATH_REGEX = /(?:\.\/|\.\.\/|\/|[A-Za-z]:\\)[\w\-/\\.]+|[\w\-.]+[/\\][\w\-/\\.]+/gu;
|
|
86412
|
+
/** YAML frontmatter: `---` on the first line, `---` on a line of its own after. */
|
|
86413
|
+
const FRONTMATTER_REGEX = /^---\r?\n[\s\S]*?\r?\n---\r?\n/u;
|
|
86414
|
+
/**
|
|
86415
|
+
* Filenames that almost certainly hold secrets, and the directories that do.
|
|
86416
|
+
*
|
|
86417
|
+
* Compressing a file means handing its bytes to a model, so this is checked
|
|
86418
|
+
* before the read rather than after it. The asset roots should never contain
|
|
86419
|
+
* any of these, which is exactly why the check is cheap to keep: it costs
|
|
86420
|
+
* nothing in the ordinary case and is the only thing standing between a
|
|
86421
|
+
* misplaced `credentials.md` and a provider's API.
|
|
86422
|
+
*/
|
|
86423
|
+
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;
|
|
86424
|
+
const SENSITIVE_PATH_SEGMENTS = /* @__PURE__ */ new Set([
|
|
86425
|
+
".ssh",
|
|
86426
|
+
".aws",
|
|
86427
|
+
".gnupg",
|
|
86428
|
+
".kube",
|
|
86429
|
+
".docker"
|
|
86430
|
+
]);
|
|
86431
|
+
const SENSITIVE_NAME_TOKENS = [
|
|
86432
|
+
"secret",
|
|
86433
|
+
"credential",
|
|
86434
|
+
"password",
|
|
86435
|
+
"passwd",
|
|
86436
|
+
"apikey",
|
|
86437
|
+
"accesskey",
|
|
86438
|
+
"token",
|
|
86439
|
+
"privatekey"
|
|
86440
|
+
];
|
|
86441
|
+
const segmentsOf = (filePath) => filePath.split(/[/\\]/u).filter((segment) => segment.length > 0);
|
|
86442
|
+
/**
|
|
86443
|
+
* Whether this path looks like something that must not be sent to a model.
|
|
86444
|
+
*
|
|
86445
|
+
* Name-based, because that is all that is knowable before the read. Separators
|
|
86446
|
+
* inside the name are normalized away first so `api-key.md` and `api_key.md`
|
|
86447
|
+
* are the same answer.
|
|
86448
|
+
*/
|
|
86449
|
+
const isSensitiveAssetPath = (filePath) => {
|
|
86450
|
+
const segments = segmentsOf(filePath);
|
|
86451
|
+
const name = segments.at(-1) ?? "";
|
|
86452
|
+
if (SENSITIVE_BASENAME_REGEX.test(name)) return true;
|
|
86453
|
+
if (segments.some((segment) => SENSITIVE_PATH_SEGMENTS.has(segment.toLowerCase()))) return true;
|
|
86454
|
+
const flattened = name.toLowerCase().replace(/[_\-\s.]/gu, "");
|
|
86455
|
+
return SENSITIVE_NAME_TOKENS.some((token) => flattened.includes(token));
|
|
86456
|
+
};
|
|
86457
|
+
/** The frontmatter block including its delimiters, or the empty string. */
|
|
86458
|
+
const frontmatterOf = (text) => FRONTMATTER_REGEX.exec(text)?.[0] ?? "";
|
|
86459
|
+
const headingsOf = (text) => [...text.matchAll(HEADING_REGEX)].map((match) => `${match[1] ?? ""} ${(match[2] ?? "").trim()}`);
|
|
86460
|
+
/**
|
|
86461
|
+
* Fenced code blocks, as whole strings, fences included.
|
|
86462
|
+
*
|
|
86463
|
+
* Line-based rather than one regex, so a closing fence is only a closing fence
|
|
86464
|
+
* when CommonMark says it is: same character, at least as long as the opener,
|
|
86465
|
+
* nothing after it. That is what lets a four-backtick block wrap three-backtick
|
|
86466
|
+
* content without the extractor stopping in the middle of it.
|
|
86467
|
+
*
|
|
86468
|
+
* An unclosed fence is skipped rather than reported. It means the markdown was
|
|
86469
|
+
* already malformed, and including it would fail every compression of a file
|
|
86470
|
+
* that was broken before anyone touched it.
|
|
86471
|
+
*/
|
|
86472
|
+
const codeBlocksOf = (text) => {
|
|
86473
|
+
const lines = text.split("\n");
|
|
86474
|
+
const blocks = [];
|
|
86475
|
+
let index = 0;
|
|
86476
|
+
while (index < lines.length) {
|
|
86477
|
+
const opening = FENCE_LINE_REGEX.exec(lines[index] ?? "");
|
|
86478
|
+
if (opening === null) {
|
|
86479
|
+
index += 1;
|
|
86480
|
+
continue;
|
|
86481
|
+
}
|
|
86482
|
+
const fence = opening[2] ?? "";
|
|
86483
|
+
const collected = [lines[index] ?? ""];
|
|
86484
|
+
index += 1;
|
|
86485
|
+
let closed = false;
|
|
86486
|
+
while (index < lines.length) {
|
|
86487
|
+
const line = lines[index] ?? "";
|
|
86488
|
+
const closing = FENCE_LINE_REGEX.exec(line);
|
|
86489
|
+
const closingFence = closing?.[2] ?? "";
|
|
86490
|
+
if (closing !== null && closingFence[0] === fence[0] && closingFence.length >= fence.length && (closing[3] ?? "").trim().length === 0) {
|
|
86491
|
+
collected.push(line);
|
|
86492
|
+
closed = true;
|
|
86493
|
+
index += 1;
|
|
86494
|
+
break;
|
|
86495
|
+
}
|
|
86496
|
+
collected.push(line);
|
|
86497
|
+
index += 1;
|
|
86498
|
+
}
|
|
86499
|
+
if (closed) blocks.push(collected.join("\n"));
|
|
86500
|
+
}
|
|
86501
|
+
return blocks;
|
|
86502
|
+
};
|
|
86503
|
+
const withoutCodeBlocks = (text) => codeBlocksOf(text).reduce((rest, block) => rest.replace(block, ""), text);
|
|
86504
|
+
const inlineCodeCounts = (text) => {
|
|
86505
|
+
const counts = /* @__PURE__ */ new Map();
|
|
86506
|
+
for (const match of withoutCodeBlocks(text).matchAll(INLINE_CODE_REGEX)) {
|
|
86507
|
+
const span = match[1] ?? "";
|
|
86508
|
+
counts.set(span, (counts.get(span) ?? 0) + 1);
|
|
86509
|
+
}
|
|
86510
|
+
return counts;
|
|
86511
|
+
};
|
|
86512
|
+
const urlsOf = (text) => new Set([...text.matchAll(URL_REGEX)].map((match) => match[0].replace(URL_TRAILING_PUNCTUATION_REGEX, "")));
|
|
86513
|
+
/**
|
|
86514
|
+
* Paths, with URLs taken out of the text first.
|
|
86515
|
+
*
|
|
86516
|
+
* `//svgl.app` inside `https://svgl.app` is a path by the pattern's reckoning,
|
|
86517
|
+
* so leaving the URLs in would have every link contribute a phantom path whose
|
|
86518
|
+
* spelling changes with the punctuation after it.
|
|
86519
|
+
*/
|
|
86520
|
+
const pathsOf = (text) => new Set([...text.replace(URL_REGEX, " ").matchAll(PATH_REGEX)].map((match) => match[0]));
|
|
86521
|
+
const countOf = (text, pattern) => [...text.matchAll(pattern)].length;
|
|
86522
|
+
const missingFrom = (original, compressed) => [...original].filter((value) => !compressed.has(value));
|
|
86523
|
+
const addedTo = (original, compressed) => [...compressed].filter((value) => !original.has(value));
|
|
86524
|
+
const quoteList = (values) => values.map((value) => `'${value}'`).join(", ");
|
|
86525
|
+
/**
|
|
86526
|
+
* Compare a compressed rewrite against the original it came from.
|
|
86527
|
+
*
|
|
86528
|
+
* Errors are the things whose loss changes what the file instructs: a code
|
|
86529
|
+
* block, a URL, a path, an inline span, a heading, the frontmatter. Additions
|
|
86530
|
+
* are warnings rather than errors throughout, because a rewrite that names one
|
|
86531
|
+
* more path than the original is doing something odd but is not losing
|
|
86532
|
+
* anything, and a refusal there would be a refusal nobody can act on.
|
|
86533
|
+
*/
|
|
86534
|
+
const validateCompression = (original, compressed) => {
|
|
86535
|
+
const errors = [];
|
|
86536
|
+
const warnings = [];
|
|
86537
|
+
if (frontmatterOf(original) !== frontmatterOf(compressed)) errors.push("The YAML frontmatter changed. It is metadata the provider parses, not prose; return it byte-identical.");
|
|
86538
|
+
const originalHeadings = headingsOf(original);
|
|
86539
|
+
const compressedHeadings = headingsOf(compressed);
|
|
86540
|
+
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.`);
|
|
86541
|
+
const originalBlocks = codeBlocksOf(original);
|
|
86542
|
+
const compressedBlocks = codeBlocksOf(compressed);
|
|
86543
|
+
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.`);
|
|
86544
|
+
const originalUrls = urlsOf(original);
|
|
86545
|
+
const compressedUrls = urlsOf(compressed);
|
|
86546
|
+
const lostUrls = missingFrom(originalUrls, compressedUrls);
|
|
86547
|
+
if (lostUrls.length > 0) errors.push(`URLs missing from the rewrite: ${quoteList(lostUrls)}.`);
|
|
86548
|
+
const addedUrls = addedTo(originalUrls, compressedUrls);
|
|
86549
|
+
if (addedUrls.length > 0) warnings.push(`URLs the original did not have: ${quoteList(addedUrls)}.`);
|
|
86550
|
+
const originalPaths = pathsOf(original);
|
|
86551
|
+
const compressedPaths = pathsOf(compressed);
|
|
86552
|
+
const lostPaths = missingFrom(originalPaths, compressedPaths);
|
|
86553
|
+
if (lostPaths.length > 0) errors.push(`Paths missing from the rewrite: ${quoteList(lostPaths)}.`);
|
|
86554
|
+
const addedPaths = addedTo(originalPaths, compressedPaths);
|
|
86555
|
+
if (addedPaths.length > 0) warnings.push(`Paths the original did not have: ${quoteList(addedPaths)}.`);
|
|
86556
|
+
const originalInline = inlineCodeCounts(original);
|
|
86557
|
+
const compressedInline = inlineCodeCounts(compressed);
|
|
86558
|
+
const lostInline = [...originalInline].filter(([span, count]) => (compressedInline.get(span) ?? 0) < count);
|
|
86559
|
+
if (lostInline.length > 0) errors.push(`Inline code spans missing from the rewrite: ${quoteList(lostInline.map(([span]) => span))}.`);
|
|
86560
|
+
const originalBullets = countOf(original, BULLET_REGEX);
|
|
86561
|
+
const compressedBullets = countOf(compressed, BULLET_REGEX);
|
|
86562
|
+
if (originalBullets > 0) {
|
|
86563
|
+
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.`);
|
|
86564
|
+
}
|
|
86565
|
+
return {
|
|
86566
|
+
errors,
|
|
86567
|
+
warnings
|
|
86568
|
+
};
|
|
86569
|
+
};
|
|
86570
|
+
/** Roughly how many tokens this text costs to read. Estimated, never measured. */
|
|
86571
|
+
const estimateTokens = (text) => Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
|
|
86572
|
+
/**
|
|
86573
|
+
* The name a backup of this file is kept under.
|
|
86574
|
+
*
|
|
86575
|
+
* Timestamped rather than fixed. Caveman refuses to compress when a backup
|
|
86576
|
+
* already exists, which protects the first original at the cost of blocking
|
|
86577
|
+
* every later compression; keeping one backup per write protects every version
|
|
86578
|
+
* instead, including the edits someone made between two compressions.
|
|
86579
|
+
*
|
|
86580
|
+
* The parent directory's name is carried in the path rather than the file name
|
|
86581
|
+
* so that `global-memory/feedback_svgl.md` and `svgl/SKILL.md` cannot collide.
|
|
86582
|
+
*/
|
|
86583
|
+
const backupFileName = (input) => `${input.fileName}.${input.atIso.replace(/[:.]/gu, "-")}.original`;
|
|
86584
|
+
//#endregion
|
|
86002
86585
|
//#region src/mcp/toolkits/threads/tools.ts
|
|
86003
86586
|
/**
|
|
86004
86587
|
* Self-scoped on purpose: neither tool takes a thread id. The thread acted on
|
|
@@ -86030,10 +86613,46 @@ const MemoryAppendTool = Tool.make("memory_append", {
|
|
|
86030
86613
|
Path.Path
|
|
86031
86614
|
]
|
|
86032
86615
|
}).annotate(Tool.Title, "Append to memory").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
86033
|
-
|
|
86616
|
+
/**
|
|
86617
|
+
* Overwrite an asset with a compressed version of itself, if it survives the
|
|
86618
|
+
* comparison.
|
|
86619
|
+
*
|
|
86620
|
+
* A tool rather than an instruction to use `Edit` because the guarantee is the
|
|
86621
|
+
* point: the original is copied out of every synced directory first, the
|
|
86622
|
+
* rewrite is compared against it, and a rewrite that lost a URL or a code block
|
|
86623
|
+
* is refused with the list of what it lost. An agent editing the file directly
|
|
86624
|
+
* can be asked to preserve those things but cannot be prevented from not.
|
|
86625
|
+
*/
|
|
86626
|
+
const AssetCompressTool = Tool.make("asset_compress", {
|
|
86627
|
+
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.",
|
|
86628
|
+
parameters: AssetCompressInput,
|
|
86629
|
+
success: AssetCompressResult,
|
|
86630
|
+
failure: ThreadControlToolError,
|
|
86631
|
+
dependencies: [
|
|
86632
|
+
McpInvocationContext,
|
|
86633
|
+
ServerConfig$1,
|
|
86634
|
+
ServerSettingsService,
|
|
86635
|
+
FileSystem.FileSystem,
|
|
86636
|
+
Path.Path
|
|
86637
|
+
]
|
|
86638
|
+
}).annotate(Tool.Title, "Compress an asset").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.Idempotent, false);
|
|
86639
|
+
const ThreadToolkit = Toolkit.make(ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
|
|
86034
86640
|
//#endregion
|
|
86035
86641
|
//#region src/mcp/toolkits/threads/handlers.ts
|
|
86036
86642
|
const DEFAULT_MEMORY_APPEND_TARGET = "CLAUDE.md";
|
|
86643
|
+
/**
|
|
86644
|
+
* Where originals are kept.
|
|
86645
|
+
*
|
|
86646
|
+
* Under the server's state directory, which is the one place on this machine
|
|
86647
|
+
* that is neither an asset root nor inside one. A backup next to the file would
|
|
86648
|
+
* be published by the next sync and re-read by the next agent turn, which is
|
|
86649
|
+
* the opposite of what compressing the file was for.
|
|
86650
|
+
*/
|
|
86651
|
+
const COMPRESS_BACKUP_DIRNAME = "asset-compress-backups";
|
|
86652
|
+
/** Prose only. A skill may ship scripts, and a rewritten script is a broken one. */
|
|
86653
|
+
const COMPRESSIBLE_EXTENSION = ".md";
|
|
86654
|
+
/** The same test the asset readers use: a NUL is what breaks a JSON text column. */
|
|
86655
|
+
const looksBinary = (content) => content.includes("\0");
|
|
86037
86656
|
const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
86038
86657
|
thread_rename: (input) => Effect.gen(function* () {
|
|
86039
86658
|
const invocation = yield* requireThreadCapability();
|
|
@@ -86083,6 +86702,68 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
86083
86702
|
file,
|
|
86084
86703
|
path: filePath
|
|
86085
86704
|
};
|
|
86705
|
+
}),
|
|
86706
|
+
asset_compress: (input) => Effect.gen(function* () {
|
|
86707
|
+
yield* requireThreadCapability();
|
|
86708
|
+
const path = yield* Path.Path;
|
|
86709
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
86710
|
+
const config = yield* ServerConfig$1;
|
|
86711
|
+
const claudeHome = { homePath: (yield* (yield* ServerSettingsService).getSettings.pipe(Effect.orElseSucceed(() => void 0)))?.providers.claudeAgent.homePath ?? "" };
|
|
86712
|
+
const refuse = (target, detail) => new AssetCompressRefusedError({
|
|
86713
|
+
path: target,
|
|
86714
|
+
detail
|
|
86715
|
+
});
|
|
86716
|
+
if (!path.isAbsolute(input.path)) return yield* refuse(input.path, "The path must be absolute.");
|
|
86717
|
+
const filePath = path.resolve(input.path);
|
|
86718
|
+
const fileName = path.basename(filePath);
|
|
86719
|
+
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.`);
|
|
86720
|
+
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.");
|
|
86721
|
+
const configDir = yield* resolveClaudeConfigDirPath(claudeHome, process.env);
|
|
86722
|
+
const skillsDir = yield* resolveClaudeUserSkillsDir(claudeHome);
|
|
86723
|
+
const agentsDir = yield* resolveClaudeUserAgentsDir(claudeHome);
|
|
86724
|
+
const within = (root) => {
|
|
86725
|
+
const relative = path.relative(root, filePath);
|
|
86726
|
+
return relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
86727
|
+
};
|
|
86728
|
+
const memoryDirectoryName = path.relative(configDir, filePath).split(path.sep)[0] ?? "";
|
|
86729
|
+
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 ")}.`);
|
|
86730
|
+
const original = yield* fileSystem.readFileString(filePath).pipe(Effect.orElseSucceed(() => void 0));
|
|
86731
|
+
if (original === void 0) return yield* refuse(filePath, "The file could not be read.");
|
|
86732
|
+
if (looksBinary(original)) return yield* refuse(filePath, "The file is not text.");
|
|
86733
|
+
if (original.trim().length === 0) return yield* refuse(filePath, "The file is empty.");
|
|
86734
|
+
const compressed = input.content;
|
|
86735
|
+
if (compressed.trim().length === 0) return yield* refuse(filePath, "The rewrite is blank.");
|
|
86736
|
+
if (compressed.trim() === original.trim()) return yield* refuse(filePath, "The rewrite is the file that is already there. Nothing was written.");
|
|
86737
|
+
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.`);
|
|
86738
|
+
const validation = validateCompression(original, compressed);
|
|
86739
|
+
if (validation.errors.length > 0) return yield* new AssetCompressValidationFailedError({
|
|
86740
|
+
path: filePath,
|
|
86741
|
+
errors: validation.errors
|
|
86742
|
+
});
|
|
86743
|
+
const backupPath = path.join(config.stateDir, COMPRESS_BACKUP_DIRNAME, path.basename(path.dirname(filePath)), backupFileName({
|
|
86744
|
+
fileName,
|
|
86745
|
+
atIso: DateTime.formatIso(yield* DateTime.now)
|
|
86746
|
+
}));
|
|
86747
|
+
if (((yield* writeFileStringAtomically({
|
|
86748
|
+
filePath: backupPath,
|
|
86749
|
+
contents: original
|
|
86750
|
+
}).pipe(Effect.as(true), Effect.orElseSucceed(() => false))) ? yield* fileSystem.readFileString(backupPath).pipe(Effect.orElseSucceed(() => void 0)) : void 0) !== original) {
|
|
86751
|
+
yield* fileSystem.remove(backupPath, { force: true }).pipe(Effect.ignore);
|
|
86752
|
+
return yield* refuse(filePath, `The original could not be backed up to '${backupPath}', so it was left as it is.`);
|
|
86753
|
+
}
|
|
86754
|
+
yield* writeFileStringAtomically({
|
|
86755
|
+
filePath,
|
|
86756
|
+
contents: compressed
|
|
86757
|
+
}).pipe(Effect.mapError((cause) => refuse(filePath, `The write failed: ${cause.message}. The original is at '${backupPath}'.`)));
|
|
86758
|
+
return {
|
|
86759
|
+
path: filePath,
|
|
86760
|
+
backupPath,
|
|
86761
|
+
charsBefore: original.length,
|
|
86762
|
+
charsAfter: compressed.length,
|
|
86763
|
+
estimatedTokensBefore: estimateTokens(original),
|
|
86764
|
+
estimatedTokensAfter: estimateTokens(compressed),
|
|
86765
|
+
warnings: validation.warnings
|
|
86766
|
+
};
|
|
86086
86767
|
})
|
|
86087
86768
|
});
|
|
86088
86769
|
//#endregion
|
|
@@ -87666,7 +88347,8 @@ const make$2 = Effect.gen(function* () {
|
|
|
87666
88347
|
modelSelection: desiredModelSelection,
|
|
87667
88348
|
...input?.resumeCursor !== void 0 ? { resumeCursor: input.resumeCursor } : {},
|
|
87668
88349
|
runtimeMode: desiredRuntimeMode,
|
|
87669
|
-
compressMode: thread.compressMode
|
|
88350
|
+
compressMode: thread.compressMode,
|
|
88351
|
+
unpromptedSubagents: thread.unpromptedSubagents
|
|
87670
88352
|
});
|
|
87671
88353
|
const bindSessionToThread = (session) => Effect.gen(function* () {
|
|
87672
88354
|
if (session.providerInstanceId === void 0) return yield* new ProviderAdapterRequestError({
|
|
@@ -87768,7 +88450,8 @@ const make$2 = Effect.gen(function* () {
|
|
|
87768
88450
|
...normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {},
|
|
87769
88451
|
...modelForTurn !== void 0 ? { modelSelection: modelForTurn } : {},
|
|
87770
88452
|
...input.interactionMode !== void 0 ? { interactionMode: input.interactionMode } : {},
|
|
87771
|
-
compressMode
|
|
88453
|
+
compressMode,
|
|
88454
|
+
unpromptedSubagents: thread.unpromptedSubagents
|
|
87772
88455
|
};
|
|
87773
88456
|
});
|
|
87774
88457
|
const maybeGenerateAndRenameWorktreeBranchForFirstTurn = Effect.fn("maybeGenerateAndRenameWorktreeBranchForFirstTurn")(function* (input) {
|