@p4code/cli 0.1.20 → 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 +561 -37
- package/dist/client/assets/{DiffPanel-Di8WylWU.js → DiffPanel-2Gqo7b0N.js} +3 -3
- package/dist/client/assets/{FilePreviewPanel-CSqqsbYX.js → FilePreviewPanel-Po3D7bHa.js} +5 -5
- package/dist/client/assets/PreviewPanel-ebSBxbsf.js +2 -0
- package/dist/client/assets/{angular-html-CPSNyy71.js → angular-html-BhHAxMde.js} +2 -2
- package/dist/client/assets/{angular-ts-BgndqOdx.js → angular-ts-CxZFyQM4.js} +2 -2
- package/dist/client/assets/{apl-CFWK0bta.js → apl-D8rOr4fH.js} +2 -2
- package/dist/client/assets/arrow-right-Dseaz2v4.js +2 -0
- package/dist/client/assets/{astro-D2uzatlp.js → astro-DYSYdOU9.js} +2 -2
- package/dist/client/assets/{blade-DfrK_8z3.js → blade-DaHnYPa_.js} +2 -2
- package/dist/client/assets/{c-DyORVYNJ.js → c-oRWOBveW.js} +2 -2
- package/dist/client/assets/{cobol-BUWePdBc.js → cobol-Djebq6RA.js} +2 -2
- package/dist/client/assets/{coffee-Ba8BYHli.js → coffee-CKhxS88r.js} +2 -2
- package/dist/client/assets/{cpp-BX0iAGNA.js → cpp-BjVgWq1h.js} +2 -2
- package/dist/client/assets/{crystal-BDSdceoQ.js → crystal-DXzIaEKx.js} +2 -2
- package/dist/client/assets/{css-TKd0Sp86.js → css-7AulbBAq.js} +2 -2
- package/dist/client/assets/{edge-Bus7Aab8.js → edge-B3VgcX6t.js} +2 -2
- package/dist/client/assets/{elixir-BlT2g4EX.js → elixir-DGUU7Wwf.js} +2 -2
- package/dist/client/assets/{elm-DTGJZ36e.js → elm-B9tr8qBJ.js} +2 -2
- package/dist/client/assets/{erb-BF4yZGxQ.js → erb-BgSl2c_f.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-C5BlQ5s_.js → fileCommentAnnotations-C06ZwrTh.js} +2 -2
- package/dist/client/assets/{git-rebase-GAe0_ZI9.js → git-rebase-CJMhrhmS.js} +2 -2
- package/dist/client/assets/{glimmer-js-GWmPSTNt.js → glimmer-js-Dzf5VMSx.js} +2 -2
- package/dist/client/assets/{glimmer-ts-CDLqKTZ9.js → glimmer-ts-CHD8gl5-.js} +2 -2
- package/dist/client/assets/{glsl-Bv3E3r0O.js → glsl-CDpKClnF.js} +2 -2
- package/dist/client/assets/{graphql-B5-LupiK.js → graphql-DX6Ul3G5.js} +2 -2
- package/dist/client/assets/{hack-KTQ3DPKm.js → hack-NU-7p0gC.js} +2 -2
- package/dist/client/assets/{haml-BvC-52XY.js → haml-Cf7Y-ZgP.js} +2 -2
- package/dist/client/assets/{handlebars-CK9WCq20.js → handlebars-BOvzNDfr.js} +2 -2
- package/dist/client/assets/{html-BGjxg0hO.js → html-CokKN6Uo.js} +2 -2
- package/dist/client/assets/{html-derivative-C0EaHUuc.js → html-derivative-CcTOncd6.js} +2 -2
- package/dist/client/assets/{http-hvxfwUee.js → http-CGr88I6S.js} +2 -2
- package/dist/client/assets/{hurl-D5cuiBPB.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-DOkofowB.js → java-DjcrYPKU.js} +2 -2
- package/dist/client/assets/{javascript-dCesbfJ7.js → javascript-C1msiELJ.js} +2 -2
- package/dist/client/assets/{jinja-BDrVqT_2.js → jinja-BP-z74Sl.js} +2 -2
- package/dist/client/assets/{jison-Dvzh0XRq.js → jison-DTU_ib6_.js} +2 -2
- package/dist/client/assets/{json-CpV_dVb2.js → json-n2oR8huX.js} +2 -2
- package/dist/client/assets/{jsx-CbaKC498.js → jsx-DLc8ZGPF.js} +2 -2
- package/dist/client/assets/{julia-BKYdI_5W.js → julia-BFtTfktg.js} +2 -2
- package/dist/client/assets/{just-FrVMEGvg.js → just-CBZfmiGm.js} +2 -2
- package/dist/client/assets/{latex-BSmwt_7j.js → latex-B9jcgQKp.js} +2 -2
- package/dist/client/assets/{liquid-Bb2nFp-U.js → liquid-D-PCb97a.js} +2 -2
- package/dist/client/assets/{lua-CEANnQ2c.js → lua-CeAbwjz7.js} +2 -2
- package/dist/client/assets/{marko-CjJZ1eHg.js → marko-D2oCpzm8.js} +2 -2
- package/dist/client/assets/{mdc-B3VSAYVT.js → mdc-BN4ff7dr.js} +2 -2
- package/dist/client/assets/{nginx-BkYJUm4E.js → nginx-TigpgX3q.js} +2 -2
- package/dist/client/assets/{nim-D8xT6_xt.js → nim-BbkIxZP5.js} +2 -2
- package/dist/client/assets/{perl-cazo7YHC.js → perl-Dq3UNlmA.js} +2 -2
- package/dist/client/assets/{php-C0LwuOo3.js → php-CDBbIDUr.js} +2 -2
- package/dist/client/assets/{pug-OP6SwNFw.js → pug-X1om8_0C.js} +2 -2
- package/dist/client/assets/{qml-C1-WQIQ-.js → qml-C_aweCjl.js} +2 -2
- package/dist/client/assets/{r-PkhwsIgx.js → r-BH02pZo7.js} +2 -2
- package/dist/client/assets/{razor-WONkd3aR.js → razor-DFd9duVt.js} +2 -2
- package/dist/client/assets/{regexp-SDnpa4md.js → regexp-BYgWQcZZ.js} +2 -2
- package/dist/client/assets/{rst-DulobrTh.js → rst-DS4ajG1_.js} +2 -2
- package/dist/client/assets/{ruby-RYHm8H3x.js → ruby-rrP3E-yX.js} +2 -2
- package/dist/client/assets/{sas-BBwb67VZ.js → sas-qOp3AbMp.js} +2 -2
- package/dist/client/assets/{scss-y2IZtjKJ.js → scss-BePdknOf.js} +2 -2
- package/dist/client/assets/{shellscript-B3vxtpJZ.js → shellscript-Di7DOIUv.js} +2 -2
- package/dist/client/assets/{shellsession-CvHfy0TS.js → shellsession-DS-qEJoT.js} +2 -2
- package/dist/client/assets/{soy-DWpn3a9x.js → soy-CT_pCK5o.js} +2 -2
- package/dist/client/assets/{sql-CCfTsIvj.js → sql-CVynNG6j.js} +2 -2
- package/dist/client/assets/{stata-Dr5CN-Tp.js → stata-B2fKxQHN.js} +2 -2
- package/dist/client/assets/{surrealql-DgzXJo35.js → surrealql-BOcosx9e.js} +2 -2
- package/dist/client/assets/{svelte-JQb5PCjP.js → svelte-BQBQ3ysY.js} +2 -2
- package/dist/client/assets/{templ-DQt5AsQ7.js → templ-cinsfbYE.js} +2 -2
- package/dist/client/assets/{tex-B8Qokwwv.js → tex-PiJ0CI8J.js} +2 -2
- package/dist/client/assets/textarea-DV6gvhvo.js +46 -0
- package/dist/client/assets/{ts-tags-B-RrSBzw.js → ts-tags-jRjqE2jk.js} +2 -2
- package/dist/client/assets/{tsx-C7Q0nn_B.js → tsx-DqzTbomV.js} +2 -2
- package/dist/client/assets/{twig-CzVGXsz7.js → twig-5U1Vd9zl.js} +2 -2
- package/dist/client/assets/{typescript-irwEvPgw.js → typescript-DmYNCX8k.js} +2 -2
- package/dist/client/assets/{vue-BE-nMa-3.js → vue-BOE7yHne.js} +2 -2
- package/dist/client/assets/{vue-html-SXHQOmAo.js → vue-html-roHe5KnN.js} +2 -2
- package/dist/client/assets/{vue-vine-D62pnm9I.js → vue-vine-YC-R0SEx.js} +2 -2
- package/dist/client/assets/{xml-BnssPo1i.js → xml-B-QQaugn.js} +2 -2
- package/dist/client/assets/{xsl-Dg7cN1LO.js → xsl-CyPKxPW7.js} +2 -2
- package/dist/client/assets/{yaml-BAeQKrXU.js → yaml-Bq2pe1E4.js} +2 -2
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/PreviewPanel-BcRUQSMy.js +0 -2
- package/dist/client/assets/arrow-right-wbRntSmq.js +0 -2
- package/dist/client/assets/index-Dol3ssFp.css +0 -1
- package/dist/client/assets/index-QC2FhlVn.js +0 -836
- package/dist/client/assets/textarea-BlxAoGJe.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
|
/**
|
|
@@ -7889,18 +7889,35 @@ var TicketResolveError = class extends Schema$1.TaggedErrorClass()("TicketResolv
|
|
|
7889
7889
|
//#endregion
|
|
7890
7890
|
//#region ../../packages/contracts/src/threadControl.ts
|
|
7891
7891
|
/**
|
|
7892
|
-
* Thread control over MCP - the
|
|
7893
|
-
* the
|
|
7894
|
-
*
|
|
7895
|
-
*
|
|
7896
|
-
*
|
|
7897
|
-
*
|
|
7898
|
-
*
|
|
7899
|
-
*
|
|
7900
|
-
*
|
|
7901
|
-
*
|
|
7902
|
-
*
|
|
7903
|
-
*
|
|
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.
|
|
7904
7921
|
*
|
|
7905
7922
|
* Quick-append to memory lives here too: it is the other thing an agent wants
|
|
7906
7923
|
* to do *from* a thread, and it shares the toolkit's capability and scope.
|
|
@@ -7918,6 +7935,64 @@ const ThreadRenameResult = Schema$1.Struct({
|
|
|
7918
7935
|
title: TrimmedNonEmptyString
|
|
7919
7936
|
});
|
|
7920
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
|
+
/**
|
|
7921
7996
|
* Which memory file an append lands in.
|
|
7922
7997
|
*
|
|
7923
7998
|
* A closed union rather than a filename, because the target directory also
|
|
@@ -8012,8 +8087,42 @@ var AssetCompressValidationFailedError = class extends Schema$1.TaggedErrorClass
|
|
|
8012
8087
|
return `The compressed ${this.path} did not validate, so nothing was written: ${this.errors.join(" ")}`;
|
|
8013
8088
|
}
|
|
8014
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
|
+
};
|
|
8015
8121
|
const ThreadControlToolError = Schema$1.Union([
|
|
8016
8122
|
ThreadToolUnavailableError,
|
|
8123
|
+
ThreadControlNotPermittedError,
|
|
8124
|
+
ThreadSpawnNotPermittedError,
|
|
8125
|
+
ThreadControlRejectedError,
|
|
8017
8126
|
ThreadRenameRejectedError,
|
|
8018
8127
|
MemoryAppendFailedError,
|
|
8019
8128
|
AssetCompressRefusedError,
|
|
@@ -14929,6 +15038,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14929
15038
|
...input.patch.projectId !== void 0 ? { projectId: input.patch.projectId } : {},
|
|
14930
15039
|
...input.patch.repositoryKey !== void 0 ? { repositoryKey: input.patch.repositoryKey } : {},
|
|
14931
15040
|
...input.patch.threadId !== void 0 ? { threadId: input.patch.threadId } : {},
|
|
15041
|
+
...input.patch.externalRef !== void 0 ? { externalRef: input.patch.externalRef } : {},
|
|
14932
15042
|
...input.patch.parentTaskId !== void 0 ? { parentTaskId: input.patch.parentTaskId } : {},
|
|
14933
15043
|
...input.patch.title !== void 0 ? { title: input.patch.title } : {},
|
|
14934
15044
|
...input.patch.body !== void 0 ? { body: input.patch.body } : {},
|
|
@@ -18870,6 +18980,16 @@ function compressTurnReminderFor(mode) {
|
|
|
18870
18980
|
*/
|
|
18871
18981
|
const PRESET_MAX_RESULT_ROWS = 20;
|
|
18872
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
|
+
/**
|
|
18873
18993
|
* Named `p4-*` on purpose.
|
|
18874
18994
|
*
|
|
18875
18995
|
* The directory is shared with whatever the user has written by hand, and
|
|
@@ -18899,6 +19019,34 @@ names a path that is not there, answer with the BLOCKED line below. Guessing at
|
|
|
18899
19019
|
the missing half of a request produces an answer that reads as confident and is
|
|
18900
19020
|
not checkable, which is worse than no answer.`;
|
|
18901
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
|
+
/**
|
|
18902
19050
|
* What every preset says about the answer.
|
|
18903
19051
|
*
|
|
18904
19052
|
* The two sentinels matter as much as the table. Without `NONE` a clean run
|
|
@@ -18917,7 +19065,8 @@ const outputContract = (row, extra) => [
|
|
|
18917
19065
|
` ${row}`,
|
|
18918
19066
|
"",
|
|
18919
19067
|
"Nothing to report: the single word NONE.",
|
|
18920
|
-
"Request insufficient
|
|
19068
|
+
"Request insufficient, or the budget spent before anything was verified:",
|
|
19069
|
+
"BLOCKED - <what is missing>, on one line.",
|
|
18921
19070
|
"",
|
|
18922
19071
|
"Paths are repository-relative and carry a line number. A row without one is",
|
|
18923
19072
|
"a row you have not verified: open the file and get it.",
|
|
@@ -18929,6 +19078,11 @@ const outputContract = (row, extra) => [
|
|
|
18929
19078
|
* The compression ruleset goes last so the contract above it is read first.
|
|
18930
19079
|
* Both are instructions to the same model and neither overrides the other:
|
|
18931
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.
|
|
18932
19086
|
*/
|
|
18933
19087
|
const buildPreset = (input) => {
|
|
18934
19088
|
const name = `${PRESET_NAME_PREFIX}${input.name}`;
|
|
@@ -18937,6 +19091,7 @@ const buildPreset = (input) => {
|
|
|
18937
19091
|
`name: ${name}`,
|
|
18938
19092
|
`description: ${JSON.stringify(input.description)}`,
|
|
18939
19093
|
`tools: ${input.tools}`,
|
|
19094
|
+
...input.model === void 0 ? [] : [`model: ${input.model}`],
|
|
18940
19095
|
"---",
|
|
18941
19096
|
"",
|
|
18942
19097
|
input.body.trim(),
|
|
@@ -18959,12 +19114,15 @@ const AGENT_PRESETS = [buildPreset({
|
|
|
18959
19114
|
name: "investigator",
|
|
18960
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.",
|
|
18961
19116
|
tools: "Read, Grep, Glob, Bash",
|
|
19117
|
+
model: "sonnet",
|
|
18962
19118
|
summary: "Locates code and answers with a path:line table.",
|
|
18963
19119
|
body: `You locate code. You do not change it, and you say no more about any location
|
|
18964
19120
|
than the one clause that explains why it is in the list.
|
|
18965
19121
|
|
|
18966
19122
|
${INPUT_CONTRACT}
|
|
18967
19123
|
|
|
19124
|
+
${WORK_BUDGET}
|
|
19125
|
+
|
|
18968
19126
|
${outputContract("path:line - symbol - one clause on why it matters", [
|
|
18969
19127
|
"",
|
|
18970
19128
|
"Symbol is the function, type or constant the line belongs to, not a paraphrase",
|
|
@@ -18982,6 +19140,8 @@ approval is a list that has to be re-read to find the three rows that matter.
|
|
|
18982
19140
|
|
|
18983
19141
|
${INPUT_CONTRACT}
|
|
18984
19142
|
|
|
19143
|
+
${WORK_BUDGET}
|
|
19144
|
+
|
|
18985
19145
|
${outputContract("path:line - severity - what is wrong, and what it breaks", [
|
|
18986
19146
|
"",
|
|
18987
19147
|
"Severity is one of three words:",
|
|
@@ -38572,6 +38732,38 @@ const requireThreadCapability = Effect.fn("mcp.requireThreadCapability")(functio
|
|
|
38572
38732
|
});
|
|
38573
38733
|
return invocation;
|
|
38574
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
|
+
});
|
|
38575
38767
|
//#endregion
|
|
38576
38768
|
//#region src/mcp/PreviewAutomationBroker.ts
|
|
38577
38769
|
var PreviewAutomationBroker = class extends Context.Service()("@p4code/cli/mcp/PreviewAutomationBroker") {};
|
|
@@ -46688,6 +46880,7 @@ const makeRemoteTaskRepository = Effect.fn("makeRemoteTaskRepository")(function*
|
|
|
46688
46880
|
...row.projectId === null ? {} : { projectId: row.projectId },
|
|
46689
46881
|
...row.repositoryKey === null ? {} : { repositoryKey: row.repositoryKey },
|
|
46690
46882
|
...row.threadId === null ? {} : { threadId: row.threadId },
|
|
46883
|
+
...row.externalRef === null ? {} : { externalRef: row.externalRef },
|
|
46691
46884
|
...row.parentTaskId === null ? {} : { parentTaskId: row.parentTaskId },
|
|
46692
46885
|
title: row.title,
|
|
46693
46886
|
body: row.body,
|
|
@@ -47576,7 +47769,10 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47576
47769
|
const crypto = yield* Crypto.Crypto;
|
|
47577
47770
|
const environmentId = yield* (yield* ServerEnvironment).getEnvironmentId;
|
|
47578
47771
|
const httpServer = yield* HttpServer.HttpServer;
|
|
47579
|
-
const state = yield* SynchronizedRef.make({
|
|
47772
|
+
const state = yield* SynchronizedRef.make({
|
|
47773
|
+
records: /* @__PURE__ */ new Map(),
|
|
47774
|
+
spawnedThreadIds: /* @__PURE__ */ new Set()
|
|
47775
|
+
});
|
|
47580
47776
|
const currentTimeMillis = options.now ? Effect.sync(options.now) : Clock.currentTimeMillis;
|
|
47581
47777
|
const livenessWindowMs = options.livenessWindowMs ?? DEFAULT_LIVENESS_WINDOW_MS;
|
|
47582
47778
|
const endpoint = httpServer.address._tag === "TcpAddress" ? `http://${getHttpMcpEndpointHost(httpServer.address.hostname)}:${httpServer.address.port}/mcp` : "http://127.0.0.1/mcp";
|
|
@@ -47597,29 +47793,35 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47597
47793
|
"threads"
|
|
47598
47794
|
]);
|
|
47599
47795
|
if (watchThreadIds.size > 0) capabilities.add("watch");
|
|
47600
|
-
const
|
|
47796
|
+
const threadId = ThreadId.make(request.threadId);
|
|
47797
|
+
const scopeWith = (mayCreateThreads) => ({
|
|
47601
47798
|
environmentId,
|
|
47602
|
-
threadId
|
|
47799
|
+
threadId,
|
|
47603
47800
|
providerSessionId,
|
|
47604
47801
|
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
47605
47802
|
capabilities,
|
|
47606
47803
|
...watchThreadIds.size > 0 ? { watchThreadIds } : {},
|
|
47804
|
+
mayCreateThreads,
|
|
47607
47805
|
issuedAt
|
|
47608
|
-
};
|
|
47609
|
-
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
47806
|
+
});
|
|
47807
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47610
47808
|
const next = new Map(pruneDead(records, issuedAt));
|
|
47611
47809
|
next.set(tokenHash, {
|
|
47612
47810
|
tokenHash,
|
|
47613
|
-
scope,
|
|
47811
|
+
scope: scopeWith(!spawnedThreadIds.has(threadId)),
|
|
47812
|
+
controlThreadIds: /* @__PURE__ */ new Set(),
|
|
47614
47813
|
lastAliveAt: issuedAt
|
|
47615
47814
|
});
|
|
47616
|
-
return {
|
|
47815
|
+
return {
|
|
47816
|
+
records: next,
|
|
47817
|
+
spawnedThreadIds
|
|
47818
|
+
};
|
|
47617
47819
|
});
|
|
47618
47820
|
return { config: {
|
|
47619
47821
|
environmentId,
|
|
47620
|
-
threadId
|
|
47822
|
+
threadId,
|
|
47621
47823
|
providerSessionId,
|
|
47622
|
-
providerInstanceId:
|
|
47824
|
+
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
47623
47825
|
endpoint,
|
|
47624
47826
|
authorizationHeader: `Bearer ${rawToken}`
|
|
47625
47827
|
} };
|
|
@@ -47628,42 +47830,76 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47628
47830
|
if (rawToken.length === 0) return void 0;
|
|
47629
47831
|
const tokenHash = yield* hashToken(rawToken);
|
|
47630
47832
|
const timestamp = yield* currentTimeMillis;
|
|
47631
|
-
return yield* SynchronizedRef.modify(state, ({ records }) => {
|
|
47833
|
+
return yield* SynchronizedRef.modify(state, ({ records, spawnedThreadIds }) => {
|
|
47632
47834
|
const current = pruneDead(records, timestamp);
|
|
47633
47835
|
const record = current.get(tokenHash);
|
|
47634
|
-
if (!record) return [void 0, {
|
|
47836
|
+
if (!record) return [void 0, {
|
|
47837
|
+
records: current,
|
|
47838
|
+
spawnedThreadIds
|
|
47839
|
+
}];
|
|
47635
47840
|
const next = new Map(current);
|
|
47636
47841
|
next.set(tokenHash, {
|
|
47637
47842
|
...record,
|
|
47638
47843
|
lastAliveAt: timestamp
|
|
47639
47844
|
});
|
|
47640
|
-
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
|
+
}];
|
|
47641
47854
|
});
|
|
47642
47855
|
});
|
|
47643
47856
|
const touch = Effect.fn("McpSessionRegistry.touch")(function* (threadId) {
|
|
47644
47857
|
const timestamp = yield* currentTimeMillis;
|
|
47645
|
-
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
47858
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47646
47859
|
const current = pruneDead(records, timestamp);
|
|
47647
47860
|
const next = new Map(current);
|
|
47648
47861
|
for (const [tokenHash, record] of current) if (record.scope.threadId === threadId) next.set(tokenHash, {
|
|
47649
47862
|
...record,
|
|
47650
47863
|
lastAliveAt: timestamp
|
|
47651
47864
|
});
|
|
47652
|
-
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
|
+
};
|
|
47653
47882
|
});
|
|
47654
47883
|
});
|
|
47655
|
-
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
|
+
}));
|
|
47656
47888
|
return McpSessionRegistry.of({
|
|
47657
47889
|
issue,
|
|
47658
47890
|
resolve,
|
|
47659
47891
|
touch,
|
|
47892
|
+
recordSpawnedThread,
|
|
47660
47893
|
revokeProviderSession: Effect.fn("McpSessionRegistry.revokeProviderSession")(function* (providerSessionId) {
|
|
47661
47894
|
yield* revokeWhere((record) => record.scope.providerSessionId === providerSessionId);
|
|
47662
47895
|
}),
|
|
47663
47896
|
revokeThread: Effect.fn("McpSessionRegistry.revokeThread")(function* (threadId) {
|
|
47664
47897
|
yield* revokeWhere((record) => record.scope.threadId === threadId);
|
|
47665
47898
|
}),
|
|
47666
|
-
revokeAll: SynchronizedRef.
|
|
47899
|
+
revokeAll: SynchronizedRef.update(state, ({ spawnedThreadIds }) => ({
|
|
47900
|
+
records: /* @__PURE__ */ new Map(),
|
|
47901
|
+
spawnedThreadIds
|
|
47902
|
+
}))
|
|
47667
47903
|
});
|
|
47668
47904
|
});
|
|
47669
47905
|
let activeMcpSessionRegistry;
|
|
@@ -86346,6 +86582,35 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
86346
86582
|
})
|
|
86347
86583
|
});
|
|
86348
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
|
|
86349
86614
|
//#region src/sync/assetCompression.ts
|
|
86350
86615
|
/**
|
|
86351
86616
|
* Checking that a compressed asset still says what the original said.
|
|
@@ -86584,12 +86849,28 @@ const backupFileName = (input) => `${input.fileName}.${input.atIso.replace(/[:.]
|
|
|
86584
86849
|
//#endregion
|
|
86585
86850
|
//#region src/mcp/toolkits/threads/tools.ts
|
|
86586
86851
|
/**
|
|
86587
|
-
*
|
|
86588
|
-
*
|
|
86589
|
-
*
|
|
86590
|
-
*
|
|
86591
|
-
*
|
|
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
|
|
86592
86872
|
*/
|
|
86873
|
+
/** Self-only, and staying that way: `thread_configure` is where a title on another thread is set. */
|
|
86593
86874
|
const ThreadRenameTool = Tool.make("thread_rename", {
|
|
86594
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.",
|
|
86595
86876
|
parameters: ThreadRenameInput,
|
|
@@ -86601,6 +86882,54 @@ const ThreadRenameTool = Tool.make("thread_rename", {
|
|
|
86601
86882
|
Crypto.Crypto
|
|
86602
86883
|
]
|
|
86603
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);
|
|
86604
86933
|
const MemoryAppendTool = Tool.make("memory_append", {
|
|
86605
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.",
|
|
86606
86935
|
parameters: MemoryAppendInput,
|
|
@@ -86636,7 +86965,7 @@ const AssetCompressTool = Tool.make("asset_compress", {
|
|
|
86636
86965
|
Path.Path
|
|
86637
86966
|
]
|
|
86638
86967
|
}).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);
|
|
86968
|
+
const ThreadToolkit = Toolkit.make(ThreadSpawnTool, ThreadConfigureTool, ThreadSettleTool, ThreadSnoozeTool, ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
|
|
86640
86969
|
//#endregion
|
|
86641
86970
|
//#region src/mcp/toolkits/threads/handlers.ts
|
|
86642
86971
|
const DEFAULT_MEMORY_APPEND_TARGET = "CLAUDE.md";
|
|
@@ -86653,7 +86982,181 @@ const COMPRESS_BACKUP_DIRNAME = "asset-compress-backups";
|
|
|
86653
86982
|
const COMPRESSIBLE_EXTENSION = ".md";
|
|
86654
86983
|
/** The same test the asset readers use: a NUL is what breaks a JSON text column. */
|
|
86655
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
|
+
});
|
|
86656
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
|
+
}),
|
|
86657
87160
|
thread_rename: (input) => Effect.gen(function* () {
|
|
86658
87161
|
const invocation = yield* requireThreadCapability();
|
|
86659
87162
|
const engine = yield* OrchestrationEngineService;
|
|
@@ -88214,13 +88717,34 @@ const make$2 = Effect.gen(function* () {
|
|
|
88214
88717
|
if (providerError) return providerError.detail;
|
|
88215
88718
|
return Cause.pretty(cause);
|
|
88216
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
|
+
});
|
|
88217
88741
|
const setThreadSession = (input) => serverCommandId("provider-session-set").pipe(Effect.flatMap((commandId) => orchestrationEngine.dispatch({
|
|
88218
88742
|
type: "thread.session.set",
|
|
88219
88743
|
commandId,
|
|
88220
88744
|
threadId: input.threadId,
|
|
88221
88745
|
session: input.session,
|
|
88222
88746
|
createdAt: input.createdAt
|
|
88223
|
-
})));
|
|
88747
|
+
})), Effect.tap(() => input.session.status === "stopped" || input.session.status === "error" ? flushQueuedSettle(input.threadId) : Effect.void));
|
|
88224
88748
|
const setThreadSessionErrorOnTurnStartFailure = Effect.fnUntraced(function* (input) {
|
|
88225
88749
|
const thread = yield* resolveThread(input.threadId);
|
|
88226
88750
|
if (!thread) return;
|