@p4code/cli 0.1.20 → 0.1.22
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 +789 -73
- package/dist/client/assets/{DiffPanel-Di8WylWU.js → DiffPanel-Cdf69_RS.js} +3 -3
- package/dist/client/assets/{FilePreviewPanel-CSqqsbYX.js → FilePreviewPanel-CkKXCbcG.js} +5 -5
- package/dist/client/assets/PreviewPanel-BtqdQqtN.js +2 -0
- package/dist/client/assets/{angular-html-CPSNyy71.js → angular-html-BPHFZwgm.js} +2 -2
- package/dist/client/assets/{angular-ts-BgndqOdx.js → angular-ts-CXoIeuGh.js} +2 -2
- package/dist/client/assets/{apl-CFWK0bta.js → apl-C6oPUNZZ.js} +2 -2
- package/dist/client/assets/arrow-right-7YX6fLbL.js +2 -0
- package/dist/client/assets/{astro-D2uzatlp.js → astro-Bq_n5uKv.js} +2 -2
- package/dist/client/assets/{blade-DfrK_8z3.js → blade-DYe65h_C.js} +2 -2
- package/dist/client/assets/{c-DyORVYNJ.js → c-BsDji_bt.js} +2 -2
- package/dist/client/assets/{cobol-BUWePdBc.js → cobol-CoC31Rpg.js} +2 -2
- package/dist/client/assets/{coffee-Ba8BYHli.js → coffee-DU1_O9cH.js} +2 -2
- package/dist/client/assets/{cpp-BX0iAGNA.js → cpp-D5_eYHZr.js} +2 -2
- package/dist/client/assets/{crystal-BDSdceoQ.js → crystal-3iIyvfhL.js} +2 -2
- package/dist/client/assets/{css-TKd0Sp86.js → css-B4bpFXEv.js} +2 -2
- package/dist/client/assets/{edge-Bus7Aab8.js → edge--YoYp8s_.js} +2 -2
- package/dist/client/assets/{elixir-BlT2g4EX.js → elixir-CZ1wqlQn.js} +2 -2
- package/dist/client/assets/{elm-DTGJZ36e.js → elm-DKdyYY92.js} +2 -2
- package/dist/client/assets/{erb-BF4yZGxQ.js → erb-PeUqy_9N.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-C5BlQ5s_.js → fileCommentAnnotations-BTb-0nj3.js} +2 -2
- package/dist/client/assets/{git-rebase-GAe0_ZI9.js → git-rebase-1Z5OxWid.js} +2 -2
- package/dist/client/assets/{glimmer-js-GWmPSTNt.js → glimmer-js-Br0y4u5b.js} +2 -2
- package/dist/client/assets/{glimmer-ts-CDLqKTZ9.js → glimmer-ts-Oh0AMNJv.js} +2 -2
- package/dist/client/assets/{glsl-Bv3E3r0O.js → glsl-DI6kM2gF.js} +2 -2
- package/dist/client/assets/{graphql-B5-LupiK.js → graphql-VCSJCQkl.js} +2 -2
- package/dist/client/assets/{hack-KTQ3DPKm.js → hack-4xJQMtbZ.js} +2 -2
- package/dist/client/assets/{haml-BvC-52XY.js → haml-Bc0kPVkr.js} +2 -2
- package/dist/client/assets/{handlebars-CK9WCq20.js → handlebars-Znb8TzWA.js} +2 -2
- package/dist/client/assets/{html-BGjxg0hO.js → html-_Zw71zQT.js} +2 -2
- package/dist/client/assets/{html-derivative-C0EaHUuc.js → html-derivative-D6Fn1mLE.js} +2 -2
- package/dist/client/assets/{http-hvxfwUee.js → http-CSdnm1Wh.js} +2 -2
- package/dist/client/assets/{hurl-D5cuiBPB.js → hurl-DKOuNoWT.js} +2 -2
- package/dist/client/assets/index-CeztlSXj.js +853 -0
- package/dist/client/assets/index-eSuLivhH.css +1 -0
- package/dist/client/assets/{java-DOkofowB.js → java-Czo4Dn1f.js} +2 -2
- package/dist/client/assets/{javascript-dCesbfJ7.js → javascript-fyReKHSS.js} +2 -2
- package/dist/client/assets/{jinja-BDrVqT_2.js → jinja-CvJhR5Z5.js} +2 -2
- package/dist/client/assets/{jison-Dvzh0XRq.js → jison-DSlj1sEU.js} +2 -2
- package/dist/client/assets/{json-CpV_dVb2.js → json-fCB7n2pX.js} +2 -2
- package/dist/client/assets/{jsx-CbaKC498.js → jsx-C1OJlXdb.js} +2 -2
- package/dist/client/assets/{julia-BKYdI_5W.js → julia-CmLSMgqX.js} +2 -2
- package/dist/client/assets/{just-FrVMEGvg.js → just-D9KdTXpD.js} +2 -2
- package/dist/client/assets/{latex-BSmwt_7j.js → latex-BZoE-Z3X.js} +2 -2
- package/dist/client/assets/{liquid-Bb2nFp-U.js → liquid-DW5UrT3Q.js} +2 -2
- package/dist/client/assets/{lua-CEANnQ2c.js → lua-DMAjig-8.js} +2 -2
- package/dist/client/assets/{marko-CjJZ1eHg.js → marko-EN2dMS-t.js} +2 -2
- package/dist/client/assets/{mdc-B3VSAYVT.js → mdc-DV5R0Zdp.js} +2 -2
- package/dist/client/assets/{nginx-BkYJUm4E.js → nginx-BhF2pPjp.js} +2 -2
- package/dist/client/assets/{nim-D8xT6_xt.js → nim-Ck0oR8VY.js} +2 -2
- package/dist/client/assets/{perl-cazo7YHC.js → perl-B4V1RSOt.js} +2 -2
- package/dist/client/assets/{php-C0LwuOo3.js → php-BvwSzqRH.js} +2 -2
- package/dist/client/assets/{pug-OP6SwNFw.js → pug-DB3guzOM.js} +2 -2
- package/dist/client/assets/{qml-C1-WQIQ-.js → qml-CohVRZGq.js} +2 -2
- package/dist/client/assets/{r-PkhwsIgx.js → r-Doi-hRR2.js} +2 -2
- package/dist/client/assets/{razor-WONkd3aR.js → razor-CtBV-RZa.js} +2 -2
- package/dist/client/assets/{regexp-SDnpa4md.js → regexp-DgF6jh6T.js} +2 -2
- package/dist/client/assets/{rst-DulobrTh.js → rst-1GlNPFb8.js} +2 -2
- package/dist/client/assets/{ruby-RYHm8H3x.js → ruby-C_Jg-W0j.js} +2 -2
- package/dist/client/assets/{sas-BBwb67VZ.js → sas-IRPqQRQC.js} +2 -2
- package/dist/client/assets/{scss-y2IZtjKJ.js → scss-BPKDbfld.js} +2 -2
- package/dist/client/assets/{shellscript-B3vxtpJZ.js → shellscript-D5KbVhsx.js} +2 -2
- package/dist/client/assets/{shellsession-CvHfy0TS.js → shellsession-bEniUyPe.js} +2 -2
- package/dist/client/assets/{soy-DWpn3a9x.js → soy-D0Pcy9LW.js} +2 -2
- package/dist/client/assets/{sql-CCfTsIvj.js → sql-DF2TL966.js} +2 -2
- package/dist/client/assets/{stata-Dr5CN-Tp.js → stata-BLI2BTCU.js} +2 -2
- package/dist/client/assets/{surrealql-DgzXJo35.js → surrealql-BjtO5tmi.js} +2 -2
- package/dist/client/assets/{svelte-JQb5PCjP.js → svelte-IyvKNOcX.js} +2 -2
- package/dist/client/assets/{templ-DQt5AsQ7.js → templ-qRJlnfHP.js} +2 -2
- package/dist/client/assets/{tex-B8Qokwwv.js → tex-CgYv5ALc.js} +2 -2
- package/dist/client/assets/textarea-B03Qe3NX.js +46 -0
- package/dist/client/assets/{ts-tags-B-RrSBzw.js → ts-tags-YasuuPCT.js} +2 -2
- package/dist/client/assets/{tsx-C7Q0nn_B.js → tsx-BF7eypTR.js} +2 -2
- package/dist/client/assets/{twig-CzVGXsz7.js → twig-BF-FIMTp.js} +2 -2
- package/dist/client/assets/{typescript-irwEvPgw.js → typescript-CtOBCMI7.js} +2 -2
- package/dist/client/assets/{vue-BE-nMa-3.js → vue-BTbCYZab.js} +2 -2
- package/dist/client/assets/{vue-html-SXHQOmAo.js → vue-html-1KQNz6IV.js} +2 -2
- package/dist/client/assets/{vue-vine-D62pnm9I.js → vue-vine-BVHOJ5oI.js} +2 -2
- package/dist/client/assets/{xml-BnssPo1i.js → xml-CftlzqvG.js} +2 -2
- package/dist/client/assets/{xsl-Dg7cN1LO.js → xsl-DoQ9dHRM.js} +2 -2
- package/dist/client/assets/{yaml-BAeQKrXU.js → yaml-DMUX1MAF.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.22";
|
|
237
237
|
//#endregion
|
|
238
238
|
//#region src/config.ts
|
|
239
239
|
/**
|
|
@@ -7858,11 +7858,21 @@ var ProjectWriteFileError = class extends Schema$1.TaggedErrorClass()("ProjectWr
|
|
|
7858
7858
|
* is a URL as often as an id, and which tracker a bare `MOBILE-12262` belongs
|
|
7859
7859
|
* to is a question only the resolver can answer.
|
|
7860
7860
|
*/
|
|
7861
|
-
const TicketResolveInput = Schema$1.Struct({ reference: TrimmedNonEmptyString });
|
|
7861
|
+
const TicketResolveInput = Schema$1.Struct({ reference: TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "The ticket as it was written: an identifier like MOBILE-12241, or a tracker URL. Pass it through verbatim rather than reformatting it." })) });
|
|
7862
7862
|
const TicketResolveResult = Schema$1.Struct({
|
|
7863
7863
|
externalRef: TaskExternalRef,
|
|
7864
7864
|
/** The issue title as the tracker reports it, for the task that mirrors it. */
|
|
7865
|
-
title: TrimmedNonEmptyString
|
|
7865
|
+
title: TrimmedNonEmptyString,
|
|
7866
|
+
/**
|
|
7867
|
+
* The issue body, when the tracker reports one.
|
|
7868
|
+
*
|
|
7869
|
+
* Optional because it is the one field here that has a legitimate empty
|
|
7870
|
+
* value - plenty of tickets are a title and nothing else - and because the
|
|
7871
|
+
* composer, which only ever renders a chip, does not read it. It is carried
|
|
7872
|
+
* for the caller that does: an agent handed a ticket id needs the
|
|
7873
|
+
* requirements, and the title alone sends it back to ask for them.
|
|
7874
|
+
*/
|
|
7875
|
+
description: Schema$1.optional(TrimmedNonEmptyString)
|
|
7866
7876
|
});
|
|
7867
7877
|
/**
|
|
7868
7878
|
* Why a reference could not be turned into a ticket.
|
|
@@ -7889,18 +7899,35 @@ var TicketResolveError = class extends Schema$1.TaggedErrorClass()("TicketResolv
|
|
|
7889
7899
|
//#endregion
|
|
7890
7900
|
//#region ../../packages/contracts/src/threadControl.ts
|
|
7891
7901
|
/**
|
|
7892
|
-
* Thread control over MCP - the
|
|
7893
|
-
* the
|
|
7894
|
-
*
|
|
7895
|
-
*
|
|
7896
|
-
*
|
|
7897
|
-
*
|
|
7898
|
-
*
|
|
7899
|
-
*
|
|
7900
|
-
*
|
|
7901
|
-
*
|
|
7902
|
-
*
|
|
7903
|
-
*
|
|
7902
|
+
* Thread control over MCP - the toolkit an agent uses to act on its own thread
|
|
7903
|
+
* and on the threads it started.
|
|
7904
|
+
*
|
|
7905
|
+
* **The scope rule.** A per-session bearer token is the only thing guarding
|
|
7906
|
+
* `/mcp` on a remote-reachable server, so reach is granted, never assumed. A
|
|
7907
|
+
* session may address its own thread and the threads it created through
|
|
7908
|
+
* `thread_spawn`, and nothing else. A leaked token therefore still reaches only
|
|
7909
|
+
* work that token itself began. The grant is held on the credential rather than
|
|
7910
|
+
* derived from the board, so it cannot widen because someone edited a task.
|
|
7911
|
+
*
|
|
7912
|
+
* **Delete is not exposed, and that is the decision rather than an omission.**
|
|
7913
|
+
* It is the one irreversible action here and a thread carries its whole
|
|
7914
|
+
* transcript. The task toolkit gave the same answer for the same reason, and a
|
|
7915
|
+
* thread is the more expensive thing to lose. An agent that wants a thread gone
|
|
7916
|
+
* settles it and says so; a person deletes it.
|
|
7917
|
+
*
|
|
7918
|
+
* **Spawning is one level deep.** `thread_spawn` seeds a first message and
|
|
7919
|
+
* starts the turn, because a create that cannot say anything is not the feature
|
|
7920
|
+
* anyone asked for. A thread that was itself spawned may not spawn: the depth
|
|
7921
|
+
* cap is what a runaway loop runs into, and one level is enough for "split this
|
|
7922
|
+
* task and start the pieces" while still being a structural bound rather than a
|
|
7923
|
+
* prompt asking nicely.
|
|
7924
|
+
*
|
|
7925
|
+
* **Settle is queued rather than refused.** The decider refuses `thread.settle`
|
|
7926
|
+
* while the target's session is starting or running, and an MCP call from a
|
|
7927
|
+
* thread's own session always arrives while that session is alive - so the
|
|
7928
|
+
* self-addressed case could never succeed as an immediate command. It is
|
|
7929
|
+
* recorded instead and applied when the session stops, which is what an agent
|
|
7930
|
+
* settling its own thread means anyway: finish, then close.
|
|
7904
7931
|
*
|
|
7905
7932
|
* Quick-append to memory lives here too: it is the other thing an agent wants
|
|
7906
7933
|
* to do *from* a thread, and it shares the toolkit's capability and scope.
|
|
@@ -7918,6 +7945,64 @@ const ThreadRenameResult = Schema$1.Struct({
|
|
|
7918
7945
|
title: TrimmedNonEmptyString
|
|
7919
7946
|
});
|
|
7920
7947
|
/**
|
|
7948
|
+
* Which thread a control tool acts on.
|
|
7949
|
+
*
|
|
7950
|
+
* Optional, and absent means the caller's own thread. That default is what
|
|
7951
|
+
* keeps the common case honest: an agent adjusting itself never has to name an
|
|
7952
|
+
* id, so it cannot name the wrong one, and every tool below reads the same way
|
|
7953
|
+
* whether or not it is addressed elsewhere.
|
|
7954
|
+
*/
|
|
7955
|
+
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." }));
|
|
7956
|
+
const ThreadSpawnInput = Schema$1.Struct({
|
|
7957
|
+
title: TrimmedNonEmptyString.annotate({ description: "Title for the new thread. Shown on the board and in the sidebar." }),
|
|
7958
|
+
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." }),
|
|
7959
|
+
projectId: Schema$1.optional(ProjectId.annotate({ description: "Project to start the thread in. Defaults to this session's own project." })),
|
|
7960
|
+
runtimeMode: Schema$1.optional(RuntimeMode.annotate({ description: "Permission mode for the new thread. Defaults to this session's own." })),
|
|
7961
|
+
interactionMode: Schema$1.optional(ProviderInteractionMode),
|
|
7962
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
7963
|
+
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean)
|
|
7964
|
+
});
|
|
7965
|
+
const ThreadSpawnResult = Schema$1.Struct({
|
|
7966
|
+
/** Watchable with `thread_watch_events`, and addressable by every tool here. */
|
|
7967
|
+
threadId: ThreadId,
|
|
7968
|
+
projectId: ProjectId,
|
|
7969
|
+
title: TrimmedNonEmptyString
|
|
7970
|
+
});
|
|
7971
|
+
const ThreadConfigureInput = Schema$1.Struct({
|
|
7972
|
+
threadId: ControlTargetThreadId,
|
|
7973
|
+
title: Schema$1.optional(TrimmedNonEmptyString),
|
|
7974
|
+
modelSelection: Schema$1.optional(ModelSelection),
|
|
7975
|
+
runtimeMode: Schema$1.optional(RuntimeMode),
|
|
7976
|
+
interactionMode: Schema$1.optional(ProviderInteractionMode),
|
|
7977
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
7978
|
+
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean)
|
|
7979
|
+
});
|
|
7980
|
+
const ThreadConfigureResult = Schema$1.Struct({
|
|
7981
|
+
threadId: ThreadId,
|
|
7982
|
+
/** The settings that were actually changed, in the order they were applied. */
|
|
7983
|
+
applied: Schema$1.Array(Schema$1.String)
|
|
7984
|
+
});
|
|
7985
|
+
const ThreadSettleInput = Schema$1.Struct({ threadId: ControlTargetThreadId });
|
|
7986
|
+
/**
|
|
7987
|
+
* Settled now, or recorded to settle when the session stops.
|
|
7988
|
+
*
|
|
7989
|
+
* Two outcomes rather than one because the difference is the agent's to know: a
|
|
7990
|
+
* queued settle has not happened yet, and a thread that goes on to do more work
|
|
7991
|
+
* still settles at the end of it.
|
|
7992
|
+
*/
|
|
7993
|
+
const ThreadSettleResult = Schema$1.Struct({
|
|
7994
|
+
threadId: ThreadId,
|
|
7995
|
+
outcome: Schema$1.Literals(["settled", "queued"])
|
|
7996
|
+
});
|
|
7997
|
+
const ThreadSnoozeInput = Schema$1.Struct({
|
|
7998
|
+
threadId: ControlTargetThreadId,
|
|
7999
|
+
snoozedUntil: IsoDateTime.annotate({ description: "When the thread should come back, as an ISO 8601 timestamp." })
|
|
8000
|
+
});
|
|
8001
|
+
const ThreadSnoozeResult = Schema$1.Struct({
|
|
8002
|
+
threadId: ThreadId,
|
|
8003
|
+
snoozedUntil: IsoDateTime
|
|
8004
|
+
});
|
|
8005
|
+
/**
|
|
7921
8006
|
* Which memory file an append lands in.
|
|
7922
8007
|
*
|
|
7923
8008
|
* A closed union rather than a filename, because the target directory also
|
|
@@ -8012,8 +8097,42 @@ var AssetCompressValidationFailedError = class extends Schema$1.TaggedErrorClass
|
|
|
8012
8097
|
return `The compressed ${this.path} did not validate, so nothing was written: ${this.errors.join(" ")}`;
|
|
8013
8098
|
}
|
|
8014
8099
|
};
|
|
8100
|
+
/**
|
|
8101
|
+
* The named thread is outside this session's grant.
|
|
8102
|
+
*
|
|
8103
|
+
* Deliberately the same answer whether the thread does not exist or exists and
|
|
8104
|
+
* belongs to someone else: a caller that could tell those apart could map the
|
|
8105
|
+
* whole board one id at a time.
|
|
8106
|
+
*/
|
|
8107
|
+
var ThreadControlNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadControlNotPermittedError", { threadId: ThreadId }) {
|
|
8108
|
+
get message() {
|
|
8109
|
+
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.`;
|
|
8110
|
+
}
|
|
8111
|
+
};
|
|
8112
|
+
/** A spawned thread asked to spawn. One level is the whole allowance. */
|
|
8113
|
+
var ThreadSpawnNotPermittedError = class extends Schema$1.TaggedErrorClass()("ThreadSpawnNotPermittedError", {
|
|
8114
|
+
threadId: ThreadId,
|
|
8115
|
+
detail: Schema$1.String
|
|
8116
|
+
}) {
|
|
8117
|
+
get message() {
|
|
8118
|
+
return `Thread ${this.threadId} cannot start another thread: ${this.detail}`;
|
|
8119
|
+
}
|
|
8120
|
+
};
|
|
8121
|
+
/** The orchestration engine declined or failed a thread control command. */
|
|
8122
|
+
var ThreadControlRejectedError = class extends Schema$1.TaggedErrorClass()("ThreadControlRejectedError", {
|
|
8123
|
+
threadId: ThreadId,
|
|
8124
|
+
commandType: Schema$1.String,
|
|
8125
|
+
detail: Schema$1.String
|
|
8126
|
+
}) {
|
|
8127
|
+
get message() {
|
|
8128
|
+
return `${this.commandType} on thread ${this.threadId} failed: ${this.detail}`;
|
|
8129
|
+
}
|
|
8130
|
+
};
|
|
8015
8131
|
const ThreadControlToolError = Schema$1.Union([
|
|
8016
8132
|
ThreadToolUnavailableError,
|
|
8133
|
+
ThreadControlNotPermittedError,
|
|
8134
|
+
ThreadSpawnNotPermittedError,
|
|
8135
|
+
ThreadControlRejectedError,
|
|
8017
8136
|
ThreadRenameRejectedError,
|
|
8018
8137
|
MemoryAppendFailedError,
|
|
8019
8138
|
AssetCompressRefusedError,
|
|
@@ -14929,6 +15048,7 @@ const makeTaskRepository = Effect.gen(function* () {
|
|
|
14929
15048
|
...input.patch.projectId !== void 0 ? { projectId: input.patch.projectId } : {},
|
|
14930
15049
|
...input.patch.repositoryKey !== void 0 ? { repositoryKey: input.patch.repositoryKey } : {},
|
|
14931
15050
|
...input.patch.threadId !== void 0 ? { threadId: input.patch.threadId } : {},
|
|
15051
|
+
...input.patch.externalRef !== void 0 ? { externalRef: input.patch.externalRef } : {},
|
|
14932
15052
|
...input.patch.parentTaskId !== void 0 ? { parentTaskId: input.patch.parentTaskId } : {},
|
|
14933
15053
|
...input.patch.title !== void 0 ? { title: input.patch.title } : {},
|
|
14934
15054
|
...input.patch.body !== void 0 ? { body: input.patch.body } : {},
|
|
@@ -18870,6 +18990,16 @@ function compressTurnReminderFor(mode) {
|
|
|
18870
18990
|
*/
|
|
18871
18991
|
const PRESET_MAX_RESULT_ROWS = 20;
|
|
18872
18992
|
/**
|
|
18993
|
+
* The tool calls a preset run is allowed.
|
|
18994
|
+
*
|
|
18995
|
+
* Fifteen because a located answer typically takes six to ten, and the runs
|
|
18996
|
+
* that overshoot do not overshoot slightly: measured against real transcripts,
|
|
18997
|
+
* the long tail sits between sixty and a hundred and thirty calls and carries
|
|
18998
|
+
* nearly all of the cost. A cap that a good run never reaches is a cap that only
|
|
18999
|
+
* ever bites the runs that had stopped making progress.
|
|
19000
|
+
*/
|
|
19001
|
+
const PRESET_MAX_TOOL_CALLS = 15;
|
|
19002
|
+
/**
|
|
18873
19003
|
* Named `p4-*` on purpose.
|
|
18874
19004
|
*
|
|
18875
19005
|
* The directory is shared with whatever the user has written by hand, and
|
|
@@ -18899,6 +19029,34 @@ names a path that is not there, answer with the BLOCKED line below. Guessing at
|
|
|
18899
19029
|
the missing half of a request produces an answer that reads as confident and is
|
|
18900
19030
|
not checkable, which is worse than no answer.`;
|
|
18901
19031
|
/**
|
|
19032
|
+
* What every preset says about the run in between.
|
|
19033
|
+
*
|
|
19034
|
+
* Three rules, each aimed at a measured failure. The cap bounds the quadratic.
|
|
19035
|
+
* The ban on nesting stops a bounded run from spawning an unbounded one, which
|
|
19036
|
+
* is the same bill again with nothing watching it. The stop-at-the-cap clause is
|
|
19037
|
+
* what makes the number safe to enforce: without somewhere to put a partial
|
|
19038
|
+
* answer, a capped subagent either grinds past the cap or throws away what it
|
|
19039
|
+
* had, and both are worse than a short table with a named gap.
|
|
19040
|
+
*/
|
|
19041
|
+
const WORK_BUDGET = `## What the run costs
|
|
19042
|
+
|
|
19043
|
+
At most ${PRESET_MAX_TOOL_CALLS} tool calls. Most answers of this kind take six
|
|
19044
|
+
to ten, so treat the cap as the point where something has gone wrong rather than
|
|
19045
|
+
as a target to spend.
|
|
19046
|
+
|
|
19047
|
+
Spend them on calls that can fail usefully: before each one, know what a hit and
|
|
19048
|
+
a miss would each tell you. Batch searches into one call separated by echoed
|
|
19049
|
+
markers rather than issuing them one at a time, bound every output with a head
|
|
19050
|
+
or a line count, and read a file only after a search has told you which lines
|
|
19051
|
+
matter, and read those lines. Never repeat a call you have already made.
|
|
19052
|
+
|
|
19053
|
+
Do not spawn a subagent of your own. You are the bounded run; one that starts
|
|
19054
|
+
another has spent the budget twice with nothing watching the second half.
|
|
19055
|
+
|
|
19056
|
+
Reaching the cap ends the run. Return the rows you have verified, and add a
|
|
19057
|
+
final line naming the search you would have run next. With no verified rows at
|
|
19058
|
+
all, that is the BLOCKED line.`;
|
|
19059
|
+
/**
|
|
18902
19060
|
* What every preset says about the answer.
|
|
18903
19061
|
*
|
|
18904
19062
|
* The two sentinels matter as much as the table. Without `NONE` a clean run
|
|
@@ -18917,7 +19075,8 @@ const outputContract = (row, extra) => [
|
|
|
18917
19075
|
` ${row}`,
|
|
18918
19076
|
"",
|
|
18919
19077
|
"Nothing to report: the single word NONE.",
|
|
18920
|
-
"Request insufficient
|
|
19078
|
+
"Request insufficient, or the budget spent before anything was verified:",
|
|
19079
|
+
"BLOCKED - <what is missing>, on one line.",
|
|
18921
19080
|
"",
|
|
18922
19081
|
"Paths are repository-relative and carry a line number. A row without one is",
|
|
18923
19082
|
"a row you have not verified: open the file and get it.",
|
|
@@ -18929,6 +19088,11 @@ const outputContract = (row, extra) => [
|
|
|
18929
19088
|
* The compression ruleset goes last so the contract above it is read first.
|
|
18930
19089
|
* Both are instructions to the same model and neither overrides the other:
|
|
18931
19090
|
* the contract fixes the shape, the ruleset fixes the prose inside it.
|
|
19091
|
+
*
|
|
19092
|
+
* `model` is optional, and omitting it inherits the session's. Naming a cheaper
|
|
19093
|
+
* tier is worth it only where the job is mechanical enough that the tier is not
|
|
19094
|
+
* what makes the answer right, which is true of locating a symbol and is not
|
|
19095
|
+
* true of deciding whether a line is a bug.
|
|
18932
19096
|
*/
|
|
18933
19097
|
const buildPreset = (input) => {
|
|
18934
19098
|
const name = `${PRESET_NAME_PREFIX}${input.name}`;
|
|
@@ -18937,6 +19101,7 @@ const buildPreset = (input) => {
|
|
|
18937
19101
|
`name: ${name}`,
|
|
18938
19102
|
`description: ${JSON.stringify(input.description)}`,
|
|
18939
19103
|
`tools: ${input.tools}`,
|
|
19104
|
+
...input.model === void 0 ? [] : [`model: ${input.model}`],
|
|
18940
19105
|
"---",
|
|
18941
19106
|
"",
|
|
18942
19107
|
input.body.trim(),
|
|
@@ -18959,12 +19124,15 @@ const AGENT_PRESETS = [buildPreset({
|
|
|
18959
19124
|
name: "investigator",
|
|
18960
19125
|
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
19126
|
tools: "Read, Grep, Glob, Bash",
|
|
19127
|
+
model: "sonnet",
|
|
18962
19128
|
summary: "Locates code and answers with a path:line table.",
|
|
18963
19129
|
body: `You locate code. You do not change it, and you say no more about any location
|
|
18964
19130
|
than the one clause that explains why it is in the list.
|
|
18965
19131
|
|
|
18966
19132
|
${INPUT_CONTRACT}
|
|
18967
19133
|
|
|
19134
|
+
${WORK_BUDGET}
|
|
19135
|
+
|
|
18968
19136
|
${outputContract("path:line - symbol - one clause on why it matters", [
|
|
18969
19137
|
"",
|
|
18970
19138
|
"Symbol is the function, type or constant the line belongs to, not a paraphrase",
|
|
@@ -18982,6 +19150,8 @@ approval is a list that has to be re-read to find the three rows that matter.
|
|
|
18982
19150
|
|
|
18983
19151
|
${INPUT_CONTRACT}
|
|
18984
19152
|
|
|
19153
|
+
${WORK_BUDGET}
|
|
19154
|
+
|
|
18985
19155
|
${outputContract("path:line - severity - what is wrong, and what it breaks", [
|
|
18986
19156
|
"",
|
|
18987
19157
|
"Severity is one of three words:",
|
|
@@ -34704,6 +34874,7 @@ const TASK_PLAN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
34704
34874
|
"task_get",
|
|
34705
34875
|
"task_current",
|
|
34706
34876
|
"task_propose",
|
|
34877
|
+
"ticket_resolve",
|
|
34707
34878
|
"taskcreate",
|
|
34708
34879
|
"taskupdate",
|
|
34709
34880
|
"tasklist",
|
|
@@ -36411,13 +36582,16 @@ const make$36 = Effect.gen(function* () {
|
|
|
36411
36582
|
reason: "not_found",
|
|
36412
36583
|
detail: `Linear returned nothing usable for ${identifier}.`
|
|
36413
36584
|
});
|
|
36585
|
+
const externalRef = {
|
|
36586
|
+
source: "linear",
|
|
36587
|
+
identifier: stringField$1(issue, "identifier") ?? identifier,
|
|
36588
|
+
url
|
|
36589
|
+
};
|
|
36590
|
+
const description = stringField$1(issue, "description");
|
|
36414
36591
|
return {
|
|
36415
|
-
externalRef
|
|
36416
|
-
|
|
36417
|
-
|
|
36418
|
-
url
|
|
36419
|
-
},
|
|
36420
|
-
title
|
|
36592
|
+
externalRef,
|
|
36593
|
+
title,
|
|
36594
|
+
...description === void 0 ? {} : { description }
|
|
36421
36595
|
};
|
|
36422
36596
|
}) };
|
|
36423
36597
|
});
|
|
@@ -38572,6 +38746,38 @@ const requireThreadCapability = Effect.fn("mcp.requireThreadCapability")(functio
|
|
|
38572
38746
|
});
|
|
38573
38747
|
return invocation;
|
|
38574
38748
|
});
|
|
38749
|
+
/**
|
|
38750
|
+
* The thread a control tool may act on, given what it was asked to act on.
|
|
38751
|
+
*
|
|
38752
|
+
* Two checks like `requireWatchCapability`, and for the same reason: the
|
|
38753
|
+
* capability says this session controls threads at all, the grant says which.
|
|
38754
|
+
* An omitted id resolves to the session's own thread, which is always in the
|
|
38755
|
+
* grant and is why the common call needs no id and so cannot name a wrong one.
|
|
38756
|
+
*/
|
|
38757
|
+
const requireThreadControlTarget = Effect.fn("mcp.requireThreadControlTarget")(function* (requested) {
|
|
38758
|
+
const invocation = yield* requireThreadCapability();
|
|
38759
|
+
if (requested === void 0 || requested === invocation.threadId) return {
|
|
38760
|
+
invocation,
|
|
38761
|
+
threadId: invocation.threadId
|
|
38762
|
+
};
|
|
38763
|
+
if (!invocation.controlThreadIds?.has(requested)) return yield* new ThreadControlNotPermittedError({ threadId: requested });
|
|
38764
|
+
return {
|
|
38765
|
+
invocation,
|
|
38766
|
+
threadId: requested
|
|
38767
|
+
};
|
|
38768
|
+
});
|
|
38769
|
+
/**
|
|
38770
|
+
* The guard on starting a thread, which is a different question from acting on
|
|
38771
|
+
* one: it is not "which thread" but "may this session make more of them".
|
|
38772
|
+
*/
|
|
38773
|
+
const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* () {
|
|
38774
|
+
const invocation = yield* requireThreadCapability();
|
|
38775
|
+
if (invocation.mayCreateThreads !== true) return yield* new ThreadSpawnNotPermittedError({
|
|
38776
|
+
threadId: invocation.threadId,
|
|
38777
|
+
detail: "this thread was itself started by an agent, and spawning goes one level deep so a runaway loop has a bound"
|
|
38778
|
+
});
|
|
38779
|
+
return invocation;
|
|
38780
|
+
});
|
|
38575
38781
|
//#endregion
|
|
38576
38782
|
//#region src/mcp/PreviewAutomationBroker.ts
|
|
38577
38783
|
var PreviewAutomationBroker = class extends Context.Service()("@p4code/cli/mcp/PreviewAutomationBroker") {};
|
|
@@ -46688,6 +46894,7 @@ const makeRemoteTaskRepository = Effect.fn("makeRemoteTaskRepository")(function*
|
|
|
46688
46894
|
...row.projectId === null ? {} : { projectId: row.projectId },
|
|
46689
46895
|
...row.repositoryKey === null ? {} : { repositoryKey: row.repositoryKey },
|
|
46690
46896
|
...row.threadId === null ? {} : { threadId: row.threadId },
|
|
46897
|
+
...row.externalRef === null ? {} : { externalRef: row.externalRef },
|
|
46691
46898
|
...row.parentTaskId === null ? {} : { parentTaskId: row.parentTaskId },
|
|
46692
46899
|
title: row.title,
|
|
46693
46900
|
body: row.body,
|
|
@@ -47534,6 +47741,18 @@ const ProviderEventLoggersLive = Layer.effect(ProviderEventLoggers, Effect.gen(f
|
|
|
47534
47741
|
var ProviderService = class extends Context.Service()("@p4code/cli/provider/Services/ProviderService") {};
|
|
47535
47742
|
//#endregion
|
|
47536
47743
|
//#region src/mcp/McpProviderSession.ts
|
|
47744
|
+
/**
|
|
47745
|
+
* The name p4code declares its own toolkit under, in every provider.
|
|
47746
|
+
*
|
|
47747
|
+
* One constant because it is one name in the agent's world: prompts, skills and
|
|
47748
|
+
* the tool categories all say `p4-code`, whichever runtime is reading them.
|
|
47749
|
+
* Every adapter also excludes it from the user's registered servers, so a
|
|
47750
|
+
* registration of the same name cannot displace the built-in one and take the
|
|
47751
|
+
* per-thread credential's tools (`task_current`, `thread_*`) with it.
|
|
47752
|
+
*/
|
|
47753
|
+
const P4CODE_MCP_SERVER_NAME = "p4-code";
|
|
47754
|
+
/** The reserved names, as the set the mappers take. */
|
|
47755
|
+
const P4CODE_MCP_SERVER_NAMES = /* @__PURE__ */ new Set([P4CODE_MCP_SERVER_NAME]);
|
|
47537
47756
|
const sessionsByThread = /* @__PURE__ */ new Map();
|
|
47538
47757
|
function setMcpProviderSession(config) {
|
|
47539
47758
|
sessionsByThread.set(config.threadId, config);
|
|
@@ -47576,7 +47795,10 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47576
47795
|
const crypto = yield* Crypto.Crypto;
|
|
47577
47796
|
const environmentId = yield* (yield* ServerEnvironment).getEnvironmentId;
|
|
47578
47797
|
const httpServer = yield* HttpServer.HttpServer;
|
|
47579
|
-
const state = yield* SynchronizedRef.make({
|
|
47798
|
+
const state = yield* SynchronizedRef.make({
|
|
47799
|
+
records: /* @__PURE__ */ new Map(),
|
|
47800
|
+
spawnedThreadIds: /* @__PURE__ */ new Set()
|
|
47801
|
+
});
|
|
47580
47802
|
const currentTimeMillis = options.now ? Effect.sync(options.now) : Clock.currentTimeMillis;
|
|
47581
47803
|
const livenessWindowMs = options.livenessWindowMs ?? DEFAULT_LIVENESS_WINDOW_MS;
|
|
47582
47804
|
const endpoint = httpServer.address._tag === "TcpAddress" ? `http://${getHttpMcpEndpointHost(httpServer.address.hostname)}:${httpServer.address.port}/mcp` : "http://127.0.0.1/mcp";
|
|
@@ -47597,29 +47819,35 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47597
47819
|
"threads"
|
|
47598
47820
|
]);
|
|
47599
47821
|
if (watchThreadIds.size > 0) capabilities.add("watch");
|
|
47600
|
-
const
|
|
47822
|
+
const threadId = ThreadId.make(request.threadId);
|
|
47823
|
+
const scopeWith = (mayCreateThreads) => ({
|
|
47601
47824
|
environmentId,
|
|
47602
|
-
threadId
|
|
47825
|
+
threadId,
|
|
47603
47826
|
providerSessionId,
|
|
47604
47827
|
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
47605
47828
|
capabilities,
|
|
47606
47829
|
...watchThreadIds.size > 0 ? { watchThreadIds } : {},
|
|
47830
|
+
mayCreateThreads,
|
|
47607
47831
|
issuedAt
|
|
47608
|
-
};
|
|
47609
|
-
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
47832
|
+
});
|
|
47833
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47610
47834
|
const next = new Map(pruneDead(records, issuedAt));
|
|
47611
47835
|
next.set(tokenHash, {
|
|
47612
47836
|
tokenHash,
|
|
47613
|
-
scope,
|
|
47837
|
+
scope: scopeWith(!spawnedThreadIds.has(threadId)),
|
|
47838
|
+
controlThreadIds: /* @__PURE__ */ new Set(),
|
|
47614
47839
|
lastAliveAt: issuedAt
|
|
47615
47840
|
});
|
|
47616
|
-
return {
|
|
47841
|
+
return {
|
|
47842
|
+
records: next,
|
|
47843
|
+
spawnedThreadIds
|
|
47844
|
+
};
|
|
47617
47845
|
});
|
|
47618
47846
|
return { config: {
|
|
47619
47847
|
environmentId,
|
|
47620
|
-
threadId
|
|
47848
|
+
threadId,
|
|
47621
47849
|
providerSessionId,
|
|
47622
|
-
providerInstanceId:
|
|
47850
|
+
providerInstanceId: ProviderInstanceId.make(request.providerInstanceId),
|
|
47623
47851
|
endpoint,
|
|
47624
47852
|
authorizationHeader: `Bearer ${rawToken}`
|
|
47625
47853
|
} };
|
|
@@ -47628,42 +47856,76 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
47628
47856
|
if (rawToken.length === 0) return void 0;
|
|
47629
47857
|
const tokenHash = yield* hashToken(rawToken);
|
|
47630
47858
|
const timestamp = yield* currentTimeMillis;
|
|
47631
|
-
return yield* SynchronizedRef.modify(state, ({ records }) => {
|
|
47859
|
+
return yield* SynchronizedRef.modify(state, ({ records, spawnedThreadIds }) => {
|
|
47632
47860
|
const current = pruneDead(records, timestamp);
|
|
47633
47861
|
const record = current.get(tokenHash);
|
|
47634
|
-
if (!record) return [void 0, {
|
|
47862
|
+
if (!record) return [void 0, {
|
|
47863
|
+
records: current,
|
|
47864
|
+
spawnedThreadIds
|
|
47865
|
+
}];
|
|
47635
47866
|
const next = new Map(current);
|
|
47636
47867
|
next.set(tokenHash, {
|
|
47637
47868
|
...record,
|
|
47638
47869
|
lastAliveAt: timestamp
|
|
47639
47870
|
});
|
|
47640
|
-
return [record.scope
|
|
47871
|
+
return [record.controlThreadIds.size === 0 ? record.scope : {
|
|
47872
|
+
...record.scope,
|
|
47873
|
+
capabilities: /* @__PURE__ */ new Set([...record.scope.capabilities, "watch"]),
|
|
47874
|
+
controlThreadIds: record.controlThreadIds,
|
|
47875
|
+
watchThreadIds: /* @__PURE__ */ new Set([...record.scope.watchThreadIds ?? [], ...record.controlThreadIds])
|
|
47876
|
+
}, {
|
|
47877
|
+
records: next,
|
|
47878
|
+
spawnedThreadIds
|
|
47879
|
+
}];
|
|
47641
47880
|
});
|
|
47642
47881
|
});
|
|
47643
47882
|
const touch = Effect.fn("McpSessionRegistry.touch")(function* (threadId) {
|
|
47644
47883
|
const timestamp = yield* currentTimeMillis;
|
|
47645
|
-
yield* SynchronizedRef.update(state, ({ records }) => {
|
|
47884
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47646
47885
|
const current = pruneDead(records, timestamp);
|
|
47647
47886
|
const next = new Map(current);
|
|
47648
47887
|
for (const [tokenHash, record] of current) if (record.scope.threadId === threadId) next.set(tokenHash, {
|
|
47649
47888
|
...record,
|
|
47650
47889
|
lastAliveAt: timestamp
|
|
47651
47890
|
});
|
|
47652
|
-
return {
|
|
47891
|
+
return {
|
|
47892
|
+
records: next,
|
|
47893
|
+
spawnedThreadIds
|
|
47894
|
+
};
|
|
47895
|
+
});
|
|
47896
|
+
});
|
|
47897
|
+
const recordSpawnedThread = Effect.fn("McpSessionRegistry.recordSpawnedThread")(function* (input) {
|
|
47898
|
+
yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
|
|
47899
|
+
const next = new Map(records);
|
|
47900
|
+
for (const [tokenHash, record] of records) if (record.scope.providerSessionId === input.providerSessionId) next.set(tokenHash, {
|
|
47901
|
+
...record,
|
|
47902
|
+
controlThreadIds: /* @__PURE__ */ new Set([...record.controlThreadIds, input.threadId])
|
|
47903
|
+
});
|
|
47904
|
+
return {
|
|
47905
|
+
records: next,
|
|
47906
|
+
spawnedThreadIds: /* @__PURE__ */ new Set([...spawnedThreadIds, input.threadId])
|
|
47907
|
+
};
|
|
47653
47908
|
});
|
|
47654
47909
|
});
|
|
47655
|
-
const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records }) => ({
|
|
47910
|
+
const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => ({
|
|
47911
|
+
records: new Map(Array.from(records).filter(([, record]) => !predicate(record))),
|
|
47912
|
+
spawnedThreadIds
|
|
47913
|
+
}));
|
|
47656
47914
|
return McpSessionRegistry.of({
|
|
47657
47915
|
issue,
|
|
47658
47916
|
resolve,
|
|
47659
47917
|
touch,
|
|
47918
|
+
recordSpawnedThread,
|
|
47660
47919
|
revokeProviderSession: Effect.fn("McpSessionRegistry.revokeProviderSession")(function* (providerSessionId) {
|
|
47661
47920
|
yield* revokeWhere((record) => record.scope.providerSessionId === providerSessionId);
|
|
47662
47921
|
}),
|
|
47663
47922
|
revokeThread: Effect.fn("McpSessionRegistry.revokeThread")(function* (threadId) {
|
|
47664
47923
|
yield* revokeWhere((record) => record.scope.threadId === threadId);
|
|
47665
47924
|
}),
|
|
47666
|
-
revokeAll: SynchronizedRef.
|
|
47925
|
+
revokeAll: SynchronizedRef.update(state, ({ spawnedThreadIds }) => ({
|
|
47926
|
+
records: /* @__PURE__ */ new Map(),
|
|
47927
|
+
spawnedThreadIds
|
|
47928
|
+
}))
|
|
47667
47929
|
});
|
|
47668
47930
|
});
|
|
47669
47931
|
let activeMcpSessionRegistry;
|
|
@@ -71327,6 +71589,81 @@ const SERVER_NOTIFICATION_PARAMS = {
|
|
|
71327
71589
|
"account/login/completed": V2AccountLoginCompletedNotification
|
|
71328
71590
|
};
|
|
71329
71591
|
//#endregion
|
|
71592
|
+
//#region src/provider/Layers/codexMcpArgs.ts
|
|
71593
|
+
/** Codex's own name for the token variable of p4code's built-in server. */
|
|
71594
|
+
const CODEX_P4CODE_BEARER_TOKEN_ENV_VAR = "P4_MCP_BEARER_TOKEN";
|
|
71595
|
+
/** Prefix for the per-server variables the registered servers get. */
|
|
71596
|
+
const BEARER_TOKEN_ENV_VAR_PREFIX = "P4_MCP_BEARER_TOKEN_";
|
|
71597
|
+
/** TOML bare keys; anything else has to be quoted inside the dotted path. */
|
|
71598
|
+
const BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
71599
|
+
const AUTHORIZATION_HEADER = "authorization";
|
|
71600
|
+
const BEARER_PREFIX = /^Bearer\s+/;
|
|
71601
|
+
const configKey = (name, field) => `mcp_servers.${BARE_KEY.test(name) ? name : JSON.stringify(name)}.${field}`;
|
|
71602
|
+
/**
|
|
71603
|
+
* A variable name derived from the server's, uniquely.
|
|
71604
|
+
*
|
|
71605
|
+
* Uppercased with every character a shell would not accept replaced, which can
|
|
71606
|
+
* collide (`a.b` and `a-b`), so the sanitized name is the prefix and the index
|
|
71607
|
+
* keeps it unique. The index is stable within one call, which is all that is
|
|
71608
|
+
* needed: the variable and the reference to it are emitted together.
|
|
71609
|
+
*/
|
|
71610
|
+
const bearerTokenEnvVar = (name, index) => `${BEARER_TOKEN_ENV_VAR_PREFIX}${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_${index}`;
|
|
71611
|
+
/**
|
|
71612
|
+
* @param resolved - Servers keyed by name, as the registry resolved them.
|
|
71613
|
+
* @param exclude - Names p4code declares itself. A registration cannot displace
|
|
71614
|
+
* the built-in `p4-code` server, whose per-thread credential is the only
|
|
71615
|
+
* thing that makes `task_current` and the thread tools work.
|
|
71616
|
+
*/
|
|
71617
|
+
const toCodexMcpConfig = (resolved, exclude = /* @__PURE__ */ new Set()) => {
|
|
71618
|
+
const args = [];
|
|
71619
|
+
const env = {};
|
|
71620
|
+
const skipped = [];
|
|
71621
|
+
const push = (name, field, value) => {
|
|
71622
|
+
args.push("-c", `${configKey(name, field)}=${value}`);
|
|
71623
|
+
};
|
|
71624
|
+
let index = 0;
|
|
71625
|
+
for (const [name, server] of Object.entries(resolved)) {
|
|
71626
|
+
if (exclude.has(name)) continue;
|
|
71627
|
+
if (server.type === "stdio") {
|
|
71628
|
+
push(name, "command", JSON.stringify(server.command));
|
|
71629
|
+
push(name, "args", JSON.stringify(server.args));
|
|
71630
|
+
if (Object.keys(server.env).length > 0) push(name, "env", `{ ${Object.entries(server.env).map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`).join(", ")} }`);
|
|
71631
|
+
index += 1;
|
|
71632
|
+
continue;
|
|
71633
|
+
}
|
|
71634
|
+
if (server.type === "sse") {
|
|
71635
|
+
skipped.push({
|
|
71636
|
+
name,
|
|
71637
|
+
reason: "Codex speaks streamable HTTP, not SSE."
|
|
71638
|
+
});
|
|
71639
|
+
continue;
|
|
71640
|
+
}
|
|
71641
|
+
const headerNames = Object.keys(server.headers);
|
|
71642
|
+
const authorization = headerNames.find((key) => key.toLowerCase() === AUTHORIZATION_HEADER);
|
|
71643
|
+
const otherHeaders = headerNames.filter((key) => key !== authorization);
|
|
71644
|
+
if (otherHeaders.length > 0) {
|
|
71645
|
+
skipped.push({
|
|
71646
|
+
name,
|
|
71647
|
+
reason: `Codex sends no custom headers, and this server needs ${otherHeaders.join(", ")}.`
|
|
71648
|
+
});
|
|
71649
|
+
continue;
|
|
71650
|
+
}
|
|
71651
|
+
push(name, "url", JSON.stringify(server.url));
|
|
71652
|
+
if (authorization !== void 0) {
|
|
71653
|
+
const token = server.headers[authorization]?.replace(BEARER_PREFIX, "") ?? "";
|
|
71654
|
+
const variable = bearerTokenEnvVar(name, index);
|
|
71655
|
+
env[variable] = token;
|
|
71656
|
+
push(name, "bearer_token_env_var", JSON.stringify(variable));
|
|
71657
|
+
}
|
|
71658
|
+
index += 1;
|
|
71659
|
+
}
|
|
71660
|
+
return {
|
|
71661
|
+
args,
|
|
71662
|
+
env,
|
|
71663
|
+
skipped
|
|
71664
|
+
};
|
|
71665
|
+
};
|
|
71666
|
+
//#endregion
|
|
71330
71667
|
//#region ../../packages/effect-codex-app-server/src/_internal/shared.ts
|
|
71331
71668
|
const JsonRpcId$1 = Schema$1.Union([Schema$1.Number, Schema$1.String]);
|
|
71332
71669
|
const JsonRpcError = Schema$1.Struct({
|
|
@@ -73723,6 +74060,11 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
|
|
|
73723
74060
|
if (existing && !existing.stopped) yield* Effect.suspend(() => stopSessionInternal(existing));
|
|
73724
74061
|
const serviceTier = input.modelSelection?.instanceId === boundInstanceId ? getCodexServiceTierOptionValue(input.modelSelection) : void 0;
|
|
73725
74062
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
74063
|
+
const externalMcp = toCodexMcpConfig(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, P4CODE_MCP_SERVER_NAMES);
|
|
74064
|
+
for (const dropped of externalMcp.skipped) yield* Effect.logWarning("codex cannot express an mcp registration", {
|
|
74065
|
+
server: dropped.name,
|
|
74066
|
+
reason: dropped.reason
|
|
74067
|
+
});
|
|
73726
74068
|
const runtimeInput = {
|
|
73727
74069
|
threadId: input.threadId,
|
|
73728
74070
|
providerInstanceId: boundInstanceId,
|
|
@@ -73735,17 +74077,18 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
|
|
|
73735
74077
|
runtimeMode: input.runtimeMode,
|
|
73736
74078
|
...input.modelSelection?.instanceId === boundInstanceId ? { model: input.modelSelection.model } : {},
|
|
73737
74079
|
...serviceTier ? { serviceTier } : {},
|
|
73738
|
-
...mcpSession ? {
|
|
74080
|
+
...externalMcp.args.length > 0 || mcpSession ? {
|
|
73739
74081
|
environment: {
|
|
73740
74082
|
...options?.environment ?? process.env,
|
|
73741
|
-
|
|
74083
|
+
...externalMcp.env,
|
|
74084
|
+
...mcpSession ? { [CODEX_P4CODE_BEARER_TOKEN_ENV_VAR]: mcpSession.authorizationHeader.replace(/^Bearer\s+/, "") } : {}
|
|
73742
74085
|
},
|
|
73743
|
-
appServerArgs: [
|
|
74086
|
+
appServerArgs: [...externalMcp.args, ...mcpSession ? [
|
|
73744
74087
|
"-c",
|
|
73745
|
-
`mcp_servers.
|
|
74088
|
+
`mcp_servers.${P4CODE_MCP_SERVER_NAME}.url=${mcpSession.endpoint}`,
|
|
73746
74089
|
"-c",
|
|
73747
|
-
|
|
73748
|
-
]
|
|
74090
|
+
`mcp_servers.${P4CODE_MCP_SERVER_NAME}.bearer_token_env_var="${CODEX_P4CODE_BEARER_TOKEN_ENV_VAR}"`
|
|
74091
|
+
] : []]
|
|
73749
74092
|
} : {}
|
|
73750
74093
|
};
|
|
73751
74094
|
const sessionScope = yield* Scope.make("sequential");
|
|
@@ -74248,6 +74591,7 @@ const CodexDriver = {
|
|
|
74248
74591
|
const adapter = yield* makeCodexAdapter(effectiveConfig, {
|
|
74249
74592
|
instanceId,
|
|
74250
74593
|
environment: processEnv,
|
|
74594
|
+
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
74251
74595
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
|
|
74252
74596
|
});
|
|
74253
74597
|
const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv);
|
|
@@ -79840,6 +80184,31 @@ const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(function*
|
|
|
79840
80184
|
};
|
|
79841
80185
|
});
|
|
79842
80186
|
//#endregion
|
|
80187
|
+
//#region src/provider/acp/acpMcpServers.ts
|
|
80188
|
+
const namedPairs = (values) => Object.entries(values).map(([name, value]) => ({
|
|
80189
|
+
name,
|
|
80190
|
+
value
|
|
80191
|
+
}));
|
|
80192
|
+
/**
|
|
80193
|
+
* @param resolved - Servers keyed by name, as the registry resolved them.
|
|
80194
|
+
* @param exclude - Names p4code declares itself, which win over a registration
|
|
80195
|
+
* of the same name. The built-in `p4-code` server carries a per-thread
|
|
80196
|
+
* credential no registration can reproduce, so a same-named registration must
|
|
80197
|
+
* not be able to displace it — it would take `task_current` with it, and the
|
|
80198
|
+
* failure would look like the agent forgetting which task it is on.
|
|
80199
|
+
*/
|
|
80200
|
+
const toAcpMcpServers = (resolved, exclude = /* @__PURE__ */ new Set()) => Object.entries(resolved).filter(([name]) => !exclude.has(name)).map(([name, server]) => server.type === "stdio" ? {
|
|
80201
|
+
name,
|
|
80202
|
+
command: server.command,
|
|
80203
|
+
args: [...server.args],
|
|
80204
|
+
env: namedPairs(server.env)
|
|
80205
|
+
} : {
|
|
80206
|
+
type: server.type,
|
|
80207
|
+
name,
|
|
80208
|
+
url: server.url,
|
|
80209
|
+
headers: namedPairs(server.headers)
|
|
80210
|
+
});
|
|
80211
|
+
//#endregion
|
|
79843
80212
|
//#region src/provider/acp/AcpAdapterSupport.ts
|
|
79844
80213
|
const isAcpProcessExitedError = Schema$1.is(AcpProcessExitedError);
|
|
79845
80214
|
const isAcpRequestError = Schema$1.is(AcpRequestError);
|
|
@@ -80324,6 +80693,15 @@ function makeCursorAdapter(cursorSettings, options) {
|
|
|
80324
80693
|
});
|
|
80325
80694
|
const effectiveCursorSettings = options?.resolveSettings ? yield* options.resolveSettings : cursorSettings;
|
|
80326
80695
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
80696
|
+
const mcpServers = [...toAcpMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, P4CODE_MCP_SERVER_NAMES), ...mcpSession ? [{
|
|
80697
|
+
type: "http",
|
|
80698
|
+
name: P4CODE_MCP_SERVER_NAME,
|
|
80699
|
+
url: mcpSession.endpoint,
|
|
80700
|
+
headers: [{
|
|
80701
|
+
name: "Authorization",
|
|
80702
|
+
value: mcpSession.authorizationHeader
|
|
80703
|
+
}]
|
|
80704
|
+
}] : []];
|
|
80327
80705
|
const acp = yield* makeCursorAcpRuntime({
|
|
80328
80706
|
cursorSettings: effectiveCursorSettings,
|
|
80329
80707
|
...options?.environment ? { environment: options.environment } : {},
|
|
@@ -80331,18 +80709,10 @@ function makeCursorAdapter(cursorSettings, options) {
|
|
|
80331
80709
|
cwd,
|
|
80332
80710
|
...resumeSessionId ? { resumeSessionId } : {},
|
|
80333
80711
|
clientInfo: {
|
|
80334
|
-
name:
|
|
80712
|
+
name: P4CODE_MCP_SERVER_NAME,
|
|
80335
80713
|
version: "0.0.0"
|
|
80336
80714
|
},
|
|
80337
|
-
...
|
|
80338
|
-
type: "http",
|
|
80339
|
-
name: "p4-code",
|
|
80340
|
-
url: mcpSession.endpoint,
|
|
80341
|
-
headers: [{
|
|
80342
|
-
name: "Authorization",
|
|
80343
|
-
value: mcpSession.authorizationHeader
|
|
80344
|
-
}]
|
|
80345
|
-
}] } : {},
|
|
80715
|
+
...mcpServers.length > 0 ? { mcpServers } : {},
|
|
80346
80716
|
...acpNativeLoggers
|
|
80347
80717
|
}).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.provideService(Scope.Scope, sessionScope), Effect.mapError((cause) => new ProviderAdapterProcessError({
|
|
80348
80718
|
provider: PROVIDER$3,
|
|
@@ -80823,6 +81193,7 @@ const CursorDriver = {
|
|
|
80823
81193
|
});
|
|
80824
81194
|
const adapter = yield* makeCursorAdapter(effectiveConfig, {
|
|
80825
81195
|
environment: processEnv,
|
|
81196
|
+
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
80826
81197
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {},
|
|
80827
81198
|
instanceId
|
|
80828
81199
|
});
|
|
@@ -81545,6 +81916,15 @@ function makeGrokAdapter(grokSettings, options) {
|
|
|
81545
81916
|
threadId: input.threadId
|
|
81546
81917
|
});
|
|
81547
81918
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
81919
|
+
const mcpServers = [...toAcpMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, P4CODE_MCP_SERVER_NAMES), ...mcpSession ? [{
|
|
81920
|
+
type: "http",
|
|
81921
|
+
name: P4CODE_MCP_SERVER_NAME,
|
|
81922
|
+
url: mcpSession.endpoint,
|
|
81923
|
+
headers: [{
|
|
81924
|
+
name: "Authorization",
|
|
81925
|
+
value: mcpSession.authorizationHeader
|
|
81926
|
+
}]
|
|
81927
|
+
}] : []];
|
|
81548
81928
|
const acp = yield* makeGrokAcpRuntime({
|
|
81549
81929
|
grokSettings,
|
|
81550
81930
|
...options?.environment ? { environment: options.environment } : {},
|
|
@@ -81552,18 +81932,10 @@ function makeGrokAdapter(grokSettings, options) {
|
|
|
81552
81932
|
cwd,
|
|
81553
81933
|
...resumeSessionId ? { resumeSessionId } : {},
|
|
81554
81934
|
clientInfo: {
|
|
81555
|
-
name:
|
|
81935
|
+
name: P4CODE_MCP_SERVER_NAME,
|
|
81556
81936
|
version: "0.0.0"
|
|
81557
81937
|
},
|
|
81558
|
-
...
|
|
81559
|
-
type: "http",
|
|
81560
|
-
name: "p4-code",
|
|
81561
|
-
url: mcpSession.endpoint,
|
|
81562
|
-
headers: [{
|
|
81563
|
-
name: "Authorization",
|
|
81564
|
-
value: mcpSession.authorizationHeader
|
|
81565
|
-
}]
|
|
81566
|
-
}] } : {},
|
|
81938
|
+
...mcpServers.length > 0 ? { mcpServers } : {},
|
|
81567
81939
|
...acpNativeLoggers
|
|
81568
81940
|
}).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.provideService(Scope.Scope, sessionScope), Effect.mapError((cause) => new ProviderAdapterProcessError({
|
|
81569
81941
|
provider: PROVIDER$2,
|
|
@@ -82379,6 +82751,7 @@ const GrokDriver = {
|
|
|
82379
82751
|
});
|
|
82380
82752
|
const adapter = yield* makeGrokAdapter(effectiveConfig, {
|
|
82381
82753
|
environment: processEnv,
|
|
82754
|
+
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
82382
82755
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {},
|
|
82383
82756
|
instanceId
|
|
82384
82757
|
});
|
|
@@ -84216,6 +84589,27 @@ const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration")(funct
|
|
|
84216
84589
|
};
|
|
84217
84590
|
});
|
|
84218
84591
|
//#endregion
|
|
84592
|
+
//#region src/provider/Layers/openCodeMcpServers.ts
|
|
84593
|
+
/**
|
|
84594
|
+
* @param resolved - Servers keyed by name, as the registry resolved them.
|
|
84595
|
+
* @param exclude - Names p4code declares itself, which a registration must not
|
|
84596
|
+
* be able to displace: the built-in `p4-code` server carries the per-thread
|
|
84597
|
+
* credential behind `task_current` and the thread tools.
|
|
84598
|
+
*/
|
|
84599
|
+
const toOpenCodeMcpServers = (resolved, exclude = /* @__PURE__ */ new Set()) => Object.entries(resolved).filter(([name]) => !exclude.has(name)).map(([name, server]) => ({
|
|
84600
|
+
name,
|
|
84601
|
+
config: server.type === "stdio" ? {
|
|
84602
|
+
type: "local",
|
|
84603
|
+
command: [server.command, ...server.args],
|
|
84604
|
+
...Object.keys(server.env).length > 0 ? { environment: server.env } : {}
|
|
84605
|
+
} : {
|
|
84606
|
+
type: "remote",
|
|
84607
|
+
url: server.url,
|
|
84608
|
+
...Object.keys(server.headers).length > 0 ? { headers: server.headers } : {},
|
|
84609
|
+
oauth: false
|
|
84610
|
+
}
|
|
84611
|
+
}));
|
|
84612
|
+
//#endregion
|
|
84219
84613
|
//#region src/provider/Layers/OpenCodeAdapter.ts
|
|
84220
84614
|
const PROVIDER = ProviderDriverKind.make("opencode");
|
|
84221
84615
|
/**
|
|
@@ -84892,8 +85286,15 @@ function makeOpenCodeAdapter(openCodeSettings, options) {
|
|
|
84892
85286
|
...server.external && serverPassword ? { serverPassword } : {}
|
|
84893
85287
|
});
|
|
84894
85288
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
85289
|
+
if (!server.external) {
|
|
85290
|
+
const externalMcpServers = toOpenCodeMcpServers(options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers, P4CODE_MCP_SERVER_NAMES);
|
|
85291
|
+
for (const entry of externalMcpServers) yield* runOpenCodeSdk("mcp.add", () => client.mcp.add(entry)).pipe(Effect.catchCause((cause) => Effect.logWarning("opencode rejected an mcp registration", {
|
|
85292
|
+
server: entry.name,
|
|
85293
|
+
cause
|
|
85294
|
+
})));
|
|
85295
|
+
}
|
|
84895
85296
|
if (mcpSession && !server.external) yield* runOpenCodeSdk("mcp.add", () => client.mcp.add({
|
|
84896
|
-
name:
|
|
85297
|
+
name: P4CODE_MCP_SERVER_NAME,
|
|
84897
85298
|
config: {
|
|
84898
85299
|
type: "remote",
|
|
84899
85300
|
url: mcpSession.endpoint,
|
|
@@ -85582,6 +85983,7 @@ const BUILT_IN_DRIVERS = [
|
|
|
85582
85983
|
const adapter = yield* makeOpenCodeAdapter(effectiveConfig, {
|
|
85583
85984
|
instanceId,
|
|
85584
85985
|
environment: processEnv,
|
|
85986
|
+
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
85585
85987
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
|
|
85586
85988
|
});
|
|
85587
85989
|
const textGeneration = yield* makeOpenCodeTextGeneration(effectiveConfig, processEnv);
|
|
@@ -86254,7 +86656,29 @@ const TaskProposeTool = Tool.make("task_propose", {
|
|
|
86254
86656
|
failure: TaskToolError,
|
|
86255
86657
|
dependencies
|
|
86256
86658
|
}).annotate(Tool.Title, "Propose a task").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86257
|
-
|
|
86659
|
+
/**
|
|
86660
|
+
* Reading a tracker ticket, for every provider rather than only the ones that
|
|
86661
|
+
* can talk to the tracker themselves.
|
|
86662
|
+
*
|
|
86663
|
+
* p4code resolves this server-side through the Linear server the user
|
|
86664
|
+
* registered once in Settings, so an agent gets the ticket whether or not its
|
|
86665
|
+
* own runtime was handed that registration. Without it, a thread started from
|
|
86666
|
+
* "MOBILE-12241" on a provider with no tracker of its own can only ask the user
|
|
86667
|
+
* to paste the description back in - which is the question they already
|
|
86668
|
+
* answered by quoting the id.
|
|
86669
|
+
*
|
|
86670
|
+
* On the task toolkit rather than a toolkit of its own because it is board
|
|
86671
|
+
* work: a resolved ticket's next step is `task_create` with the `externalRef`
|
|
86672
|
+
* this returns, and the two share the one capability a session is granted.
|
|
86673
|
+
*/
|
|
86674
|
+
const TicketResolveTool = Tool.make("ticket_resolve", {
|
|
86675
|
+
description: "Read a tracker ticket by id or URL (e.g. MOBILE-12241, or a linear.app link), returning its identifier, canonical URL, title and description. Use this whenever the user names a ticket rather than asking them to paste it. Pass the returned externalRef straight to task_create to mirror the ticket onto the board. Fails when no tracker is registered on this machine, which is a thing only the user can fix.",
|
|
86676
|
+
parameters: TicketResolveInput,
|
|
86677
|
+
success: TicketResolveResult,
|
|
86678
|
+
failure: Schema$1.Union([TaskToolUnavailableError, TicketResolveError]),
|
|
86679
|
+
dependencies: [McpInvocationContext, TicketResolver]
|
|
86680
|
+
}).annotate(Tool.Title, "Resolve a ticket").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86681
|
+
const TaskToolkit = Toolkit.make(TaskListTool, TaskGetTool, TaskCurrentTool, TaskCreateTool, TaskUpdateTool, TaskProposeTool, TicketResolveTool);
|
|
86258
86682
|
//#endregion
|
|
86259
86683
|
//#region src/mcp/toolkits/tasks/handlers.ts
|
|
86260
86684
|
const nowIso$1 = Effect.map(DateTime.now, DateTime.formatIso);
|
|
@@ -86340,12 +86764,45 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
86340
86764
|
if (Option.isNone(patched)) return yield* new TaskNotFoundError({ taskId: input.taskId });
|
|
86341
86765
|
return patched.value;
|
|
86342
86766
|
}),
|
|
86767
|
+
ticket_resolve: (input) => Effect.gen(function* () {
|
|
86768
|
+
yield* requireTaskCapability();
|
|
86769
|
+
return yield* (yield* TicketResolver).resolve(input.reference);
|
|
86770
|
+
}),
|
|
86343
86771
|
task_propose: () => Effect.gen(function* () {
|
|
86344
86772
|
yield* requireTaskCapability();
|
|
86345
86773
|
return { awaitingConfirmation: true };
|
|
86346
86774
|
})
|
|
86347
86775
|
});
|
|
86348
86776
|
//#endregion
|
|
86777
|
+
//#region src/orchestration/pendingThreadSettles.ts
|
|
86778
|
+
/**
|
|
86779
|
+
* Threads that asked to settle while their session was still running.
|
|
86780
|
+
*
|
|
86781
|
+
* The decider refuses `thread.settle` for a thread whose session is starting or
|
|
86782
|
+
* running, and an agent settling its own thread always calls from inside that
|
|
86783
|
+
* session - so the command it wants can never be the command it sends. The
|
|
86784
|
+
* intent is recorded here instead and replayed when the session stops, which is
|
|
86785
|
+
* what "settle this thread" meant from the agent's side anyway: finish, then
|
|
86786
|
+
* close.
|
|
86787
|
+
*
|
|
86788
|
+
* A module-level set rather than a service because it has to be reachable from
|
|
86789
|
+
* two layer trees that do not otherwise meet - an MCP tool handler on one side,
|
|
86790
|
+
* the provider command reactor on the other - and threading a new service
|
|
86791
|
+
* through both to hold a set of ids is more wiring than the thing is worth.
|
|
86792
|
+
* `McpProviderSession` and the active-registry handle are the same shape for
|
|
86793
|
+
* the same reason. It is intentionally not durable: a queued settle belongs to
|
|
86794
|
+
* a session, and a server restart has already ended every session there was.
|
|
86795
|
+
*/
|
|
86796
|
+
const pending = /* @__PURE__ */ new Set();
|
|
86797
|
+
const queueThreadSettle = (threadId) => {
|
|
86798
|
+
pending.add(threadId);
|
|
86799
|
+
};
|
|
86800
|
+
/**
|
|
86801
|
+
* Removes the intent and reports whether there was one, so a caller cannot
|
|
86802
|
+
* settle the same thread twice by reading and then forgetting to clear.
|
|
86803
|
+
*/
|
|
86804
|
+
const takeQueuedThreadSettle = (threadId) => pending.delete(threadId);
|
|
86805
|
+
//#endregion
|
|
86349
86806
|
//#region src/sync/assetCompression.ts
|
|
86350
86807
|
/**
|
|
86351
86808
|
* Checking that a compressed asset still says what the original said.
|
|
@@ -86584,12 +87041,28 @@ const backupFileName = (input) => `${input.fileName}.${input.atIso.replace(/[:.]
|
|
|
86584
87041
|
//#endregion
|
|
86585
87042
|
//#region src/mcp/toolkits/threads/tools.ts
|
|
86586
87043
|
/**
|
|
86587
|
-
*
|
|
86588
|
-
*
|
|
86589
|
-
*
|
|
86590
|
-
*
|
|
86591
|
-
*
|
|
87044
|
+
* Scoped to what this session started, on purpose.
|
|
87045
|
+
*
|
|
87046
|
+
* A control tool may name a thread, but only one this session created with
|
|
87047
|
+
* `thread_spawn`; omitting the id means the session's own thread, which is the
|
|
87048
|
+
* common case and the one that cannot be got wrong. The grant lives on the
|
|
87049
|
+
* credential in `McpSessionRegistry`, so a leaked per-session token still
|
|
87050
|
+
* reaches only the work that token itself began, and its reach cannot widen
|
|
87051
|
+
* because something changed on the board.
|
|
87052
|
+
*
|
|
87053
|
+
* Delete and archive stay unexposed. Deletion is the one irreversible action in
|
|
87054
|
+
* this file and a thread carries its whole transcript, which is a worse thing
|
|
87055
|
+
* to lose than the task row the task toolkit withheld delete for.
|
|
87056
|
+
*
|
|
87057
|
+
* `thread_spawn` goes one level: a thread that was itself spawned is issued a
|
|
87058
|
+
* credential that cannot spawn, so a loop meets a bound rather than a request
|
|
87059
|
+
* not to loop. `thread_settle` on a live session is queued and applied when the
|
|
87060
|
+
* session stops, because the decider refuses an immediate settle there and a
|
|
87061
|
+
* call from a thread's own session always arrives while it is alive.
|
|
87062
|
+
*
|
|
87063
|
+
* @module mcp/toolkits/threads/tools
|
|
86592
87064
|
*/
|
|
87065
|
+
/** Self-only, and staying that way: `thread_configure` is where a title on another thread is set. */
|
|
86593
87066
|
const ThreadRenameTool = Tool.make("thread_rename", {
|
|
86594
87067
|
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
87068
|
parameters: ThreadRenameInput,
|
|
@@ -86601,6 +87074,54 @@ const ThreadRenameTool = Tool.make("thread_rename", {
|
|
|
86601
87074
|
Crypto.Crypto
|
|
86602
87075
|
]
|
|
86603
87076
|
}).annotate(Tool.Title, "Rename this thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
87077
|
+
const ThreadSpawnTool = Tool.make("thread_spawn", {
|
|
87078
|
+
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.",
|
|
87079
|
+
parameters: ThreadSpawnInput,
|
|
87080
|
+
success: ThreadSpawnResult,
|
|
87081
|
+
failure: ThreadControlToolError,
|
|
87082
|
+
dependencies: [
|
|
87083
|
+
McpInvocationContext,
|
|
87084
|
+
McpSessionRegistry,
|
|
87085
|
+
OrchestrationEngineService,
|
|
87086
|
+
ProjectionThreadRepository,
|
|
87087
|
+
Crypto.Crypto
|
|
87088
|
+
]
|
|
87089
|
+
}).annotate(Tool.Title, "Start a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
87090
|
+
const ThreadConfigureTool = Tool.make("thread_configure", {
|
|
87091
|
+
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.",
|
|
87092
|
+
parameters: ThreadConfigureInput,
|
|
87093
|
+
success: ThreadConfigureResult,
|
|
87094
|
+
failure: ThreadControlToolError,
|
|
87095
|
+
dependencies: [
|
|
87096
|
+
McpInvocationContext,
|
|
87097
|
+
OrchestrationEngineService,
|
|
87098
|
+
Crypto.Crypto
|
|
87099
|
+
]
|
|
87100
|
+
}).annotate(Tool.Title, "Configure a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
87101
|
+
const ThreadSettleTool = Tool.make("thread_settle", {
|
|
87102
|
+
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.",
|
|
87103
|
+
parameters: ThreadSettleInput,
|
|
87104
|
+
success: ThreadSettleResult,
|
|
87105
|
+
failure: ThreadControlToolError,
|
|
87106
|
+
dependencies: [
|
|
87107
|
+
McpInvocationContext,
|
|
87108
|
+
OrchestrationEngineService,
|
|
87109
|
+
ProjectionThreadRepository,
|
|
87110
|
+
ProjectionThreadSessionRepository,
|
|
87111
|
+
Crypto.Crypto
|
|
87112
|
+
]
|
|
87113
|
+
}).annotate(Tool.Title, "Settle a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
87114
|
+
const ThreadSnoozeTool = Tool.make("thread_snooze", {
|
|
87115
|
+
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.",
|
|
87116
|
+
parameters: ThreadSnoozeInput,
|
|
87117
|
+
success: ThreadSnoozeResult,
|
|
87118
|
+
failure: ThreadControlToolError,
|
|
87119
|
+
dependencies: [
|
|
87120
|
+
McpInvocationContext,
|
|
87121
|
+
OrchestrationEngineService,
|
|
87122
|
+
Crypto.Crypto
|
|
87123
|
+
]
|
|
87124
|
+
}).annotate(Tool.Title, "Snooze a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
86604
87125
|
const MemoryAppendTool = Tool.make("memory_append", {
|
|
86605
87126
|
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
87127
|
parameters: MemoryAppendInput,
|
|
@@ -86636,7 +87157,7 @@ const AssetCompressTool = Tool.make("asset_compress", {
|
|
|
86636
87157
|
Path.Path
|
|
86637
87158
|
]
|
|
86638
87159
|
}).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);
|
|
87160
|
+
const ThreadToolkit = Toolkit.make(ThreadSpawnTool, ThreadConfigureTool, ThreadSettleTool, ThreadSnoozeTool, ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
|
|
86640
87161
|
//#endregion
|
|
86641
87162
|
//#region src/mcp/toolkits/threads/handlers.ts
|
|
86642
87163
|
const DEFAULT_MEMORY_APPEND_TARGET = "CLAUDE.md";
|
|
@@ -86653,7 +87174,181 @@ const COMPRESS_BACKUP_DIRNAME = "asset-compress-backups";
|
|
|
86653
87174
|
const COMPRESSIBLE_EXTENSION = ".md";
|
|
86654
87175
|
/** The same test the asset readers use: a NUL is what breaks a JSON text column. */
|
|
86655
87176
|
const looksBinary = (content) => content.includes("\0");
|
|
87177
|
+
/**
|
|
87178
|
+
* Every control command shares this: mint an id, dispatch, and turn a decider
|
|
87179
|
+
* refusal into one error the agent can read.
|
|
87180
|
+
*/
|
|
87181
|
+
const dispatchControl = Effect.fn("mcp.threads.dispatchControl")(function* (command, threadId) {
|
|
87182
|
+
yield* (yield* OrchestrationEngineService).dispatch(command).pipe(Effect.mapError((cause) => new ThreadControlRejectedError({
|
|
87183
|
+
threadId,
|
|
87184
|
+
commandType: command.type,
|
|
87185
|
+
detail: cause.message
|
|
87186
|
+
})));
|
|
87187
|
+
});
|
|
87188
|
+
const newCommandId = Effect.gen(function* () {
|
|
87189
|
+
const crypto = yield* Crypto.Crypto;
|
|
87190
|
+
return CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
87191
|
+
});
|
|
86656
87192
|
const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
87193
|
+
thread_spawn: (input) => Effect.gen(function* () {
|
|
87194
|
+
const invocation = yield* requireThreadSpawn();
|
|
87195
|
+
const registry = yield* McpSessionRegistry;
|
|
87196
|
+
const threads = yield* ProjectionThreadRepository;
|
|
87197
|
+
const crypto = yield* Crypto.Crypto;
|
|
87198
|
+
const parent = yield* threads.getById({ threadId: invocation.threadId }).pipe(Effect.mapError((cause) => new ThreadSpawnNotPermittedError({
|
|
87199
|
+
threadId: invocation.threadId,
|
|
87200
|
+
detail: `this thread could not be read to take its defaults from: ${cause.message}`
|
|
87201
|
+
})));
|
|
87202
|
+
if (Option.isNone(parent)) return yield* new ThreadSpawnNotPermittedError({
|
|
87203
|
+
threadId: invocation.threadId,
|
|
87204
|
+
detail: "this thread is not on the board, so there is nothing to start a thread beside"
|
|
87205
|
+
});
|
|
87206
|
+
const template = parent.value;
|
|
87207
|
+
const projectId = input.projectId ?? template.projectId;
|
|
87208
|
+
const runtimeMode = input.runtimeMode ?? template.runtimeMode;
|
|
87209
|
+
const interactionMode = input.interactionMode ?? template.interactionMode;
|
|
87210
|
+
const compressMode = input.compressMode ?? template.compressMode;
|
|
87211
|
+
const unpromptedSubagents = input.unpromptedSubagents ?? template.unpromptedSubagents === 1;
|
|
87212
|
+
const threadId = ThreadId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
87213
|
+
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
87214
|
+
yield* dispatchControl({
|
|
87215
|
+
type: "thread.create",
|
|
87216
|
+
commandId: yield* newCommandId,
|
|
87217
|
+
threadId,
|
|
87218
|
+
projectId,
|
|
87219
|
+
title: input.title,
|
|
87220
|
+
modelSelection: template.modelSelection,
|
|
87221
|
+
runtimeMode,
|
|
87222
|
+
interactionMode,
|
|
87223
|
+
compressMode,
|
|
87224
|
+
unpromptedSubagents,
|
|
87225
|
+
branch: null,
|
|
87226
|
+
worktreePath: null,
|
|
87227
|
+
createdAt
|
|
87228
|
+
}, threadId);
|
|
87229
|
+
yield* registry.recordSpawnedThread({
|
|
87230
|
+
providerSessionId: invocation.providerSessionId,
|
|
87231
|
+
threadId
|
|
87232
|
+
});
|
|
87233
|
+
yield* dispatchControl({
|
|
87234
|
+
type: "thread.turn.start",
|
|
87235
|
+
commandId: yield* newCommandId,
|
|
87236
|
+
threadId,
|
|
87237
|
+
message: {
|
|
87238
|
+
messageId: MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
|
|
87239
|
+
role: "user",
|
|
87240
|
+
text: input.prompt,
|
|
87241
|
+
attachments: []
|
|
87242
|
+
},
|
|
87243
|
+
modelSelection: template.modelSelection,
|
|
87244
|
+
titleSeed: input.title,
|
|
87245
|
+
runtimeMode,
|
|
87246
|
+
interactionMode,
|
|
87247
|
+
compressMode,
|
|
87248
|
+
unpromptedSubagents,
|
|
87249
|
+
createdAt
|
|
87250
|
+
}, threadId);
|
|
87251
|
+
return {
|
|
87252
|
+
threadId,
|
|
87253
|
+
projectId,
|
|
87254
|
+
title: input.title
|
|
87255
|
+
};
|
|
87256
|
+
}),
|
|
87257
|
+
thread_configure: (input) => Effect.gen(function* () {
|
|
87258
|
+
const { threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
87259
|
+
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
87260
|
+
const applied = [];
|
|
87261
|
+
if (input.title !== void 0 || input.modelSelection !== void 0) {
|
|
87262
|
+
yield* dispatchControl({
|
|
87263
|
+
type: "thread.meta.update",
|
|
87264
|
+
commandId: yield* newCommandId,
|
|
87265
|
+
threadId,
|
|
87266
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
87267
|
+
...input.modelSelection !== void 0 ? { modelSelection: input.modelSelection } : {}
|
|
87268
|
+
}, threadId);
|
|
87269
|
+
if (input.title !== void 0) applied.push("title");
|
|
87270
|
+
if (input.modelSelection !== void 0) applied.push("modelSelection");
|
|
87271
|
+
}
|
|
87272
|
+
if (input.runtimeMode !== void 0) {
|
|
87273
|
+
yield* dispatchControl({
|
|
87274
|
+
type: "thread.runtime-mode.set",
|
|
87275
|
+
commandId: yield* newCommandId,
|
|
87276
|
+
threadId,
|
|
87277
|
+
runtimeMode: input.runtimeMode,
|
|
87278
|
+
createdAt
|
|
87279
|
+
}, threadId);
|
|
87280
|
+
applied.push("runtimeMode");
|
|
87281
|
+
}
|
|
87282
|
+
if (input.interactionMode !== void 0) {
|
|
87283
|
+
yield* dispatchControl({
|
|
87284
|
+
type: "thread.interaction-mode.set",
|
|
87285
|
+
commandId: yield* newCommandId,
|
|
87286
|
+
threadId,
|
|
87287
|
+
interactionMode: input.interactionMode,
|
|
87288
|
+
createdAt
|
|
87289
|
+
}, threadId);
|
|
87290
|
+
applied.push("interactionMode");
|
|
87291
|
+
}
|
|
87292
|
+
if (input.compressMode !== void 0) {
|
|
87293
|
+
yield* dispatchControl({
|
|
87294
|
+
type: "thread.compress-mode.set",
|
|
87295
|
+
commandId: yield* newCommandId,
|
|
87296
|
+
threadId,
|
|
87297
|
+
compressMode: input.compressMode,
|
|
87298
|
+
createdAt
|
|
87299
|
+
}, threadId);
|
|
87300
|
+
applied.push("compressMode");
|
|
87301
|
+
}
|
|
87302
|
+
if (input.unpromptedSubagents !== void 0) {
|
|
87303
|
+
yield* dispatchControl({
|
|
87304
|
+
type: "thread.unprompted-subagents.set",
|
|
87305
|
+
commandId: yield* newCommandId,
|
|
87306
|
+
threadId,
|
|
87307
|
+
unpromptedSubagents: input.unpromptedSubagents,
|
|
87308
|
+
createdAt
|
|
87309
|
+
}, threadId);
|
|
87310
|
+
applied.push("unpromptedSubagents");
|
|
87311
|
+
}
|
|
87312
|
+
return {
|
|
87313
|
+
threadId,
|
|
87314
|
+
applied
|
|
87315
|
+
};
|
|
87316
|
+
}),
|
|
87317
|
+
thread_settle: (input) => Effect.gen(function* () {
|
|
87318
|
+
const { threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
87319
|
+
const threads = yield* ProjectionThreadRepository;
|
|
87320
|
+
const session = yield* (yield* ProjectionThreadSessionRepository).getByThreadId({ threadId }).pipe(Effect.catchCause(() => Effect.succeed(Option.none())));
|
|
87321
|
+
if (!(Option.isSome(session) && (session.value.status === "starting" || session.value.status === "running"))) {
|
|
87322
|
+
yield* dispatchControl({
|
|
87323
|
+
type: "thread.settle",
|
|
87324
|
+
commandId: yield* newCommandId,
|
|
87325
|
+
threadId
|
|
87326
|
+
}, threadId);
|
|
87327
|
+
return {
|
|
87328
|
+
threadId,
|
|
87329
|
+
outcome: "settled"
|
|
87330
|
+
};
|
|
87331
|
+
}
|
|
87332
|
+
queueThreadSettle(threadId);
|
|
87333
|
+
yield* threads.getById({ threadId }).pipe(Effect.orDie);
|
|
87334
|
+
return {
|
|
87335
|
+
threadId,
|
|
87336
|
+
outcome: "queued"
|
|
87337
|
+
};
|
|
87338
|
+
}),
|
|
87339
|
+
thread_snooze: (input) => Effect.gen(function* () {
|
|
87340
|
+
const { threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
87341
|
+
yield* dispatchControl({
|
|
87342
|
+
type: "thread.snooze",
|
|
87343
|
+
commandId: yield* newCommandId,
|
|
87344
|
+
threadId,
|
|
87345
|
+
snoozedUntil: input.snoozedUntil
|
|
87346
|
+
}, threadId);
|
|
87347
|
+
return {
|
|
87348
|
+
threadId,
|
|
87349
|
+
snoozedUntil: input.snoozedUntil
|
|
87350
|
+
};
|
|
87351
|
+
}),
|
|
86657
87352
|
thread_rename: (input) => Effect.gen(function* () {
|
|
86658
87353
|
const invocation = yield* requireThreadCapability();
|
|
86659
87354
|
const engine = yield* OrchestrationEngineService;
|
|
@@ -88214,13 +88909,34 @@ const make$2 = Effect.gen(function* () {
|
|
|
88214
88909
|
if (providerError) return providerError.detail;
|
|
88215
88910
|
return Cause.pretty(cause);
|
|
88216
88911
|
};
|
|
88912
|
+
/**
|
|
88913
|
+
* Applies a settle an agent asked for while its session was still running.
|
|
88914
|
+
*
|
|
88915
|
+
* The decider refuses `thread.settle` for a live session, so `thread_settle`
|
|
88916
|
+
* over MCP records the intent instead of failing. This is the moment it
|
|
88917
|
+
* becomes legal: the session has just been written as stopped, so the same
|
|
88918
|
+
* command the agent could not send now succeeds. Taken from the queue rather
|
|
88919
|
+
* than read, so a second stop cannot settle the thread twice.
|
|
88920
|
+
*/
|
|
88921
|
+
const flushQueuedSettle = Effect.fnUntraced(function* (threadId) {
|
|
88922
|
+
if (!takeQueuedThreadSettle(threadId)) return;
|
|
88923
|
+
const commandId = yield* serverCommandId("queued-thread-settle");
|
|
88924
|
+
yield* orchestrationEngine.dispatch({
|
|
88925
|
+
type: "thread.settle",
|
|
88926
|
+
commandId,
|
|
88927
|
+
threadId
|
|
88928
|
+
}).pipe(Effect.catchCause((cause) => Effect.logDebug("queued thread settle did not apply", {
|
|
88929
|
+
threadId,
|
|
88930
|
+
cause
|
|
88931
|
+
})));
|
|
88932
|
+
});
|
|
88217
88933
|
const setThreadSession = (input) => serverCommandId("provider-session-set").pipe(Effect.flatMap((commandId) => orchestrationEngine.dispatch({
|
|
88218
88934
|
type: "thread.session.set",
|
|
88219
88935
|
commandId,
|
|
88220
88936
|
threadId: input.threadId,
|
|
88221
88937
|
session: input.session,
|
|
88222
88938
|
createdAt: input.createdAt
|
|
88223
|
-
})));
|
|
88939
|
+
})), Effect.tap(() => input.session.status === "stopped" || input.session.status === "error" ? flushQueuedSettle(input.threadId) : Effect.void));
|
|
88224
88940
|
const setThreadSessionErrorOnTurnStartFailure = Effect.fnUntraced(function* (input) {
|
|
88225
88941
|
const thread = yield* resolveThread(input.threadId);
|
|
88226
88942
|
if (!thread) return;
|