@open-mercato/cezar 0.9.2 → 0.9.3
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/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/contract/health.d.ts +2 -0
- package/dist/contract/index.js +23 -10
- package/dist/git-diff-base.d.ts +51 -26
- package/dist/git-diff-base.js +163 -37
- package/dist/git-diff-base.js.map +1 -1
- package/dist/git-worktree.d.ts +15 -6
- package/dist/git-worktree.js +10 -3
- package/dist/git-worktree.js.map +1 -1
- package/dist/server/capabilities.d.ts +15 -1
- package/dist/server/capabilities.js +16 -1
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/git-changes.d.ts +7 -5
- package/dist/server/git-changes.js +15 -10
- package/dist/server/git-changes.js.map +1 -1
- package/dist/server/server.d.ts +59 -58
- package/dist/server/server.js +54 -3
- package/dist/server/server.js.map +1 -1
- package/dist/workflows/run.d.ts +27 -7
- package/dist/workflows/run.js +106 -43
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/config.d.ts +8 -0
- package/dist/workspace/config.js +29 -2
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +7 -1
- package/dist/workspace/semaphore.js +10 -4
- package/dist/workspace/semaphore.js.map +1 -1
- package/dist/workspace/ui-state.d.ts +3 -2
- package/dist/workspace/ui-state.js +3 -2
- package/dist/workspace/ui-state.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/{alert-dialog-ghffK6g9.js → alert-dialog-V9cigyAO.js} +1 -1
- package/web/dist/assets/arrow-down-DflHdNlN.js +1 -0
- package/web/dist/assets/arrow-left-IIL4uR-C.js +1 -0
- package/web/dist/assets/centered-state-BkAJVLJE.js +43 -0
- package/web/dist/assets/chevron-right-CxAoD1eD.js +1 -0
- package/web/dist/assets/circle-check-CdwcIbkx.js +1 -0
- package/web/dist/assets/{circle-x-C3EFZ3xf.js → circle-x-Cnc43xJ2.js} +1 -1
- package/web/dist/assets/{collapsible-B0JVzd0B.js → collapsible-BlF-7CeC.js} +1 -1
- package/web/dist/assets/{commit-list-wm6DPHNs.js → commit-list-D2mKElUx.js} +1 -1
- package/web/dist/assets/{compare-variants-DbdP4Y5T.js → compare-variants-CFBY16Rc.js} +1 -1
- package/web/dist/assets/{diff-Bizc96qe.js → diff-DE6u5dTe.js} +2 -2
- package/web/dist/assets/{diff-stat-Dyfg3ltF.js → diff-stat-B1j6L7C6.js} +1 -1
- package/web/dist/assets/{diff-view-CG8ek6tC.js → diff-view-Gb2kk3Le.js} +1 -1
- package/web/dist/assets/{dropdown-menu-C1Dlf35P.js → dropdown-menu-Ns-rY6sm.js} +1 -1
- package/web/dist/assets/editable-title-BGuiDxFT.js +1 -0
- package/web/dist/assets/{ellipsis-vertical-DpGTRqC2.js → ellipsis-vertical-BbxtXb6s.js} +1 -1
- package/web/dist/assets/{file-DlUGpUlb.js → file-j5O4NryU.js} +1 -1
- package/web/dist/assets/{folder-BAY6Vddy.js → folder-w9Lwj9x9.js} +1 -1
- package/web/dist/assets/{git-pull-request-agOG0iGM.js → git-pull-request-DlSop3Ch.js} +1 -1
- package/web/dist/assets/{git-toolbar-DGiNMmtY.js → git-toolbar-BdJ8XSxI.js} +1 -1
- package/web/dist/assets/github-CCLn2OAU.js +1 -0
- package/web/dist/assets/{image-preview-BWGk5fPX.js → image-preview-CcFdkZXW.js} +1 -1
- package/web/dist/assets/index-BmHHLbwc.js +6 -0
- package/web/dist/assets/index-u8OAnq0r.css +2 -0
- package/web/dist/assets/{markdown-BGBOGCA_.js → markdown-q9lm7kQ2.js} +1 -1
- package/web/dist/assets/{new-task-form-BpAw_Dks.js → new-task-form-BuYxAWN7.js} +1 -1
- package/web/dist/assets/{pill-Dvs-P0xy.js → pill-t97MvPW_.js} +1 -1
- package/web/dist/assets/{project-router-Cx8NPoqn.js → project-router-DudOmkHf.js} +1 -1
- package/web/dist/assets/{prompt-templates-CSBYBR1I.js → prompt-templates-C5iO3Lda.js} +1 -1
- package/web/dist/assets/{refresh-cw-BoGdAykg.js → refresh-cw-_C2EGJK5.js} +1 -1
- package/web/dist/assets/{repo-git-PmX-BNMo.js → repo-git-DqQzj33d.js} +1 -1
- package/web/dist/assets/{run-diff-EcOkf3ti.js → run-diff-BLoKEnTx.js} +1 -1
- package/web/dist/assets/run-header-Dho4eurS.js +1 -0
- package/web/dist/assets/{search-x-X3EpVhgs.js → search-x-BgxaJgro.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-CZLALx6l.js → skill-empty-hint-D5MOmycv.js} +1 -1
- package/web/dist/assets/skills-B5g064CC.js +1 -0
- package/web/dist/assets/{sparkles-Dt4q7pbG.js → sparkles-ANSka5bH.js} +1 -1
- package/web/dist/assets/{square-terminal-hwBL8XsC.js → square-terminal-qojq20cH.js} +1 -1
- package/web/dist/assets/{tab-link-DIlR5T07.js → tab-link-CIcwFJtC.js} +1 -1
- package/web/dist/assets/task-changes-yRfsEsnh.js +1 -0
- package/web/dist/assets/{task-commits-BTTA5k5T.js → task-commits-yXmDrCoE.js} +1 -1
- package/web/dist/assets/{task-files-Da8_4C9g.js → task-files-BDRmCCPN.js} +2 -2
- package/web/dist/assets/task-thread-C9AnIz4Z.js +9 -0
- package/web/dist/assets/{textarea-CUdokhkv.js → textarea-DVO7pg8Y.js} +1 -1
- package/web/dist/assets/{thread-loading-B8q8ukXJ.js → thread-loading-DJDl701Z.js} +1 -1
- package/web/dist/assets/{trash-2-DhVIdWv8.js → trash-2-DKcnmg6e.js} +1 -1
- package/web/dist/assets/{triangle-alert-XYT0DGCI.js → triangle-alert-CPUEVSZ3.js} +1 -1
- package/web/dist/assets/{upload-BKjN59-1.js → upload-D5Jrq7QK.js} +1 -1
- package/web/dist/assets/{use-desktop-Bf5LS_P4.js → use-desktop-CXae6uyk.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-CzVyoAMd.js → use-submit-shortcut-RKJLKdsW.js} +1 -1
- package/web/dist/assets/{utils-DsLuRXwV.js → utils-Wtkd5E6j.js} +1 -1
- package/web/dist/assets/{workflows-o2QU_pvU.js → workflows-CGyTdyds.js} +3 -3
- package/web/dist/assets/{zoomable-image-7WyUiaVD.js → zoomable-image-hcqwUIlQ.js} +1 -1
- package/web/dist/index.html +22 -22
- package/web/dist/assets/arrow-down-Cz8qb71e.js +0 -1
- package/web/dist/assets/arrow-left-wcYKQXI_.js +0 -1
- package/web/dist/assets/centered-state-ZtnFHA-n.js +0 -43
- package/web/dist/assets/chevron-right-G4caF2QV.js +0 -1
- package/web/dist/assets/circle-check--KvBxXWp.js +0 -1
- package/web/dist/assets/editable-title-5rC_QRBZ.js +0 -1
- package/web/dist/assets/github-Vp7lxYGQ.js +0 -1
- package/web/dist/assets/index-DSxyJpuK.css +0 -2
- package/web/dist/assets/index-DiawI6RX.js +0 -6
- package/web/dist/assets/run-header-BGiv6_qX.js +0 -1
- package/web/dist/assets/skills-CC91ouGH.js +0 -1
- package/web/dist/assets/task-changes-XkHDxzQA.js +0 -1
- package/web/dist/assets/task-thread-DoNXUsHr.js +0 -9
package/dist/workflows/run.d.ts
CHANGED
|
@@ -20,6 +20,14 @@ export declare const AUTOSAVE_INTERVAL_MS = 90000;
|
|
|
20
20
|
* noise interleaving PR history. The flushes (`autosaveCommit` at turn end and before
|
|
21
21
|
* a draft PR) are NOT gated: the branch must still end holding the finished state. */
|
|
22
22
|
export declare function periodicAutosaveEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Explicitly opt out of the repository-root lease for runs that execute in the
|
|
25
|
+
* current checkout. This covers explicit worktree opt-out, non-Git degradation,
|
|
26
|
+
* and continuations whose worktree cannot be restored (spec 006 hardening, #438).
|
|
27
|
+
* This is intentionally unsafe: concurrent agents may overwrite each other's
|
|
28
|
+
* files or Git state. Isolated worktree runs are unaffected.
|
|
29
|
+
*/
|
|
30
|
+
export declare function repositoryRootLockDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
23
31
|
export interface StartRunInput {
|
|
24
32
|
task: string;
|
|
25
33
|
model?: string;
|
|
@@ -142,8 +150,9 @@ export declare class RunManager {
|
|
|
142
150
|
private pumpAgain;
|
|
143
151
|
/**
|
|
144
152
|
* Runs normally isolate in worktrees and may execute in parallel. When that
|
|
145
|
-
* isolation is unavailable (or explicitly disabled),
|
|
146
|
-
*
|
|
153
|
+
* isolation is unavailable (or explicitly disabled), access to `repoRoot` is
|
|
154
|
+
* serialized by default so two agents cannot edit/revert the same files
|
|
155
|
+
* (#438). `CEZ_DISABLE_REPO_LOCK=1` deliberately bypasses this safety lease.
|
|
147
156
|
*/
|
|
148
157
|
private repoRootTail;
|
|
149
158
|
/** `.ai/cezar` — where the per-task handoff files and todos.json live. */
|
|
@@ -524,12 +533,23 @@ export declare function makeRunTitle(task: string, workflow: WorkflowDef): strin
|
|
|
524
533
|
*/
|
|
525
534
|
export declare function skillSystemPrompt(skill: Pick<Skill, 'name' | 'description' | 'body'> & Partial<Pick<Skill, 'path' | 'source'>>): string;
|
|
526
535
|
/**
|
|
527
|
-
* Expand a registry-backed slash skill in
|
|
528
|
-
*
|
|
536
|
+
* Expand a registry-backed slash skill in one prompt string before it reaches a
|
|
537
|
+
* backend. Claude otherwise intercepts an unknown leading slash command, and
|
|
529
538
|
* Codex/OpenCode have no native slash-skill lookup at all (#676).
|
|
530
539
|
*
|
|
531
|
-
* Only
|
|
532
|
-
*
|
|
533
|
-
*
|
|
540
|
+
* Only a match at character zero counts, and unknown commands pass through
|
|
541
|
+
* byte-for-byte — a backend's OWN slash commands must keep working. The caller
|
|
542
|
+
* persists the original user text before applying this delivery-only rewrite.
|
|
543
|
+
*
|
|
544
|
+
* Both delivery seams route through here: live-session messages via
|
|
545
|
+
* `expandRegistrySlashSkill`, and a continuation's opening prompt, which becomes
|
|
546
|
+
* the session's `userPrompt` and never passes through `deliverMessage` at all
|
|
547
|
+
* (#811).
|
|
548
|
+
*/
|
|
549
|
+
export declare function expandRegistrySlashSkillText(text: string, skills: readonly Skill[]): string;
|
|
550
|
+
/**
|
|
551
|
+
* `expandRegistrySlashSkillText` over a live chat message: only the first text
|
|
552
|
+
* block is eligible, and an unchanged block returns the caller's array
|
|
553
|
+
* identity untouched.
|
|
534
554
|
*/
|
|
535
555
|
export declare function expandRegistrySlashSkill(content: ContentBlock[], skills: readonly Skill[]): ContentBlock[];
|
package/dist/workflows/run.js
CHANGED
|
@@ -98,6 +98,17 @@ export const AUTOSAVE_INTERVAL_MS = 90_000;
|
|
|
98
98
|
export function periodicAutosaveEnabled(env = process.env) {
|
|
99
99
|
return env.CEZ_AUTOSAVE === '1';
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Explicitly opt out of the repository-root lease for runs that execute in the
|
|
103
|
+
* current checkout. This covers explicit worktree opt-out, non-Git degradation,
|
|
104
|
+
* and continuations whose worktree cannot be restored (spec 006 hardening, #438).
|
|
105
|
+
* This is intentionally unsafe: concurrent agents may overwrite each other's
|
|
106
|
+
* files or Git state. Isolated worktree runs are unaffected.
|
|
107
|
+
*/
|
|
108
|
+
export function repositoryRootLockDisabled(env = process.env) {
|
|
109
|
+
return env.CEZ_DISABLE_REPO_LOCK === '1';
|
|
110
|
+
}
|
|
111
|
+
const REPOSITORY_ROOT_LOCK_DISABLED_NOTE = 'repository-root lock disabled by CEZ_DISABLE_REPO_LOCK=1 (shared checkout is unsafe)';
|
|
101
112
|
/** Safety cap on autonomous auto-continues per run — stops a stuck agent from nudging forever. */
|
|
102
113
|
const MAX_AUTO_CONTINUES = 40;
|
|
103
114
|
const AUTONOMOUS_NUDGE = 'Continue working autonomously until the task is fully complete. Do not ask me for confirmation or clarification — make reasonable assumptions and proceed. When everything is done, end the session with your done signal.';
|
|
@@ -234,8 +245,9 @@ export class RunManager {
|
|
|
234
245
|
pumpAgain = false;
|
|
235
246
|
/**
|
|
236
247
|
* Runs normally isolate in worktrees and may execute in parallel. When that
|
|
237
|
-
* isolation is unavailable (or explicitly disabled),
|
|
238
|
-
*
|
|
248
|
+
* isolation is unavailable (or explicitly disabled), access to `repoRoot` is
|
|
249
|
+
* serialized by default so two agents cannot edit/revert the same files
|
|
250
|
+
* (#438). `CEZ_DISABLE_REPO_LOCK=1` deliberately bypasses this safety lease.
|
|
239
251
|
*/
|
|
240
252
|
repoRootTail = Promise.resolve();
|
|
241
253
|
/** `.ai/cezar` — where the per-task handoff files and todos.json live. */
|
|
@@ -1300,19 +1312,27 @@ export class RunManager {
|
|
|
1300
1312
|
this.active.set(runId, state);
|
|
1301
1313
|
this.starting.delete(runId);
|
|
1302
1314
|
if (state.cwd === this.repoRoot) {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (!(await this.acquireRepoRoot(runId, state))) {
|
|
1308
|
-
this.store.updateRun(runId, {
|
|
1309
|
-
status: 'cancelled',
|
|
1310
|
-
finishedAt: new Date().toISOString(),
|
|
1311
|
-
currentStepId: undefined,
|
|
1315
|
+
if (repositoryRootLockDisabled()) {
|
|
1316
|
+
this.store.appendEvent(runId, {
|
|
1317
|
+
type: 'note',
|
|
1318
|
+
message: REPOSITORY_ROOT_LOCK_DISABLED_NOTE,
|
|
1312
1319
|
});
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
this.store.appendEvent(runId, {
|
|
1323
|
+
type: 'note',
|
|
1324
|
+
message: 'waiting for exclusive access to the repository working tree',
|
|
1325
|
+
});
|
|
1326
|
+
if (!(await this.acquireRepoRoot(runId, state))) {
|
|
1327
|
+
this.store.updateRun(runId, {
|
|
1328
|
+
status: 'cancelled',
|
|
1329
|
+
finishedAt: new Date().toISOString(),
|
|
1330
|
+
currentStepId: undefined,
|
|
1331
|
+
});
|
|
1332
|
+
this.store.appendEvent(runId, { type: 'lifecycle', message: 'run cancelled' });
|
|
1333
|
+
this.dropActive(runId);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1316
1336
|
}
|
|
1317
1337
|
}
|
|
1318
1338
|
this.armAutosave(state);
|
|
@@ -1333,6 +1353,17 @@ export class RunManager {
|
|
|
1333
1353
|
backend,
|
|
1334
1354
|
});
|
|
1335
1355
|
this.store.appendEvent(runId, { type: 'step-start', stepId, name: 'Continue', kind: 'agent', iteration: 1 });
|
|
1356
|
+
// Registry snapshot for `/skill` expansion. `execute` loads this for the workflow's own
|
|
1357
|
+
// sessions; a continuation builds its OWN ActiveRun, and without this the resumed session
|
|
1358
|
+
// expanded against an empty registry and leaked `/om-...` verbatim to the backend, which
|
|
1359
|
+
// answered "Unknown skill" (#811). Best-effort — discovery must never break Continue.
|
|
1360
|
+
//
|
|
1361
|
+
// Loaded AFTER the run is marked `running`, not before: `continueRun` returns the moment it
|
|
1362
|
+
// schedules this, so every await ahead of that write is a window in which the run still
|
|
1363
|
+
// reads with its previous TERMINAL status. A directory scan there is long enough to be
|
|
1364
|
+
// observed. Nothing between here and `startSession` reads `state.skills`, and no message can
|
|
1365
|
+
// be delivered into a session that does not exist yet, so the invariant is unaffected.
|
|
1366
|
+
state.skills = await discoverSkills(this.repoRoot).catch(() => []);
|
|
1336
1367
|
// Attachments pasted into the follow-up composer, on the same terms as a live-session
|
|
1337
1368
|
// message (#357): persisted to the run's own image store so the thread renders the bubble's
|
|
1338
1369
|
// images rather than a bare count, and handed to the agent BOTH as base64 blocks (so it can
|
|
@@ -1493,12 +1524,19 @@ export class RunManager {
|
|
|
1493
1524
|
const runner = createRunner(continueBackend);
|
|
1494
1525
|
state.currentStepId = stepId;
|
|
1495
1526
|
this.beginUsageInvocation(runId, state, stepId);
|
|
1527
|
+
// A continuation's opening message becomes the session's `userPrompt` and never passes
|
|
1528
|
+
// through `deliverMessage`, so it needs the SAME delivery-only `/skill` rewrite the
|
|
1529
|
+
// live path applies (#811). Delivery-only: the `user-message` event above already
|
|
1530
|
+
// persisted the user's original text, and the transcript must keep showing that.
|
|
1531
|
+
const openingPrompt = expandRegistrySlashSkillText(prompt, state.skills ?? []);
|
|
1496
1532
|
const session = runner.startSession({
|
|
1497
1533
|
// The Continue step is a fresh agent session on the same run — the
|
|
1498
1534
|
// run's extra system prompt (already resolved at execute time and
|
|
1499
1535
|
// echoed on the record) rides along with the handoff contract.
|
|
1500
1536
|
systemPrompt: composeSystemPrompt(record?.systemPrompt, generateFollowups ? HANDOFF_INSTRUCTIONS : HANDOFF_ONLY_INSTRUCTIONS),
|
|
1501
|
-
userPrompt: attachments.length
|
|
1537
|
+
userPrompt: attachments.length
|
|
1538
|
+
? `${openingPrompt}\n\n${pastedAttachmentsText(attachments)}`
|
|
1539
|
+
: openingPrompt,
|
|
1502
1540
|
...(openingImages.length ? { images: openingImages } : {}),
|
|
1503
1541
|
cwd: state.cwd,
|
|
1504
1542
|
allowedTools: DEFAULT_ALLOWED_TOOLS,
|
|
@@ -1601,7 +1639,8 @@ export class RunManager {
|
|
|
1601
1639
|
const repo = await getRepoInfo(this.repoRoot);
|
|
1602
1640
|
if (repo && input.worktree === false) {
|
|
1603
1641
|
// Composer opt-out: run in the repo working tree, no branch/worktree. The
|
|
1604
|
-
// repository-root lease serializes these runs
|
|
1642
|
+
// repository-root lease serializes these runs by default; the explicit
|
|
1643
|
+
// CEZ_DISABLE_REPO_LOCK=1 escape hatch allows unsafe overlap.
|
|
1605
1644
|
// Pin the starting commit: the session's Changes and Commits views use it
|
|
1606
1645
|
// as their stable lower bound while reading the current working copy.
|
|
1607
1646
|
const startingCommit = await getHeadCommit(repo.root);
|
|
@@ -1673,14 +1712,22 @@ export class RunManager {
|
|
|
1673
1712
|
emit({ type: 'note', message: 'not a git repository — running in place, one task at a time' });
|
|
1674
1713
|
}
|
|
1675
1714
|
if (state.cwd === this.repoRoot) {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1715
|
+
if (repositoryRootLockDisabled()) {
|
|
1716
|
+
emit({
|
|
1717
|
+
type: 'note',
|
|
1718
|
+
message: REPOSITORY_ROOT_LOCK_DISABLED_NOTE,
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
else {
|
|
1722
|
+
emit({
|
|
1723
|
+
type: 'note',
|
|
1724
|
+
message: 'waiting for exclusive access to the repository working tree',
|
|
1725
|
+
});
|
|
1726
|
+
// A cancel during the wait leaves the lease ungranted; the step loop
|
|
1727
|
+
// below breaks on `cancelled` before touching the tree and settles the
|
|
1728
|
+
// run through the usual path.
|
|
1729
|
+
await this.acquireRepoRoot(runId, state);
|
|
1730
|
+
}
|
|
1684
1731
|
}
|
|
1685
1732
|
// Handoff journal (spec 007) — seeded after the worktree exists so the
|
|
1686
1733
|
// header can name the branch. Idempotent: an existing file stays as-is.
|
|
@@ -1688,6 +1735,8 @@ export class RunManager {
|
|
|
1688
1735
|
if (seeded)
|
|
1689
1736
|
seedHandoffFile(this.dataDir, seeded);
|
|
1690
1737
|
const skills = await discoverSkills(this.repoRoot);
|
|
1738
|
+
// Every ActiveRun construction site must carry the registry — `runContinuation` builds
|
|
1739
|
+
// its own, and the one that skipped this leaked raw `/skill` text to the backend (#811).
|
|
1691
1740
|
state.skills = skills;
|
|
1692
1741
|
const retriesUsed = new Map();
|
|
1693
1742
|
let checkFailure = null;
|
|
@@ -2210,11 +2259,13 @@ export class RunManager {
|
|
|
2210
2259
|
// deliberately NEVER a title source; see maybeRefreshTitle below. The
|
|
2211
2260
|
// one exception is an explicit CEZ:TITLE declaration (applied above).
|
|
2212
2261
|
if (run.worktreePath && existsSync(run.worktreePath)) {
|
|
2213
|
-
// `taskBranch`
|
|
2214
|
-
// repoints the worktree onto the branch under review, and without the
|
|
2215
|
-
// compare HEAD against
|
|
2262
|
+
// `taskBranch` + `runStartedAt` are what keep this number *this task's* (#751): a
|
|
2263
|
+
// review/QA run repoints the worktree onto the branch under review, and without the
|
|
2264
|
+
// branch to compare HEAD against and the moment it was checked out, the stat would
|
|
2265
|
+
// claim that whole branch's diff.
|
|
2216
2266
|
const stat = await worktreeShortstat(run.worktreePath, run.baseBranch ?? 'HEAD', {
|
|
2217
2267
|
taskBranch: run.branch,
|
|
2268
|
+
runStartedAt: run.startedAt,
|
|
2218
2269
|
});
|
|
2219
2270
|
if (stat)
|
|
2220
2271
|
this.store.updateRun(runId, { diffStat: stat });
|
|
@@ -2586,13 +2637,33 @@ export function skillSystemPrompt(skill) {
|
|
|
2586
2637
|
return lines.join('\n');
|
|
2587
2638
|
}
|
|
2588
2639
|
/**
|
|
2589
|
-
* Expand a registry-backed slash skill in
|
|
2590
|
-
*
|
|
2640
|
+
* Expand a registry-backed slash skill in one prompt string before it reaches a
|
|
2641
|
+
* backend. Claude otherwise intercepts an unknown leading slash command, and
|
|
2591
2642
|
* Codex/OpenCode have no native slash-skill lookup at all (#676).
|
|
2592
2643
|
*
|
|
2593
|
-
* Only
|
|
2594
|
-
*
|
|
2595
|
-
*
|
|
2644
|
+
* Only a match at character zero counts, and unknown commands pass through
|
|
2645
|
+
* byte-for-byte — a backend's OWN slash commands must keep working. The caller
|
|
2646
|
+
* persists the original user text before applying this delivery-only rewrite.
|
|
2647
|
+
*
|
|
2648
|
+
* Both delivery seams route through here: live-session messages via
|
|
2649
|
+
* `expandRegistrySlashSkill`, and a continuation's opening prompt, which becomes
|
|
2650
|
+
* the session's `userPrompt` and never passes through `deliverMessage` at all
|
|
2651
|
+
* (#811).
|
|
2652
|
+
*/
|
|
2653
|
+
export function expandRegistrySlashSkillText(text, skills) {
|
|
2654
|
+
const match = /^\/([A-Za-z0-9][A-Za-z0-9._-]*)(?=\s|$)/.exec(text);
|
|
2655
|
+
if (!match)
|
|
2656
|
+
return text;
|
|
2657
|
+
const skill = skills.find((candidate) => candidate.name === match[1]);
|
|
2658
|
+
if (!skill)
|
|
2659
|
+
return text;
|
|
2660
|
+
const request = text.slice(match[0].length).trim();
|
|
2661
|
+
return request ? `${skillSystemPrompt(skill)}\n\nUser request:\n${request}` : skillSystemPrompt(skill);
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* `expandRegistrySlashSkillText` over a live chat message: only the first text
|
|
2665
|
+
* block is eligible, and an unchanged block returns the caller's array
|
|
2666
|
+
* identity untouched.
|
|
2596
2667
|
*/
|
|
2597
2668
|
export function expandRegistrySlashSkill(content, skills) {
|
|
2598
2669
|
const textIndex = content.findIndex((block) => block.type === 'text');
|
|
@@ -2601,19 +2672,11 @@ export function expandRegistrySlashSkill(content, skills) {
|
|
|
2601
2672
|
const block = content[textIndex];
|
|
2602
2673
|
if (!block || block.type !== 'text')
|
|
2603
2674
|
return content;
|
|
2604
|
-
const
|
|
2605
|
-
if (
|
|
2606
|
-
return content;
|
|
2607
|
-
const skill = skills.find((candidate) => candidate.name === match[1]);
|
|
2608
|
-
if (!skill)
|
|
2675
|
+
const text = expandRegistrySlashSkillText(block.text, skills);
|
|
2676
|
+
if (text === block.text)
|
|
2609
2677
|
return content;
|
|
2610
|
-
const request = block.text.slice(match[0].length).trim();
|
|
2611
|
-
const replacement = {
|
|
2612
|
-
type: 'text',
|
|
2613
|
-
text: request ? `${skillSystemPrompt(skill)}\n\nUser request:\n${request}` : skillSystemPrompt(skill),
|
|
2614
|
-
};
|
|
2615
2678
|
const expanded = [...content];
|
|
2616
|
-
expanded[textIndex] =
|
|
2679
|
+
expanded[textIndex] = { type: 'text', text };
|
|
2617
2680
|
return expanded;
|
|
2618
2681
|
}
|
|
2619
2682
|
//# sourceMappingURL=run.js.map
|