@p4code/cli 0.1.23 → 0.1.25
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 +256 -21
- package/dist/client/assets/DiffPanel-C6ILyHye.js +98 -0
- package/dist/client/assets/{FilePreviewPanel-CBdYEmtx.js → FilePreviewPanel-CgFwP53a.js} +4 -4
- package/dist/client/assets/PreviewPanel-CBlKzh6g.js +2 -0
- package/dist/client/assets/arrow-right-DdiaD_l3.js +2 -0
- package/dist/client/assets/{fileCommentAnnotations-BvEFhUAB.js → fileCommentAnnotations-BDZMQFe-.js} +2 -2
- package/dist/client/assets/index-CCDCJ1fx.js +867 -0
- package/dist/client/assets/index-CVtJ86EL.css +1 -0
- package/dist/client/assets/terminal-links-McQ4PMJM.js +46 -0
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/DiffPanel-D0McnRVJ.js +0 -98
- package/dist/client/assets/PreviewPanel-F4V0X333.js +0 -2
- package/dist/client/assets/arrow-right-Fc_jCzG8.js +0 -2
- package/dist/client/assets/index-DN4UQwOg.js +0 -853
- package/dist/client/assets/index-jIQiqqGn.css +0 -1
- package/dist/client/assets/terminal-links-B96vwInV.js +0 -46
package/dist/bin.mjs
CHANGED
|
@@ -236,7 +236,7 @@ const make$76 = () => {
|
|
|
236
236
|
const layer$72 = Layer.sync(NetService, make$76);
|
|
237
237
|
//#endregion
|
|
238
238
|
//#region package.json
|
|
239
|
-
var version = "0.1.
|
|
239
|
+
var version = "0.1.25";
|
|
240
240
|
//#endregion
|
|
241
241
|
//#region src/config.ts
|
|
242
242
|
/**
|
|
@@ -5035,6 +5035,13 @@ const ThreadTokenUsageSnapshot = Schema$1.Struct({
|
|
|
5035
5035
|
toolUses: Schema$1.optional(NonNegativeInt),
|
|
5036
5036
|
durationMs: Schema$1.optional(NonNegativeInt),
|
|
5037
5037
|
compactsAutomatically: Schema$1.optional(Schema$1.Boolean),
|
|
5038
|
+
/**
|
|
5039
|
+
* The compress mode the turn these deltas belong to actually ran under, not
|
|
5040
|
+
* the thread's mode now. Without it the per-turn output figures cannot be
|
|
5041
|
+
* attributed, and the whole question of what compression costs stays a guess.
|
|
5042
|
+
* Optional because snapshots recorded before it existed have no answer.
|
|
5043
|
+
*/
|
|
5044
|
+
compressMode: Schema$1.optional(CompressMode),
|
|
5038
5045
|
breakdown: Schema$1.optional(ContextWindowBreakdown)
|
|
5039
5046
|
});
|
|
5040
5047
|
const ThreadTokenUsageUpdatedPayload = Schema$1.Struct({ usage: ThreadTokenUsageSnapshot });
|
|
@@ -16151,6 +16158,36 @@ function buildSkillOverrides(disabled, discovered) {
|
|
|
16151
16158
|
* @module provider/Drivers/ClaudeSkills
|
|
16152
16159
|
*/
|
|
16153
16160
|
const FRONTMATTER_PATTERN$1 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
16161
|
+
/** The `true` spellings the CLI accepts for a boolean frontmatter field. */
|
|
16162
|
+
const TRUTHY_FRONTMATTER_VALUES = /* @__PURE__ */ new Set([
|
|
16163
|
+
"true",
|
|
16164
|
+
"yes",
|
|
16165
|
+
"on",
|
|
16166
|
+
"1"
|
|
16167
|
+
]);
|
|
16168
|
+
/**
|
|
16169
|
+
* A frontmatter boolean, spelled any of the ways the CLI allows.
|
|
16170
|
+
*
|
|
16171
|
+
* YAML already decodes an unquoted `true` to a boolean, but a quoted `"yes"`
|
|
16172
|
+
* arrives as a string, and the CLI accepts both. Reading only the boolean would
|
|
16173
|
+
* make a skill that works there silently model-invocable here.
|
|
16174
|
+
*/
|
|
16175
|
+
function readFrontmatterBoolean(value) {
|
|
16176
|
+
if (typeof value === "boolean") return value;
|
|
16177
|
+
if (typeof value === "number") return value === 1;
|
|
16178
|
+
if (typeof value === "string") return TRUTHY_FRONTMATTER_VALUES.has(value.trim().toLowerCase());
|
|
16179
|
+
}
|
|
16180
|
+
/**
|
|
16181
|
+
* The document with its frontmatter block removed.
|
|
16182
|
+
*
|
|
16183
|
+
* The body is what a skill actually instructs; the header is metadata about
|
|
16184
|
+
* how it is selected. Exported for the expansion that inlines a skill into a
|
|
16185
|
+
* turn, which must send the instructions without the routing header.
|
|
16186
|
+
*/
|
|
16187
|
+
function stripMarkdownFrontmatter(contents) {
|
|
16188
|
+
const match = FRONTMATTER_PATTERN$1.exec(contents);
|
|
16189
|
+
return (match ? contents.slice(match[0].length) : contents).trim();
|
|
16190
|
+
}
|
|
16154
16191
|
/**
|
|
16155
16192
|
* The `name`/`description` header a skill and a subagent definition both carry.
|
|
16156
16193
|
*
|
|
@@ -16172,10 +16209,12 @@ function parseMarkdownFrontmatter(contents) {
|
|
|
16172
16209
|
const record = parsed;
|
|
16173
16210
|
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
16174
16211
|
const description = typeof record.description === "string" ? record.description.trim() : "";
|
|
16212
|
+
const disableModelInvocation = readFrontmatterBoolean(record["disable-model-invocation"]);
|
|
16175
16213
|
return {
|
|
16176
16214
|
kind: "parsed",
|
|
16177
16215
|
...name ? { name } : {},
|
|
16178
|
-
...description ? { description } : {}
|
|
16216
|
+
...description ? { description } : {},
|
|
16217
|
+
...disableModelInvocation === void 0 ? {} : { disableModelInvocation }
|
|
16179
16218
|
};
|
|
16180
16219
|
}
|
|
16181
16220
|
/**
|
|
@@ -18928,7 +18967,7 @@ const COMPRESS_SHARED_RULES = `Respond terse. All technical substance stays. Onl
|
|
|
18928
18967
|
|
|
18929
18968
|
## Persistence
|
|
18930
18969
|
|
|
18931
|
-
Active every response. No
|
|
18970
|
+
Active every response. No filler drift, no drifting back to verbose prose on your own. A later instruction that switches the response style off or changes its intensity replaces this block: obey the most recent one, not this one.
|
|
18932
18971
|
|
|
18933
18972
|
## Rules
|
|
18934
18973
|
|
|
@@ -19016,12 +19055,35 @@ const COMPRESS_TURN_REMINDERS = {
|
|
|
19016
19055
|
full: "[response style: compressed full - terse fragments, drop articles/filler; keep negations and numbers exact; code/commits/security text normal]",
|
|
19017
19056
|
ultra: "[response style: compressed ultra - maximum terseness, one word when enough; keep negations and numbers exact; code/commits/security text normal]"
|
|
19018
19057
|
};
|
|
19058
|
+
/**
|
|
19059
|
+
* Sent when compression is switched off on a session that is still carrying a
|
|
19060
|
+
* ruleset it cannot un-send. Silence is not enough: the ruleset states its own
|
|
19061
|
+
* persistence, so the absence of a reminder reads to the model as "nothing
|
|
19062
|
+
* changed" rather than "stop".
|
|
19063
|
+
*/
|
|
19064
|
+
const COMPRESS_REVERT_REMINDER = "[response style: compression off - any earlier response-compression instruction in this session no longer applies; write normal, complete prose from here on]";
|
|
19019
19065
|
function compressRulesetFor(mode) {
|
|
19020
19066
|
return mode === "off" ? void 0 : COMPRESS_RULESETS[mode];
|
|
19021
19067
|
}
|
|
19022
19068
|
function compressTurnReminderFor(mode) {
|
|
19023
19069
|
return mode === "off" ? void 0 : COMPRESS_TURN_REMINDERS[mode];
|
|
19024
19070
|
}
|
|
19071
|
+
/**
|
|
19072
|
+
* What to prepend to a turn's message so the running session ends up in the
|
|
19073
|
+
* requested compress mode.
|
|
19074
|
+
*
|
|
19075
|
+
* `staleRulesetMode` is the mode whose ruleset the session is still carrying in
|
|
19076
|
+
* a channel that cannot be rebuilt for this turn - Claude's system prompt is
|
|
19077
|
+
* frozen at session start, and a first-turn message prefix lives in the
|
|
19078
|
+
* conversation history forever. Codex rebuilds its developer instructions every
|
|
19079
|
+
* turn, so callers pass `undefined` for it and no revert line is spent.
|
|
19080
|
+
*/
|
|
19081
|
+
function compressTurnPrefixFor(input) {
|
|
19082
|
+
if (input.injectRuleset) return compressRulesetFor(input.mode);
|
|
19083
|
+
const reminder = compressTurnReminderFor(input.mode);
|
|
19084
|
+
if (reminder !== void 0) return reminder;
|
|
19085
|
+
return input.staleRulesetMode !== void 0 && input.staleRulesetMode !== "off" ? COMPRESS_REVERT_REMINDER : void 0;
|
|
19086
|
+
}
|
|
19025
19087
|
//#endregion
|
|
19026
19088
|
//#region src/sync/agentPresets.ts
|
|
19027
19089
|
/**
|
|
@@ -88365,6 +88427,8 @@ const BUFFERED_PROPOSED_PLAN_BY_ID_CACHE_CAPACITY = 1e4;
|
|
|
88365
88427
|
const BUFFERED_PROPOSED_PLAN_BY_ID_TTL = Duration.minutes(120);
|
|
88366
88428
|
const TASK_DESCRIPTION_BY_TASK_CACHE_CAPACITY = 1e4;
|
|
88367
88429
|
const TASK_DESCRIPTION_BY_TASK_TTL = Duration.minutes(120);
|
|
88430
|
+
const COMPRESS_MODE_BY_THREAD_CACHE_CAPACITY = 1e4;
|
|
88431
|
+
const COMPRESS_MODE_BY_THREAD_TTL = Duration.minutes(120);
|
|
88368
88432
|
const MAX_BUFFERED_ASSISTANT_CHARS = 24e3;
|
|
88369
88433
|
const STRICT_PROVIDER_LIFECYCLE_GUARD = process.env.P4CODE_STRICT_PROVIDER_LIFECYCLE_GUARD !== "0";
|
|
88370
88434
|
function toTurnId$1(value) {
|
|
@@ -88437,9 +88501,12 @@ function assistantSegmentBaseKeyFromEvent(event) {
|
|
|
88437
88501
|
function assistantSegmentMessageId(baseKey, segmentIndex) {
|
|
88438
88502
|
return MessageId.make(segmentIndex === 0 ? `assistant:${baseKey}` : `assistant:${baseKey}:segment:${segmentIndex}`);
|
|
88439
88503
|
}
|
|
88440
|
-
function buildContextWindowActivityPayload(event) {
|
|
88504
|
+
function buildContextWindowActivityPayload(event, compressMode) {
|
|
88441
88505
|
if (event.type !== "thread.token-usage.updated" || event.payload.usage.usedTokens <= 0) return;
|
|
88442
|
-
return event.payload.usage
|
|
88506
|
+
return compressMode === void 0 ? event.payload.usage : {
|
|
88507
|
+
...event.payload.usage,
|
|
88508
|
+
compressMode
|
|
88509
|
+
};
|
|
88443
88510
|
}
|
|
88444
88511
|
function normalizeRuntimeTurnState(value) {
|
|
88445
88512
|
switch (value) {
|
|
@@ -88474,7 +88541,7 @@ function requestKindFromCanonicalRequestType(requestType) {
|
|
|
88474
88541
|
default: return;
|
|
88475
88542
|
}
|
|
88476
88543
|
}
|
|
88477
|
-
function runtimeEventToActivities(event, taskTitle) {
|
|
88544
|
+
function runtimeEventToActivities(event, taskTitle, compressMode) {
|
|
88478
88545
|
const maybeSequence = (() => {
|
|
88479
88546
|
const eventWithSequence = event;
|
|
88480
88547
|
return eventWithSequence.sessionSequence !== void 0 ? { sequence: eventWithSequence.sessionSequence } : {};
|
|
@@ -88684,7 +88751,7 @@ function runtimeEventToActivities(event, taskTitle) {
|
|
|
88684
88751
|
...maybeSequence
|
|
88685
88752
|
}];
|
|
88686
88753
|
case "thread.token-usage.updated": {
|
|
88687
|
-
const payload = buildContextWindowActivityPayload(event);
|
|
88754
|
+
const payload = buildContextWindowActivityPayload(event, compressMode);
|
|
88688
88755
|
if (!payload) return [];
|
|
88689
88756
|
return [{
|
|
88690
88757
|
id: event.eventId,
|
|
@@ -88785,6 +88852,11 @@ const make$3 = Effect.gen(function* () {
|
|
|
88785
88852
|
timeToLive: TASK_DESCRIPTION_BY_TASK_TTL,
|
|
88786
88853
|
lookup: () => Effect.succeed("")
|
|
88787
88854
|
});
|
|
88855
|
+
const compressModeByThreadId = yield* Cache.make({
|
|
88856
|
+
capacity: COMPRESS_MODE_BY_THREAD_CACHE_CAPACITY,
|
|
88857
|
+
timeToLive: COMPRESS_MODE_BY_THREAD_TTL,
|
|
88858
|
+
lookup: () => Effect.die(/* @__PURE__ */ new Error("compress mode should be read through getOption"))
|
|
88859
|
+
});
|
|
88788
88860
|
const rememberTaskDescription = (threadId, taskId, description) => Cache.set(taskDescriptionByTaskKey, providerTaskKey(threadId, taskId), description);
|
|
88789
88861
|
const lookupTaskDescription = (threadId, taskId) => Cache.getOption(taskDescriptionByTaskKey, providerTaskKey(threadId, taskId)).pipe(Effect.map((description) => Option.filter(description, (value) => value.length > 0).pipe(Option.getOrUndefined)));
|
|
88790
88862
|
const resolveThreadDetail = Effect.fn("resolveThreadDetail")(function* (threadId) {
|
|
@@ -89291,7 +89363,9 @@ const make$3 = Effect.gen(function* () {
|
|
|
89291
89363
|
taskTitle = yield* lookupTaskDescription(thread.id, event.payload.taskId);
|
|
89292
89364
|
if (!taskTitle) taskTitle = findTaskTitleInActivities((yield* getLoadedThreadDetail())?.activities, event.payload.taskId);
|
|
89293
89365
|
}
|
|
89294
|
-
|
|
89366
|
+
if (event.type === "turn.started") yield* Cache.set(compressModeByThreadId, thread.id, thread.compressMode);
|
|
89367
|
+
const turnCompressMode = event.type === "thread.token-usage.updated" ? (yield* Cache.getOption(compressModeByThreadId, thread.id).pipe(Effect.map(Option.getOrUndefined))) ?? thread.compressMode : void 0;
|
|
89368
|
+
const activities = runtimeEventToActivities(event, taskTitle, turnCompressMode);
|
|
89295
89369
|
yield* Effect.forEach(activities, (activity) => providerCommandId(event, "thread-activity-append").pipe(Effect.flatMap((commandId) => orchestrationEngine.dispatch({
|
|
89296
89370
|
type: "thread.activity.append",
|
|
89297
89371
|
commandId,
|
|
@@ -89332,6 +89406,126 @@ const make$3 = Effect.gen(function* () {
|
|
|
89332
89406
|
});
|
|
89333
89407
|
const ProviderRuntimeIngestionLive = Layer.effect(ProviderRuntimeIngestionService, make$3).pipe(Layer.provide(ProjectionTurnRepositoryLive));
|
|
89334
89408
|
//#endregion
|
|
89409
|
+
//#region src/provider/userInvokedSkills.ts
|
|
89410
|
+
/**
|
|
89411
|
+
* Running a skill the model is not allowed to reach.
|
|
89412
|
+
*
|
|
89413
|
+
* A skill carrying `disable-model-invocation: true` is withheld from the model
|
|
89414
|
+
* entirely — description included — so only a person typing `/name` can start
|
|
89415
|
+
* it. That works in the interactive CLI, which expands the slash command before
|
|
89416
|
+
* the model ever sees it. It does not work here: p4code drives the Agent SDK,
|
|
89417
|
+
* whose non-interactive path forwards the typed text verbatim, and p4code's own
|
|
89418
|
+
* fallback is to ask the model to call the `Skill` tool — which is the one door
|
|
89419
|
+
* this flag locks. The result is a skill that is unreachable from both sides,
|
|
89420
|
+
* with nothing in the transcript to say why.
|
|
89421
|
+
*
|
|
89422
|
+
* So the expansion happens here, server-side, before the turn leaves: the
|
|
89423
|
+
* skill's own instructions are inlined into the message the provider receives.
|
|
89424
|
+
* The transcript keeps what the person typed, the same split compression
|
|
89425
|
+
* already uses.
|
|
89426
|
+
*
|
|
89427
|
+
* **Only skills the model may not invoke are expanded.** Everything else is
|
|
89428
|
+
* left alone deliberately — those already work through the `Skill` tool, and
|
|
89429
|
+
* inlining them too would mean two mechanisms racing to run the same skill.
|
|
89430
|
+
*
|
|
89431
|
+
* @module provider/userInvokedSkills
|
|
89432
|
+
*/
|
|
89433
|
+
/**
|
|
89434
|
+
* A leading `/name`, with whatever followed it.
|
|
89435
|
+
*
|
|
89436
|
+
* Anchored at the start: a slash somewhere inside a sentence is a path, a date
|
|
89437
|
+
* or an "and/or", and treating those as commands would rewrite ordinary
|
|
89438
|
+
* messages. The name pattern matches what a skill directory can be called,
|
|
89439
|
+
* including the `plugin:skill` form.
|
|
89440
|
+
*/
|
|
89441
|
+
const SLASH_INVOCATION = /^\/([A-Za-z0-9][A-Za-z0-9_:-]*)[ \t]*([\s\S]*)$/;
|
|
89442
|
+
/** `SKILL.md` is the only file a skill is required to have. */
|
|
89443
|
+
const SKILL_FILENAME = "SKILL.md";
|
|
89444
|
+
const parseSlashInvocation = (text) => {
|
|
89445
|
+
const match = SLASH_INVOCATION.exec(text.trim());
|
|
89446
|
+
const name = match?.[1];
|
|
89447
|
+
if (name === void 0) return void 0;
|
|
89448
|
+
return {
|
|
89449
|
+
name,
|
|
89450
|
+
args: (match?.[2] ?? "").trim()
|
|
89451
|
+
};
|
|
89452
|
+
};
|
|
89453
|
+
/**
|
|
89454
|
+
* The message the provider receives in place of `/name args`.
|
|
89455
|
+
*
|
|
89456
|
+
* Tagged rather than pasted bare so the instructions cannot be mistaken for the
|
|
89457
|
+
* person's own words, and attributed to its file so an agent that needs the
|
|
89458
|
+
* skill's siblings — a reference, a template — knows where to look.
|
|
89459
|
+
*/
|
|
89460
|
+
const buildUserInvokedSkillInput = (input) => [
|
|
89461
|
+
`<skill name="${input.name}" path="${input.path}">`,
|
|
89462
|
+
input.body,
|
|
89463
|
+
"</skill>",
|
|
89464
|
+
"",
|
|
89465
|
+
`The user invoked the ${input.name} skill${input.args ? ` with: ${input.args}` : ""}. Follow the instructions above.`
|
|
89466
|
+
].join("\n");
|
|
89467
|
+
/**
|
|
89468
|
+
* The `SKILL.md` a typed name refers to, searched across the given roots.
|
|
89469
|
+
*
|
|
89470
|
+
* Both names a skill answers to are matched — the frontmatter `name` and the
|
|
89471
|
+
* directory it lives in — because either is what a person sees depending on the
|
|
89472
|
+
* surface they read it from, and matching one only would leave a skill whose
|
|
89473
|
+
* two names disagree quietly unreachable. Later roots win, which is how the
|
|
89474
|
+
* CLI resolves a project skill over a user one.
|
|
89475
|
+
*/
|
|
89476
|
+
const findSkillFile = Effect.fnUntraced(function* (input) {
|
|
89477
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
89478
|
+
const path = yield* Path.Path;
|
|
89479
|
+
let found;
|
|
89480
|
+
for (const root of input.roots) {
|
|
89481
|
+
const entries = yield* fileSystem.readDirectory(root).pipe(Effect.orElseSucceed(() => []));
|
|
89482
|
+
for (const entry of [...entries].sort()) {
|
|
89483
|
+
const skillPath = path.join(root, entry, SKILL_FILENAME);
|
|
89484
|
+
const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
|
|
89485
|
+
if (contents === void 0) continue;
|
|
89486
|
+
const frontmatter = parseMarkdownFrontmatter(contents);
|
|
89487
|
+
const frontmatterName = frontmatter.kind === "parsed" ? frontmatter.name : void 0;
|
|
89488
|
+
if (entry !== input.name && frontmatterName !== input.name) continue;
|
|
89489
|
+
found = {
|
|
89490
|
+
path: skillPath,
|
|
89491
|
+
contents
|
|
89492
|
+
};
|
|
89493
|
+
}
|
|
89494
|
+
}
|
|
89495
|
+
return found;
|
|
89496
|
+
});
|
|
89497
|
+
/**
|
|
89498
|
+
* The turn's text, with a user-invoked skill inlined when there is one.
|
|
89499
|
+
*
|
|
89500
|
+
* `undefined` means "nothing to do", which covers every ordinary message: no
|
|
89501
|
+
* leading slash, a name no skill answers to, a skill the model can invoke for
|
|
89502
|
+
* itself, or a body that is empty once the frontmatter comes off. Reading the
|
|
89503
|
+
* filesystem is best-effort throughout — an unreadable root or file leaves the
|
|
89504
|
+
* message exactly as it was typed, because failing a turn over a skill lookup
|
|
89505
|
+
* would be a worse answer than sending the text.
|
|
89506
|
+
*
|
|
89507
|
+
* @param roots - Skill directories, least specific first.
|
|
89508
|
+
*/
|
|
89509
|
+
const expandUserInvokedSkill = Effect.fnUntraced(function* (input) {
|
|
89510
|
+
const invocation = parseSlashInvocation(input.text);
|
|
89511
|
+
if (invocation === void 0) return void 0;
|
|
89512
|
+
const skill = yield* findSkillFile({
|
|
89513
|
+
name: invocation.name,
|
|
89514
|
+
roots: input.roots
|
|
89515
|
+
});
|
|
89516
|
+
if (skill === void 0) return void 0;
|
|
89517
|
+
const frontmatter = parseMarkdownFrontmatter(skill.contents);
|
|
89518
|
+
if (frontmatter.kind !== "parsed" || frontmatter.disableModelInvocation !== true) return;
|
|
89519
|
+
const body = stripMarkdownFrontmatter(skill.contents);
|
|
89520
|
+
if (body.length === 0) return void 0;
|
|
89521
|
+
return buildUserInvokedSkillInput({
|
|
89522
|
+
name: invocation.name,
|
|
89523
|
+
path: skill.path,
|
|
89524
|
+
body,
|
|
89525
|
+
args: invocation.args
|
|
89526
|
+
});
|
|
89527
|
+
});
|
|
89528
|
+
//#endregion
|
|
89335
89529
|
//#region src/orchestration/Layers/ProviderCommandReactor.ts
|
|
89336
89530
|
const isProviderAdapterRequestError = Schema$1.is(ProviderAdapterRequestError);
|
|
89337
89531
|
const isProviderDriverKind = Schema$1.is(ProviderDriverKind);
|
|
@@ -89415,6 +89609,15 @@ const make$2 = Effect.gen(function* () {
|
|
|
89415
89609
|
});
|
|
89416
89610
|
const hasHandledTurnStartRecently = (key) => Cache.getOption(handledTurnStartKeys, key).pipe(Effect.flatMap((cached) => Cache.set(handledTurnStartKeys, key, true).pipe(Effect.as(Option.isSome(cached)))));
|
|
89417
89611
|
const threadModelSelections = /* @__PURE__ */ new Map();
|
|
89612
|
+
/**
|
|
89613
|
+
* The compress mode whose ruleset the thread's live session was handed at
|
|
89614
|
+
* start, kept because two of the three delivery channels cannot be rebuilt
|
|
89615
|
+
* afterwards: Claude bakes the ruleset into the system prompt of a
|
|
89616
|
+
* long-lived process, and the prefix providers put it in the first turn's
|
|
89617
|
+
* message. Toggling compression off has to send a counter-instruction, and
|
|
89618
|
+
* this is how the reactor knows there is something to counter.
|
|
89619
|
+
*/
|
|
89620
|
+
const threadSessionRulesetModes = /* @__PURE__ */ new Map();
|
|
89418
89621
|
const appendProviderFailureActivity = (input) => Effect.all({
|
|
89419
89622
|
commandId: serverCommandId("provider-failure-activity"),
|
|
89420
89623
|
eventId: serverEventId()
|
|
@@ -89519,6 +89722,25 @@ const make$2 = Effect.gen(function* () {
|
|
|
89519
89722
|
const resolveProject = Effect.fnUntraced(function* (projectId) {
|
|
89520
89723
|
return yield* projectionSnapshotQuery.getProjectShellById(projectId).pipe(Effect.map(Option.getOrUndefined));
|
|
89521
89724
|
});
|
|
89725
|
+
/**
|
|
89726
|
+
* Where a typed `/name` is looked up: the user's skills, then the
|
|
89727
|
+
* workspace's.
|
|
89728
|
+
*
|
|
89729
|
+
* Project last so it wins, matching the CLI's most-specific-wins resolution.
|
|
89730
|
+
* The user root is resolved with an empty `homePath` on purpose — this runs
|
|
89731
|
+
* before a provider instance is chosen, and the config-dir override that a
|
|
89732
|
+
* single Claude instance may carry is not a fact about the person's skills.
|
|
89733
|
+
*/
|
|
89734
|
+
const resolveUserInvokedSkillRoots = Effect.fnUntraced(function* (thread) {
|
|
89735
|
+
const path = yield* Path.Path;
|
|
89736
|
+
const userSkillsDir = yield* resolveClaudeUserSkillsDir({ homePath: "" });
|
|
89737
|
+
const project = yield* resolveProject(thread.projectId);
|
|
89738
|
+
const cwd = resolveThreadWorkspaceCwd({
|
|
89739
|
+
thread,
|
|
89740
|
+
projects: project ? [project] : []
|
|
89741
|
+
});
|
|
89742
|
+
return [userSkillsDir, ...cwd ? [path.join(cwd, ".claude", "skills")] : []];
|
|
89743
|
+
});
|
|
89522
89744
|
const resolveThread = Effect.fnUntraced(function* (threadId) {
|
|
89523
89745
|
return yield* projectionSnapshotQuery.getThreadDetailById(threadId).pipe(Effect.map(Option.getOrUndefined));
|
|
89524
89746
|
});
|
|
@@ -89613,17 +89835,20 @@ const make$2 = Effect.gen(function* () {
|
|
|
89613
89835
|
thread,
|
|
89614
89836
|
projects: project ? [project] : []
|
|
89615
89837
|
});
|
|
89616
|
-
const startProviderSession = (input) =>
|
|
89617
|
-
threadId,
|
|
89618
|
-
|
|
89619
|
-
|
|
89620
|
-
|
|
89621
|
-
|
|
89622
|
-
|
|
89623
|
-
|
|
89624
|
-
|
|
89625
|
-
|
|
89626
|
-
|
|
89838
|
+
const startProviderSession = (input) => {
|
|
89839
|
+
threadSessionRulesetModes.set(threadId, thread.compressMode);
|
|
89840
|
+
return providerService.startSession(threadId, {
|
|
89841
|
+
threadId,
|
|
89842
|
+
...preferredProvider ? { provider: preferredProvider } : {},
|
|
89843
|
+
providerInstanceId: desiredInstanceId,
|
|
89844
|
+
...effectiveCwd ? { cwd: effectiveCwd } : {},
|
|
89845
|
+
modelSelection: desiredModelSelection,
|
|
89846
|
+
...input?.resumeCursor !== void 0 ? { resumeCursor: input.resumeCursor } : {},
|
|
89847
|
+
runtimeMode: desiredRuntimeMode,
|
|
89848
|
+
compressMode: thread.compressMode,
|
|
89849
|
+
unpromptedSubagents: thread.unpromptedSubagents
|
|
89850
|
+
});
|
|
89851
|
+
};
|
|
89627
89852
|
const bindSessionToThread = (session) => Effect.gen(function* () {
|
|
89628
89853
|
if (session.providerInstanceId === void 0) return yield* new ProviderAdapterRequestError({
|
|
89629
89854
|
provider: providerErrorLabel(session.provider),
|
|
@@ -89702,6 +89927,10 @@ const make$2 = Effect.gen(function* () {
|
|
|
89702
89927
|
});
|
|
89703
89928
|
if (input.modelSelection !== void 0) threadModelSelections.set(input.threadId, input.modelSelection);
|
|
89704
89929
|
const normalizedInput = toNonEmptyProviderInput(input.messageText);
|
|
89930
|
+
const expandedInput = normalizedInput === void 0 ? void 0 : (yield* expandUserInvokedSkill({
|
|
89931
|
+
text: normalizedInput,
|
|
89932
|
+
roots: yield* resolveUserInvokedSkillRoots(thread)
|
|
89933
|
+
})) ?? normalizedInput;
|
|
89705
89934
|
const normalizedAttachments = input.attachments ?? [];
|
|
89706
89935
|
const activeSession = yield* providerService.listSessions().pipe(Effect.map((sessions) => sessions.find((session) => session.threadId === input.threadId)));
|
|
89707
89936
|
const sessionModelSwitch = activeSession === void 0 ? "in-session" : activeSession.providerInstanceId === void 0 ? yield* new ProviderAdapterRequestError({
|
|
@@ -89716,8 +89945,13 @@ const make$2 = Effect.gen(function* () {
|
|
|
89716
89945
|
} : requestedModelSelection : input.modelSelection;
|
|
89717
89946
|
const compressMode = thread.compressMode;
|
|
89718
89947
|
const hasSessionLevelRuleset = activeSession?.provider === "claudeAgent" || activeSession?.provider === "codex";
|
|
89719
|
-
const
|
|
89720
|
-
const
|
|
89948
|
+
const rebuildsRulesetEachTurn = activeSession?.provider === "codex";
|
|
89949
|
+
const compressPrefix = compressTurnPrefixFor({
|
|
89950
|
+
mode: compressMode,
|
|
89951
|
+
injectRuleset: !hadActiveSession && !hasSessionLevelRuleset,
|
|
89952
|
+
staleRulesetMode: hadActiveSession && !rebuildsRulesetEachTurn ? threadSessionRulesetModes.get(input.threadId) : void 0
|
|
89953
|
+
});
|
|
89954
|
+
const inputWithCompressPrefix = expandedInput !== void 0 && compressPrefix !== void 0 ? `${compressPrefix}\n\n${expandedInput}` : expandedInput;
|
|
89721
89955
|
return {
|
|
89722
89956
|
threadId: input.threadId,
|
|
89723
89957
|
...inputWithCompressPrefix ? { input: inputWithCompressPrefix } : {},
|
|
@@ -89940,6 +90174,7 @@ const make$2 = Effect.gen(function* () {
|
|
|
89940
90174
|
if (!thread) return;
|
|
89941
90175
|
const now = event.payload.createdAt;
|
|
89942
90176
|
if (thread.session && thread.session.status !== "stopped") yield* providerService.stopSession({ threadId: thread.id });
|
|
90177
|
+
threadSessionRulesetModes.delete(thread.id);
|
|
89943
90178
|
yield* setThreadSession({
|
|
89944
90179
|
threadId: thread.id,
|
|
89945
90180
|
session: {
|