claude-code-session-manager 0.86.0 → 0.87.0
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/assets/AgentLibrary-DyLWzZDf.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel--mISIJ6h.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-C2ahUXTg.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-BiC6oyR2.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-BPleEOld.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-Dc8Qst1R.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-DIXh-OLX.js} +1 -1
- package/dist/assets/MarkdownEditor-C90bkLXK.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DqcbLOLZ.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-CW62MXlh.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-Bw1FhRuF.js} +1 -1
- package/dist/assets/Permissions-BcUC-5y8.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-BnKx9flD.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-Bw5vNVPT.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-CWr0O_w-.js} +1 -1
- package/dist/assets/Scheduler-DYdLuUqq.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-CrBLbg8s.js} +1 -1
- package/dist/assets/Settings-DluB-vN1.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-CHLSseay.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-gNdo_HNK.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Cru05-Ia.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-DNHY0xou.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-I4lmbCgP.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-bWMHjmRh.js} +1 -1
- package/dist/assets/{index-CDo9xBR9.css → index-DV3PorRY.css} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-fc_JjdxL.js} +724 -724
- package/dist/assets/settingsSchema-BfhtZnGD.js +3 -0
- package/dist/index.html +2 -2
- package/package.json +14 -14
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/scheduler-mcp-server.cjs +7 -0
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/epicMint.test.cjs +2 -2
- package/src/main/__tests__/health-delegation-chain.test.cjs +2 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +3 -3
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +15 -1
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +43 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +63 -0
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +101 -1
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +2 -2
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +344 -0
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-looks-done.test.cjs +93 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +160 -0
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +88 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +14 -4
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +14 -0
- package/src/main/chatRunner.cjs +8 -5
- package/src/main/health.cjs +1 -1
- package/src/main/historyAggregator.cjs +5 -0
- package/src/main/index.cjs +95 -44
- package/src/main/ipcSchemas.cjs +47 -0
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +322 -43
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +89 -0
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/usageCircuit.test.cjs +224 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +312 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +193 -0
- package/{scripts → src/main}/lib/activeSessions.cjs +50 -4
- package/src/main/lib/agentModelResolve.cjs +65 -27
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/effectiveModelInfo.cjs +333 -0
- package/src/main/lib/ephemeralCwd.cjs +1 -1
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +42 -12
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/mcpToolCatalog.cjs +4 -1
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +1 -1
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +167 -45
- package/src/main/lib/projectHomeAdminRoutes.cjs +4 -4
- package/src/main/lib/projectPageSummarySchema.cjs +1 -1
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +19 -1
- package/src/main/lib/queueStore.cjs +6 -1
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/scheduleJobSchema.cjs +8 -0
- package/src/main/lib/scheduleJobTransitions.cjs +33 -0
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/usageCircuit.cjs +159 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +1 -1
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +1285 -143
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/templates/project-pages-catalog.json +1 -1
- package/src/main/usage.cjs +21 -3
- package/src/preload/api.d.ts +92 -1
- package/src/preload/index.cjs +10 -0
- package/web/README.md +41 -0
- package/{scripts/render-project-pages.cjs → web/project-pages/render.cjs} +4 -4
- package/{scripts/render-project-pages → web/project-pages/renderer}/dist/renderer.cjs +1 -1
- package/{scripts/validate-project-pages-summary.cjs → web/project-pages/validate-summary.cjs} +5 -5
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- /package/{scripts/project-pages-logic → web/project-pages/logic}/dist/logic.cjs +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<!-- PRD_AUTHORING.md v2 -->
|
|
1
2
|
# PRD Authoring Guide — Scheduler Safety Rules
|
|
2
3
|
|
|
3
4
|
This guide codifies lessons from two stuck-job incidents (fizzpop poll-hang, etch-engine post-AC overrun) into enforceable rules every PRD MUST follow. Violating these rules costs real money and wastes hours waiting at a terminal.
|
|
@@ -148,6 +149,8 @@ estimateMinutes: 60
|
|
|
148
149
|
|
|
149
150
|
**Rule:** Include exact file paths, function signatures if they save a Read, library versions, and the name of any sibling PRD the executor must NOT duplicate. Do not reference "the conversation", "what we discussed", "the design doc", or any other external context. If the executor would need to search for something, include the answer.
|
|
150
151
|
|
|
152
|
+
**Epic context digest is additive, not a dependency (PRD 958):** when a job's `epicId` resolves to a known Epic in that project's `active-index.json`, the scheduler prepends a short digest of the Epic's own session (goal text + recent turns, built by `src/main/lib/epicContextDigest.cjs`'s `buildContextDigest`) to the `-p` prompt sent to the executor — the on-disk PRD `.md` file itself is never rewritten. This exists purely to orient the executor faster; it is never load-bearing. The digest is a silent no-op when the Epic doesn't resolve, and any failure building it is caught and logged, never blocking dispatch. Every PRD body must still stand on its own per the rule above — write it as if the digest will not be there.
|
|
153
|
+
|
|
151
154
|
---
|
|
152
155
|
|
|
153
156
|
## §8 Scope sizing — target ~15 min, ceiling 30 (data-driven, 2026-06)
|
|
@@ -448,3 +451,50 @@ intake`, commit `352b89c`). It syncs open GitHub issues into `session-manager-op
|
|
|
448
451
|
→ queue path the rest of this guide documents. An external project building a Slack (or any
|
|
449
452
|
other) adapter should follow the same source → triage → queue shape, ending at
|
|
450
453
|
`scheduler_create_prd` (app running) or a hand-written PRD file (app closed) as described above.
|
|
454
|
+
|
|
455
|
+
## §15 Resolving a `failed`/`needs_review` job whose target work is already done
|
|
456
|
+
|
|
457
|
+
**Symptom:** a job lands in `failed` or `needs_review` even though its actual objective was
|
|
458
|
+
already satisfied — either by a sibling/concurrent job that finished the same work first (a
|
|
459
|
+
duplicate PRD racing another one), or by an out-of-band actor (a human, another agent) reaching
|
|
460
|
+
the same target state before the scheduler's run even started. The job's own transcript may be
|
|
461
|
+
completely accurate (`SCHEDULER_VERDICT: PASS but no commit landed during the run window`) — the
|
|
462
|
+
"failure" is a stale queue entry, not broken work.
|
|
463
|
+
|
|
464
|
+
**Do NOT hand-edit `queue.json` to fix the status.** It's a live file the running Electron
|
|
465
|
+
scheduler process (and the external watchdog) both read and write; a manual edit races the app's
|
|
466
|
+
own save cycle and risks a torn write. There is also no supported IPC/CLI surface today to flip a
|
|
467
|
+
single job's `status` field directly.
|
|
468
|
+
|
|
469
|
+
**The safe, supported remediation — confirmed working live (2026-07-18):** archive the job's PRD
|
|
470
|
+
*source file*, not the queue entry. `reconcile()` (`src/main/scheduler.cjs`) runs on every queue
|
|
471
|
+
read/tick and drops any `queue.json` job entry whose PRD `.md` no longer exists in `prds/` — so
|
|
472
|
+
archiving the file is sufficient; you never touch `queue.json` yourself.
|
|
473
|
+
|
|
474
|
+
```js
|
|
475
|
+
// From this repo's root (session-manager), or anywhere queueOps.cjs is reachable:
|
|
476
|
+
const q = require('./src/main/queueOps.cjs');
|
|
477
|
+
await q.archiveMany(['<slug-of-the-stale-job>']);
|
|
478
|
+
// Atomic rename to prds-archived/<ISO>/<slug>.md — reversible, path-contained,
|
|
479
|
+
// no queue.json write. The next reconcile() (within one scheduler tick, ~10-15s
|
|
480
|
+
// observed) drops the matching queue.json job entry automatically.
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
This is the exact mechanism `schedule:clear-queue`'s IPC handler uses internally, just scoped to
|
|
484
|
+
one slug instead of "every non-running job" — safe to call from outside Electron (no admin API
|
|
485
|
+
needed) since `queueOps.cjs`'s `archiveMany` is a plain exported function, not IPC-gated.
|
|
486
|
+
|
|
487
|
+
**When to use this vs. requeueing a retry:** only when you've confirmed the target state is
|
|
488
|
+
already correct (read the failed job's own transcript/log — did it conclude "nothing left to
|
|
489
|
+
do"? did a sibling job's commit already land the same objective? does an independent check like
|
|
490
|
+
`gh pr view <n> --json mergeable` already show the desired end state?). If the target state is
|
|
491
|
+
genuinely NOT yet reached, fix and requeue instead — archiving does not fix an actual bug, it
|
|
492
|
+
only clears a stale status label for already-completed work.
|
|
493
|
+
|
|
494
|
+
**Known gap this doesn't cover:** the verifier's `pass_no_commit` classification (a run that
|
|
495
|
+
concludes "PASS, no code change needed" gets flagged `needs_review` as if it were suspicious)
|
|
496
|
+
does not yet special-case "another actor already satisfied this PRD's postcondition" for
|
|
497
|
+
externally-checkable targets (e.g. a `gh pr view`-mergeable branch). `RESCANNABLE_VERDICTS`
|
|
498
|
+
already includes `pass_no_commit` for one narrow case (fix-plan jobs, exempted 2026-07-12) but
|
|
499
|
+
not the general case. See `session-manager-operations/feedback/processed/` for the tracked
|
|
500
|
+
follow-up on softening this classification for merge-style PRDs with a checkable postcondition.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "GENERATED by
|
|
2
|
+
"$comment": "GENERATED by web/project-pages/assets.cjs from src/renderer/lib/projectPages/library/*.tsx — do not edit; run `npm run build:project-pages-assets`.",
|
|
3
3
|
"lenses": [
|
|
4
4
|
{
|
|
5
5
|
"id": "home",
|
package/src/main/usage.cjs
CHANGED
|
@@ -116,6 +116,19 @@ const OK_CACHE_TTL_MS = 30_000;
|
|
|
116
116
|
*
|
|
117
117
|
* Returns one of: 'ok' | 'auth' | 'transient' | 'meter_rate_limited'
|
|
118
118
|
*/
|
|
119
|
+
/**
|
|
120
|
+
* Parses a `Retry-After` header value (delta-seconds or an HTTP-date) into a
|
|
121
|
+
* millisecond duration from now, or null if absent/unparseable.
|
|
122
|
+
*/
|
|
123
|
+
function parseRetryAfterMs(headerValue) {
|
|
124
|
+
if (!headerValue) return null;
|
|
125
|
+
const seconds = Number(headerValue);
|
|
126
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
|
|
127
|
+
const dateMs = Date.parse(headerValue);
|
|
128
|
+
if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
119
132
|
function classifyUsageResponse(status, bodyText) {
|
|
120
133
|
if (status === 401 || status === 403) return { kind: 'auth', httpStatus: status };
|
|
121
134
|
if (status === 429) {
|
|
@@ -195,7 +208,12 @@ async function fetchUsage() {
|
|
|
195
208
|
let parsed = null;
|
|
196
209
|
try { parsed = JSON.parse(body); } catch { /* */ }
|
|
197
210
|
if (parsed?.error?.type === 'rate_limit_error') {
|
|
198
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
kind: 'meter_rate_limited',
|
|
213
|
+
message: body.slice(0, 200),
|
|
214
|
+
httpStatus: 429,
|
|
215
|
+
retryAfterMs: parseRetryAfterMs(r.headers.get('retry-after')),
|
|
216
|
+
};
|
|
199
217
|
}
|
|
200
218
|
return { kind: 'transient', message: body.slice(0, 200) || 'HTTP 429', httpStatus: 429 };
|
|
201
219
|
}
|
|
@@ -241,11 +259,11 @@ function registerBillingHandlers() {
|
|
|
241
259
|
return r;
|
|
242
260
|
}
|
|
243
261
|
if (r.kind === 'meter_rate_limited') {
|
|
244
|
-
if (cache) return { kind: 'meter_rate_limited', message: r.message, httpStatus: r.httpStatus, cached: cache.data, staleSince: cache.fetchedAt };
|
|
262
|
+
if (cache) return { kind: 'meter_rate_limited', message: r.message, httpStatus: r.httpStatus, retryAfterMs: r.retryAfterMs, cached: cache.data, staleSince: cache.fetchedAt };
|
|
245
263
|
return r;
|
|
246
264
|
}
|
|
247
265
|
return r; // config
|
|
248
266
|
});
|
|
249
267
|
}
|
|
250
268
|
|
|
251
|
-
module.exports = { registerBillingHandlers, fetchUsage, classifyUsageResponse, usageMeterApplicable, readClaudeSettingsAuth };
|
|
269
|
+
module.exports = { registerBillingHandlers, fetchUsage, classifyUsageResponse, parseRetryAfterMs, usageMeterApplicable, readClaudeSettingsAuth };
|
package/src/preload/api.d.ts
CHANGED
|
@@ -181,7 +181,7 @@ export type BillingFetchResult =
|
|
|
181
181
|
| { kind: 'ok-stale'; data: BillingData; staleSince: number; lastError: string }
|
|
182
182
|
| { kind: 'auth'; message: string; httpStatus: number; expiredAt?: number | null; cached?: BillingData; staleSince?: number }
|
|
183
183
|
| { kind: 'transient'; message: string; httpStatus: number | null }
|
|
184
|
-
| { kind: 'meter_rate_limited'; message: string; httpStatus: number; cached?: BillingData; staleSince?: number }
|
|
184
|
+
| { kind: 'meter_rate_limited'; message: string; httpStatus: number; retryAfterMs?: number | null; cached?: BillingData; staleSince?: number }
|
|
185
185
|
| { kind: 'config'; message: string };
|
|
186
186
|
|
|
187
187
|
// ── MCP server live connection probe (`claude mcp list`)
|
|
@@ -550,6 +550,11 @@ export interface ScheduleJob {
|
|
|
550
550
|
/** Explicit cross-PRD ordering (PRD 832): slugs that must complete before
|
|
551
551
|
* this job is eligible. Replaces the retired shared-NN-parallel convention. */
|
|
552
552
|
dependsOn?: string[];
|
|
553
|
+
/** Wave-authoring decision (scheduler wave-disposition PRD): 'append' (this
|
|
554
|
+
* row's dependsOn extends the Epic's existing chain) or 'new-head' (an
|
|
555
|
+
* independent root). Refreshed from the PRD file on every reconcile, like
|
|
556
|
+
* dependsOn above; null when this row never needed the decision. */
|
|
557
|
+
disposition?: 'append' | 'new-head' | null;
|
|
553
558
|
/** The originating claude session — the Epic's claudeSessionId, resolved
|
|
554
559
|
* from active-index.json at ingest (PRD 832). An Epic IS a tagged session. */
|
|
555
560
|
originSessionId?: string | null;
|
|
@@ -697,6 +702,13 @@ export interface PrdListItem {
|
|
|
697
702
|
sourcePromptId?: string | null;
|
|
698
703
|
/** Owning Epic id, derived from the PRD's directory. See ScheduleJob.epicId. */
|
|
699
704
|
epicId?: string | null;
|
|
705
|
+
/** PRD frontmatter `dependsOn` — slugs that must complete before this PRD
|
|
706
|
+
* is eligible. Mirrors ScheduleJob.dependsOn; parsed by parsePrd but
|
|
707
|
+
* previously dropped by listPrdsInternal before reaching this type, the
|
|
708
|
+
* same class of bug this field's sibling comment (epicId) warns about. */
|
|
709
|
+
dependsOn?: string[] | null;
|
|
710
|
+
/** PRD frontmatter `disposition` — see ScheduleJob.disposition. */
|
|
711
|
+
disposition?: 'append' | 'new-head' | null;
|
|
700
712
|
/** True when this PRD's source .md was found in a `prds-archived/` dir
|
|
701
713
|
* (its scheduler job already ran to completion) rather than the live
|
|
702
714
|
* `prds/` dir. Archived PRDs have no matching queue.json job row (the
|
|
@@ -824,6 +836,10 @@ export interface ScheduleStateSnapshot {
|
|
|
824
836
|
jobs: ScheduleJob[];
|
|
825
837
|
scheduledFor: string | null;
|
|
826
838
|
lastRunAt: string | null;
|
|
839
|
+
/** Stamped every time tickQueue reaches the picker at all, whether or not
|
|
840
|
+
* that pass ends in a launch — distinct from lastRunAt (only stamped on an
|
|
841
|
+
* actual launch). See classifyQueueHealth's header comment. */
|
|
842
|
+
lastDispatchAttemptAt?: string | null;
|
|
827
843
|
nextReset: string | null;
|
|
828
844
|
/** Set when scheduler self-paused (rate-limit detected). null when running normally. */
|
|
829
845
|
paused: SchedulePauseInfo | null;
|
|
@@ -862,6 +878,48 @@ export interface ScheduleLoadGate {
|
|
|
862
878
|
at: string;
|
|
863
879
|
}
|
|
864
880
|
|
|
881
|
+
/** One dependency-chain group blocked on a terminal (failed/skipped) row — computeBlockedChains' shape. */
|
|
882
|
+
export interface ScheduleBlockedChain {
|
|
883
|
+
cwd: string;
|
|
884
|
+
blockedBy: string[];
|
|
885
|
+
blocked: number;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** classifyQueueHealth's verdict — the one honest cause behind a stale-looking queue. */
|
|
889
|
+
export interface ScheduleQueueHealthVerdict {
|
|
890
|
+
kind: 'paused' | 'launch-blocked' | 'idle' | 'saturated' | 'blocked' | 'stalled' | 'running';
|
|
891
|
+
cwd: string | null;
|
|
892
|
+
pending: number;
|
|
893
|
+
needsReviewCount: number;
|
|
894
|
+
runningCount: number;
|
|
895
|
+
/** Pending rows whose dependsOn chain is fully satisfied right now — always computed, regardless of kind. */
|
|
896
|
+
dispatchable: number;
|
|
897
|
+
blockedChains: ScheduleBlockedChain[];
|
|
898
|
+
/** Present on 'blocked' / 'stalled' — ms since lastDispatchAttemptAt. */
|
|
899
|
+
idleMs?: number;
|
|
900
|
+
/** Present on 'paused'. */
|
|
901
|
+
reason?: string | null;
|
|
902
|
+
/** Present on 'launch-blocked'. */
|
|
903
|
+
agentType?: string;
|
|
904
|
+
block?: ScheduleLaunchBlock;
|
|
905
|
+
/** Present on 'saturated'. */
|
|
906
|
+
totalSlots?: number | null;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export interface ScheduleQueueHealthResult {
|
|
910
|
+
/** True when queue.json was unreadable — every other field is absent; render "unknown", never a zero. */
|
|
911
|
+
unknown: boolean;
|
|
912
|
+
reason?: string;
|
|
913
|
+
now?: number;
|
|
914
|
+
verdict?: ScheduleQueueHealthVerdict;
|
|
915
|
+
/** Machine-wide session slot pool — not scoped to the requested cwd. */
|
|
916
|
+
slots?: { inUse: number; total: number; free: number; source: 'env' | 'pool' };
|
|
917
|
+
/** Oldest currently-running job's age in ms, across every project. null when nothing is running. */
|
|
918
|
+
oldestRunningAgeMs?: number | null;
|
|
919
|
+
lastRunAt?: string | null;
|
|
920
|
+
lastDispatchAttemptAt?: string | null;
|
|
921
|
+
}
|
|
922
|
+
|
|
865
923
|
export interface HistoryAggregateRequest {
|
|
866
924
|
fromDate?: string;
|
|
867
925
|
toDate?: string;
|
|
@@ -1621,6 +1679,28 @@ export interface SessionManagerAPI {
|
|
|
1621
1679
|
* `listPersonas` above, which only reads the global directory. Null when neither
|
|
1622
1680
|
* location has the file. */
|
|
1623
1681
|
getPersonaBody: (payload: { cwd: string; name: string }) => Promise<{ path: string; text: string } | null>;
|
|
1682
|
+
/** "What will this Epic actually run as?" — persona alias, overlay-aware
|
|
1683
|
+
* provenance, and evidence-based concrete model id (scheduler run log or
|
|
1684
|
+
* session transcript). Never throws on the main side; a dangling
|
|
1685
|
+
* agentType or missing evidence degrades individual fields to null. Pair
|
|
1686
|
+
* with the renderer's `useEffectiveSettings.ts` scope-chain reader for
|
|
1687
|
+
* the effortLevel/effortSource half — this call only covers the parts
|
|
1688
|
+
* that need main-process filesystem access. */
|
|
1689
|
+
resolveModelInfo: (payload: { cwd: string; agentType: string }) => Promise<{
|
|
1690
|
+
agentType: string;
|
|
1691
|
+
modelAlias: string | null;
|
|
1692
|
+
modelSource: 'persona' | 'persona-overlay' | 'inherit' | 'fallback';
|
|
1693
|
+
resolvedModelId: string | null;
|
|
1694
|
+
resolvedFrom: 'scheduler-run' | 'transcript' | null;
|
|
1695
|
+
effortReachable: boolean;
|
|
1696
|
+
}>;
|
|
1697
|
+
/** The launch-time authority for an Epic-backed session's `--model`: the SAME
|
|
1698
|
+
* resolver (`agentModelResolve.cjs`'s `resolveEpicModel`) chatRunner.cjs's Chat
|
|
1699
|
+
* path calls in-process, reached here so Terminal (`EpicTerminalPane.tsx`, no
|
|
1700
|
+
* fs access) agrees with Chat by calling the identical function. Overlay-aware
|
|
1701
|
+
* (project `.claude/agents/<name>.md` wins over global), never throws — a
|
|
1702
|
+
* dangling agentType or absent/`inherit` model falls back to `'sonnet'`. */
|
|
1703
|
+
resolveEpicModel: (payload: { cwd: string; claudeSessionId: string }) => Promise<string>;
|
|
1624
1704
|
/** Fires after any save/delete/removeOverride — subscribers should re-fetch listPersonas(). */
|
|
1625
1705
|
onChanged: (handler: () => void) => () => void;
|
|
1626
1706
|
};
|
|
@@ -1764,6 +1844,10 @@ export interface SessionManagerAPI {
|
|
|
1764
1844
|
writePrd: (slug: string, body: string) => Promise<{ ok: true; bytesWritten: number } | { ok: false; error: string }>;
|
|
1765
1845
|
listPrds: () => Promise<PrdListItem[]>;
|
|
1766
1846
|
health: () => Promise<ScheduleHealthSnapshot>;
|
|
1847
|
+
/** Queue-health header verdict — reuses classifyQueueHealth so this can
|
|
1848
|
+
* never disagree with the starvation watchdog. `cwd` null/omitted =
|
|
1849
|
+
* machine-wide scope. */
|
|
1850
|
+
queueHealth: (cwd?: string | null) => Promise<ScheduleQueueHealthResult>;
|
|
1767
1851
|
onState: (handler: (snapshot: ScheduleStateSnapshot) => void) => () => void;
|
|
1768
1852
|
/** Heartbeat-driven stall alert: queue holds jobs but 0 running / 0 pending / not paused for a full poll interval. Fires at most once per stall episode. */
|
|
1769
1853
|
onStall: (handler: (event: ScheduleStallEvent) => void) => () => void;
|
|
@@ -1775,6 +1859,13 @@ export interface SessionManagerAPI {
|
|
|
1775
1859
|
retagPrds: (items: RetagPrdItem[]) => Promise<RetagPrdResult>;
|
|
1776
1860
|
/** Stamps a 'quarantined' PRD (no createdVia provenance) as legacy-adopted via the update-prd API, then reconciles it to 'pending'. */
|
|
1777
1861
|
adoptPrd: (slug: string) => Promise<ActionOutcome>;
|
|
1862
|
+
/** Change a PRD's wave disposition (promote to a new head, or attach behind another chain's terminal PRD(s)) — refuses a running/completed row or a dependsOn cycle. */
|
|
1863
|
+
setPrdDisposition: (args: {
|
|
1864
|
+
slug: string;
|
|
1865
|
+
cwd?: string;
|
|
1866
|
+
disposition: 'append' | 'new-head';
|
|
1867
|
+
dependsOn?: string[];
|
|
1868
|
+
}) => Promise<ActionOutcome>;
|
|
1778
1869
|
/** Return the last N completed/failed jobs from queue.json (newest first). */
|
|
1779
1870
|
getHistory: (limit?: number) => Promise<ScheduleHistoryResult>;
|
|
1780
1871
|
};
|
package/src/preload/index.cjs
CHANGED
|
@@ -105,6 +105,8 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
105
105
|
deletePersona: (payload) => ipcRenderer.invoke('agents:delete-persona', payload),
|
|
106
106
|
removeOverride: (payload) => ipcRenderer.invoke('agents:remove-override', payload),
|
|
107
107
|
getPersonaBody: (payload) => ipcRenderer.invoke('agents:get-persona-body', payload),
|
|
108
|
+
resolveModelInfo: (payload) => ipcRenderer.invoke('agents:resolve-model-info', payload),
|
|
109
|
+
resolveEpicModel: (payload) => ipcRenderer.invoke('agents:resolve-epic-model', payload),
|
|
108
110
|
// Fired after any save/delete/removeOverride so every mounted
|
|
109
111
|
// subscriber (Agent Library, Tag Library) re-fetches the same file.
|
|
110
112
|
onChanged: (handler) => {
|
|
@@ -243,6 +245,10 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
243
245
|
writePrd: (slug, body) => ipcRenderer.invoke('schedule:write-prd', { slug, body }),
|
|
244
246
|
listPrds: () => ipcRenderer.invoke('schedule:list-prds'),
|
|
245
247
|
health: () => ipcRenderer.invoke('schedule:health'),
|
|
248
|
+
// Queue-health header: one honest verdict for "why does the queue look
|
|
249
|
+
// stale" (saturated/blocked/idle/stalled/paused/launch-blocked), scoped
|
|
250
|
+
// to `cwd` (null = machine-wide).
|
|
251
|
+
queueHealth: (cwd) => ipcRenderer.invoke('schedule:queue-health', cwd ? { cwd } : {}),
|
|
246
252
|
onState: (handler) => {
|
|
247
253
|
const listener = (_e, payload) => handler(payload);
|
|
248
254
|
ipcRenderer.on('schedule:state', listener);
|
|
@@ -264,6 +270,10 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
264
270
|
// the same update-prd API path the admin route/MCP tool use, then
|
|
265
271
|
// reconciles so the row promotes to 'pending' on the very same call.
|
|
266
272
|
adoptPrd: (slug) => ipcRenderer.invoke('schedule:adopt-prd', { slug }),
|
|
273
|
+
// Change a PRD's wave disposition (promote to a new head, or attach
|
|
274
|
+
// behind another chain's terminal PRD(s)) — see scheduler.cjs's
|
|
275
|
+
// remote.setPrdDisposition for the safety validation this goes through.
|
|
276
|
+
setPrdDisposition: (args) => ipcRenderer.invoke('schedule:set-prd-disposition', args),
|
|
267
277
|
// History — last N completed/failed jobs from queue.json.
|
|
268
278
|
getHistory: (limit) => ipcRenderer.invoke('schedule:get-history', limit !== undefined ? { limit } : {}),
|
|
269
279
|
},
|
package/web/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# web/ — the WEB PRESENCE partition
|
|
2
|
+
|
|
3
|
+
This directory collects every producer of content displayed on **bilko.run**. It is one of the
|
|
4
|
+
four partitions described in
|
|
5
|
+
[`session-manager-operations/architecture/project-partition.md`](../session-manager-operations/architecture/project-partition.md)
|
|
6
|
+
— PRD 1184 made this partition PHYSICAL so the boundary is visible in the tree, not just on
|
|
7
|
+
paper.
|
|
8
|
+
|
|
9
|
+
**The user-facing pages themselves are NOT here.** They live in `~/Projects/Bilko`, a sibling
|
|
10
|
+
repo. This directory only holds the tooling that *produces* what Bilko serves:
|
|
11
|
+
|
|
12
|
+
- `project-pages/` — renders a project's Home/Marketing/Feature/Architecture/Brief pages from a
|
|
13
|
+
saved component library + a summary JSON. Consumed by the `project-home-builder` agent
|
|
14
|
+
persona and by `src/main/lib/projectHomeAdminRoutes.cjs` (an Electron IPC handler that stays
|
|
15
|
+
in `src/main/` — it's DESKTOP HARNESS runtime that *calls* this WEB PRESENCE toolchain, not
|
|
16
|
+
part of it).
|
|
17
|
+
- `manual/` — builds and captures figures for the Field Manual, the paid product sold at
|
|
18
|
+
bilko.run/manual. Source content lives in `session-manager-operations/manual/` (OPERATIONS
|
|
19
|
+
STATE, unmoved); output lands in `~/Projects/Bilko/data/manual/releases/`.
|
|
20
|
+
- `remote-app/` — the phone-remote PWA, the live source of the bundle published at
|
|
21
|
+
`bilko.run/projects/session-manager/`.
|
|
22
|
+
|
|
23
|
+
## Staged for extraction
|
|
24
|
+
|
|
25
|
+
This directory is staged for a future `git subtree split --prefix=web` into its own repo. That
|
|
26
|
+
split has NOT happened yet — this PRD only collected the partition under one path so the split
|
|
27
|
+
becomes mechanical later. Before actually running it, the human still needs to decide:
|
|
28
|
+
|
|
29
|
+
- **How the extracted repo reaches `src/main`'s IPC handlers.** `projectHomeAdminRoutes.cjs`
|
|
30
|
+
currently reads the `project-pages/renderer/dist/` and `project-pages/logic/dist/` bundles by
|
|
31
|
+
a relative path assuming both live in the same repo checkout. Once `web/` is a separate repo,
|
|
32
|
+
something has to publish those bundles somewhere the harness can still read them (a build
|
|
33
|
+
step that vendors them back in, a package dependency, a fetched artifact — undecided).
|
|
34
|
+
- **Who then owns the npm `files` array entries.** `package.json`'s `files` array currently
|
|
35
|
+
lists `web/project-pages/render.cjs`, `web/project-pages/renderer/dist/`,
|
|
36
|
+
`web/project-pages/logic/dist/`, and `web/project-pages/validate-summary.cjs` directly. If
|
|
37
|
+
`web/` moves to its own repo, either the harness repo re-vendors those specific build outputs
|
|
38
|
+
before packing, or the split repo publishes them and the harness repo depends on that package
|
|
39
|
+
— undecided.
|
|
40
|
+
- `web-remote/relay/` is dead code that was deliberately NOT moved here — it stays where it is
|
|
41
|
+
pending a separate decision on whether to delete it.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Bash-invocable CLI wrapping the Stage 0 renderer (render.tsx →
|
|
3
|
-
//
|
|
3
|
+
// web/project-pages/renderer/dist/renderer.cjs, built by
|
|
4
4
|
// `npm run build:project-pages`). Requires that build to have already run —
|
|
5
5
|
// this script does NOT bundle on the fly, since PRD authoring standards
|
|
6
6
|
// forbid unbounded/slow steps inside a headless job.
|
|
7
7
|
//
|
|
8
8
|
// Usage:
|
|
9
|
-
// node
|
|
9
|
+
// node web/project-pages/render.cjs <summary.json> <picks.json> <output dir> <generatedAt>
|
|
10
10
|
//
|
|
11
11
|
// generatedAt (4th arg, required) is an ISO 8601 timestamp string the CALLER
|
|
12
12
|
// stamps — Date.now() is unavailable in some execution contexts (e.g.
|
|
@@ -35,13 +35,13 @@ function fail(message) {
|
|
|
35
35
|
function main() {
|
|
36
36
|
const [summaryPath, picksPath, outDir, generatedAt] = process.argv.slice(2);
|
|
37
37
|
if (!summaryPath || !picksPath || !outDir || !generatedAt) {
|
|
38
|
-
fail('usage: node
|
|
38
|
+
fail('usage: node web/project-pages/render.cjs <summary.json> <picks.json> <output dir> <generatedAt>');
|
|
39
39
|
}
|
|
40
40
|
if (Number.isNaN(Date.parse(generatedAt))) {
|
|
41
41
|
fail(`generatedAt must be an ISO 8601 timestamp, got: ${generatedAt}`);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const bundlePath = path.join(__dirname, '
|
|
44
|
+
const bundlePath = path.join(__dirname, 'renderer', 'dist', 'renderer.cjs');
|
|
45
45
|
if (!fs.existsSync(bundlePath)) {
|
|
46
46
|
fail(`build bundle not found at ${bundlePath} — run "npm run build:project-pages" first`);
|
|
47
47
|
}
|
|
@@ -18738,7 +18738,7 @@ var FONT_DATA = {
|
|
|
18738
18738
|
var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
|
|
18739
18739
|
function fontFace(family, weight, filename) {
|
|
18740
18740
|
const base64 = FONT_DATA[filename];
|
|
18741
|
-
if (!base64) throw new Error(`Missing font data for ${filename} \u2014 rerun
|
|
18741
|
+
if (!base64) throw new Error(`Missing font data for ${filename} \u2014 rerun web/project-pages/generate-font-data.mjs`);
|
|
18742
18742
|
return `@font-face{font-family:'${family}';font-style:normal;font-weight:${weight};font-display:swap;src:url(data:font/woff2;base64,${base64}) format('woff2');}`;
|
|
18743
18743
|
}
|
|
18744
18744
|
function buildFontFaces() {
|
package/{scripts/validate-project-pages-summary.cjs → web/project-pages/validate-summary.cjs}
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Thin CLI wrapper around validateProjectPageSummary (src/renderer/lib/
|
|
3
3
|
// projectPages/summaryValidate.ts), compiled into
|
|
4
|
-
//
|
|
4
|
+
// web/project-pages/logic/dist/logic.cjs by `npm run
|
|
5
5
|
// build:project-pages-logic`. The project-home-builder Epic runs this
|
|
6
6
|
// against its own summary.json before proceeding to Stage 2/3 (see
|
|
7
7
|
// .claude/agents/project-home-builder.md).
|
|
8
8
|
//
|
|
9
|
-
// Usage: node
|
|
9
|
+
// Usage: node web/project-pages/validate-summary.cjs <summary.json path>
|
|
10
10
|
// Exits 0 and prints "valid" on success; exits 1 and prints each error on
|
|
11
|
-
// failure. Same CLI-wrapper pattern as
|
|
11
|
+
// failure. Same CLI-wrapper pattern as web/project-pages/render.cjs.
|
|
12
12
|
//
|
|
13
13
|
// The path argument is used as-is (no allowedRoots validation like
|
|
14
14
|
// config.cjs's validatePath) — this is a standalone local dev/ops script run
|
|
@@ -27,10 +27,10 @@ function fail(message) {
|
|
|
27
27
|
function main() {
|
|
28
28
|
const [summaryPath] = process.argv.slice(2);
|
|
29
29
|
if (!summaryPath) {
|
|
30
|
-
fail('usage: node
|
|
30
|
+
fail('usage: node web/project-pages/validate-summary.cjs <summary.json path>');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const bundlePath = path.join(__dirname, '
|
|
33
|
+
const bundlePath = path.join(__dirname, 'logic', 'dist', 'logic.cjs');
|
|
34
34
|
if (!fs.existsSync(bundlePath)) {
|
|
35
35
|
fail(`build bundle not found at ${bundlePath} — run "npm run build:project-pages-logic" first`);
|
|
36
36
|
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{a9 as e,z as V,ai as m,j as z,b as D,as as b,ap as X,T as _,ar as Z,az as ee,A as T}from"./index-CXFQIPhO.js";import{P as te}from"./Panel-Cj2kw-Zv.js";import{L as ne}from"./ListDetail-DYUZN-x-.js";import"./monaco-editor-BW5C4Iv1.js";function y({children:t,onClick:r,disabled:x,variant:g="default",tooltip:i,type:f="button",className:u}){const v=`px-2 py-0.5 text-xs rounded border transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${g==="primary"?"text-accent border-accent/50 hover:bg-bg-hi":g==="danger"?"text-red-400 hover:text-red-300 border-line hover:border-red-400/50":"text-fg-dim border-line hover:text-fg hover:bg-bg-hi"} ${u??""}`,a=e.jsx("button",{type:f,title:i,onClick:r,disabled:x,className:v,children:t});return i?e.jsx(V,{content:i,align:"bottom-center",children:a}):a}const ae=["inherit","haiku","sonnet","opus"],B=["Read","Grep","Glob","Bash","Write","Edit","WebFetch","WebSearch","Task"],se=["","red","orange","yellow","green","cyan","blue","purple","pink"],G={red:"#c0392b",orange:"#b85c34",yellow:"#c9a227",green:"#6f7d52",cyan:"#3f8f8a",blue:"#4a6fa5",purple:"#7a6a8a",pink:"#b45a80"};function R(t){return{name:t.name,description:t.description??"",tools:t.tools,model:t.model??"inherit",color:t.color??"",tags:t.tags,projects:t.projects??[],action:t.action??"",actionLabel:t.actionLabel??"",body:t.body}}const I={name:"new-agent",description:"",tools:["Read","Grep"],model:"inherit",color:"",tags:[],projects:[],action:"",actionLabel:"",body:""},re=new Set(_.map(t=>t.tag));function oe(t){return t.filter(r=>re.has(r))}function ie(){const[t,r]=m.useState(null),[x,g]=m.useState(null),[i,f]=m.useState(""),[u,c]=m.useState(null),[v,a]=m.useState(!1),[j,N]=m.useState(!1),[O,C]=m.useState(null),[k,s]=m.useState(!1),w=m.useRef(!0);m.useEffect(()=>()=>{w.current=!1},[]);const p=async n=>{try{const d=await window.api.agents.listPersonas();if(!w.current)return;r(d),g(L=>{var P;return n??L??((P=d[0])==null?void 0:P.name)??null})}catch(d){if(!w.current)return;r([]),b.error(d.message||"failed to load agent personas")}};if(m.useEffect(()=>{p()},[]),m.useEffect(()=>window.api.agents.onChanged(()=>p()),[]),m.useEffect(()=>{const n=X();n&&g(n);const d=L=>g(L.detail);return window.addEventListener("sm:select-persona",d),()=>window.removeEventListener("sm:select-persona",d)},[]),t===null)return e.jsx(z,{title:"loading…"});const $=i.toLowerCase(),E=t.filter(n=>!$||n.name.toLowerCase().includes($)||(n.description??"").toLowerCase().includes($)),l=t.find(n=>n.name===x)??t[0]??null,o=u??(l?R(l):null),F=!!u&&!!l&&JSON.stringify(u)!==JSON.stringify(R(l)),S=v,K=n=>{c(null),a(!1),N(!1),C(null),g(n)},W=n=>c(d=>({...d??(l?R(l):I),...n})),M=()=>{c({...I}),a(!0),N(!1)},J=()=>{o&&(c({...o,name:o.name+"-copy"}),a(!0))},H=()=>{c(null),a(!1)},Y=async()=>{if(o){if(!/^[a-z][a-z0-9-]*$/.test(o.name)){b.error('agent name must be lowercase, hyphenated (e.g. "my-agent")');return}s(!0);try{const n={name:o.name,originalName:S||l==null?void 0:l.name,description:o.description,tools:o.tools,model:o.model,color:o.color,tags:oe(o.tags),projects:o.projects,action:o.action,actionLabel:o.actionLabel,body:o.body};await window.api.agents.savePersona(n),b.info(S?`created ${o.name}`:`saved ${o.name}`),c(null),a(!1),await p(o.name)}catch(n){b.error(n.message||"failed to save agent persona")}finally{s(!1)}}},q=async()=>{if(l){s(!0);try{await window.api.agents.deletePersona({name:l.name}),b.info(`deleted ${l.name}`),N(!1),c(null),a(!1),await p()}catch(n){b.error(n.message||"failed to delete agent persona")}finally{s(!1)}}},Q=async n=>{s(!0);try{await window.api.agents.deletePersona({name:n}),b.info(`deleted ${n}`),C(null),x===n&&(c(null),a(!1)),await p()}catch(d){b.error(d.message||"failed to delete agent persona")}finally{s(!1)}},U=async n=>{if(l)try{await window.api.agents.removeOverride({name:l.name,projectName:n}),b.info(`removed ${n}'s override of ${l.name}`),await p(l.name)}catch(d){b.error(d.message||"failed to remove override")}};return e.jsx(te,{toolbar:e.jsxs(e.Fragment,{children:[e.jsxs("span",{className:"text-fg-faint",children:[t.length," persona",t.length===1?"":"s"]}),e.jsx("input",{value:i,onChange:n=>f(n.target.value),placeholder:"filter…",className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg w-40"}),e.jsx("div",{className:"flex-1"}),e.jsx(y,{variant:"primary",onClick:M,children:"+ New agent"})]}),children:e.jsx(ne,{sidebar:e.jsxs("div",{className:"py-1",children:[S&&e.jsx("div",{className:"w-full text-left px-3 py-1 text-xs flex flex-col gap-0.5 bg-bg-hi text-fg border-l-2 border-accent",children:e.jsxs("span",{className:"flex items-center gap-2 justify-between w-full",children:[e.jsx("span",{className:"truncate font-mono",children:o==null?void 0:o.name}),e.jsx("span",{className:"shrink-0 text-[10px] font-semibold text-accent",children:"new"})]})}),E.length===0&&!S?e.jsx("div",{className:"px-3 py-1 text-xs text-fg-faint italic",children:"no agent personas found"}):E.map(n=>{const d=!S&&n.name===x,L=d&&F,P=O===n.name;return e.jsxs("div",{"data-agent-row":n.name,className:`group w-full text-left px-3 py-1 text-xs flex flex-col gap-0.5 border-l-2 cursor-pointer ${d?"bg-bg-hi text-fg border-accent":"text-fg-dim hover:text-fg hover:bg-bg-hi border-transparent"}`,onClick:()=>K(n.name),children:[e.jsxs("span",{className:"flex items-center gap-2 justify-between w-full",children:[e.jsxs("span",{className:"flex items-center gap-1.5 min-w-0",children:[e.jsx("span",{className:"w-2 h-2 rounded-sm shrink-0",style:{background:n.color?G[n.color]??n.color:"var(--fg-faint, #999)",opacity:n.color?1:.35}}),e.jsx("span",{className:"truncate font-mono",children:n.name})]}),e.jsxs("span",{className:"shrink-0 flex items-center gap-1.5",children:[L&&e.jsx("span",{className:"text-[10px] font-semibold text-accent",children:"unsaved"}),!L&&n.overridingProjects.length>0&&e.jsxs(D,{tone:"dim",children:[n.overridingProjects.length," override",n.overridingProjects.length>1?"s":""]}),P?e.jsxs("span",{className:"flex items-center gap-1",onClick:A=>A.stopPropagation(),children:[e.jsx("button",{onClick:()=>Q(n.name),disabled:k,title:`Delete ${n.name}`,className:"text-[10px] font-semibold text-red-400 hover:text-red-300",children:"confirm"}),e.jsx("button",{onClick:()=>C(null),title:"Cancel",className:"text-[10px] text-fg-faint hover:text-fg",children:"cancel"})]}):e.jsx("button",{onClick:A=>{A.stopPropagation(),C(n.name)},title:`Delete ${n.name}`,className:"opacity-0 group-hover:opacity-100 text-fg-faint hover:text-red-400 transition-opacity",children:"×"})]})]}),n.description&&e.jsx("span",{className:"truncate text-fg-faint",children:n.description})]},n.name)})]}),detail:o?e.jsx(le,{obj:o,set:W,saved:l,dirty:F,isNew:S,busy:k,confirmDelete:j,setConfirmDelete:N,onDuplicate:J,onRevert:H,onSave:Y,onDelete:q,onDropOverride:U}):e.jsx(z,{title:"select an agent"})})})}const pe=m.memo(ie);function le({obj:t,set:r,saved:x,dirty:g,isNew:i,busy:f,confirmDelete:u,setConfirmDelete:c,onDuplicate:v,onRevert:a,onSave:j,onDelete:N,onDropOverride:O}){const C=`~/.claude/agents/${t.name}.md`,k=g||i;return e.jsxs("div",{className:"max-w-3xl",children:[e.jsxs("div",{className:"px-4 py-3 border-b border-line flex items-center gap-2 flex-wrap",children:[e.jsx("span",{className:"text-sm font-semibold text-fg font-mono",children:t.name}),i?e.jsx(D,{tone:"accent",children:"unsaved draft"}):g?e.jsx(D,{tone:"warn",children:"edited"}):e.jsx(D,{tone:"good",children:"saved"}),e.jsx("span",{className:"ml-auto text-[11px] text-fg-faint font-mono",children:C})]}),e.jsxs("div",{className:"p-4 space-y-4",children:[e.jsx(h,{label:"name",hint:"Filename on disk — lowercase, hyphenated.",children:e.jsx("input",{value:t.name,onChange:s=>r({name:s.target.value.toLowerCase()}),className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg font-mono"})}),e.jsx(h,{label:"description",hint:"Shown in the list and in agent pickers.",children:e.jsx("input",{value:t.description,onChange:s=>r({description:s.target.value}),className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg"})}),e.jsx(h,{label:"default model",hint:"This persona's default model for its Epics — sets the --model an Epic launches with (Terminal + Chat both honor it). 'inherit' falls back to each view's own default, not this Settings page. Cheap models for read-only personas; opus for judgement calls.",children:e.jsx(de,{options:ae,value:t.model,onChange:s=>r({model:s}),mono:!0})}),e.jsx(h,{label:"tools",hint:`${t.tools.length} of ${B.length} granted. Anything not ticked is refused at call time.`,children:e.jsx("div",{className:"flex flex-wrap gap-1.5",children:B.map(s=>{const w=t.tools.includes(s);return e.jsx("button",{onClick:()=>r({tools:w?t.tools.filter(p=>p!==s):[...t.tools,s]}),className:`px-2 py-0.5 rounded-full text-[11px] font-mono border ${w?"bg-sage/15 text-sage-dark border-sage/30":"bg-bg-hi text-fg-faint border-line"}`,children:s},s)})})}),e.jsx(h,{label:"color",children:e.jsx("div",{className:"flex gap-1.5",children:se.map(s=>e.jsx("button",{title:s||"none",onClick:()=>r({color:s}),className:"w-5 h-5 rounded",style:{background:s?G[s]:"transparent",border:t.color===s?"2px solid var(--accent, #b85c34)":"1px solid var(--line, #555)"}},s||"none"))})}),e.jsx(h,{label:"tags",hint:"Session mission tags this persona is associated with — shown as the default agent on that tag's own page. The first one (in Tag Library order) is the MISSION an Action button's session opens with.",children:e.jsx("div",{className:"flex flex-wrap gap-1.5",children:_.map(s=>{const w=t.tags.includes(s.tag),p=Z(s.tag);return e.jsxs("button",{onClick:()=>r({tags:w?t.tags.filter($=>$!==s.tag):[...t.tags,s.tag]}),className:`px-2 py-0.5 rounded-full text-[11px] font-mono border ${w?`${p.bg} ${p.text} ${p.border?"border-current/30":"border-transparent"}`:"bg-bg-hi text-fg-faint border-line"}`,children:["#",s.tag]},s.tag)})})}),e.jsx(ce,{obj:t,set:r}),e.jsx(h,{label:"definition",hint:"The system prompt this persona runs under.",children:e.jsx("textarea",{rows:9,value:t.body,onChange:s=>r({body:s.target.value}),className:"w-full bg-bg border border-line rounded px-2 py-1.5 text-xs text-fg font-mono leading-relaxed resize-y"})}),e.jsx(h,{label:"project overrides",hint:"Open projects that shadow this definition with a local copy.",children:x&&x.overridingProjects.length>0?e.jsx("div",{className:"flex flex-wrap gap-1.5",children:x.overridingProjects.map(s=>e.jsxs("span",{className:"inline-flex items-center gap-1.5 bg-bg-elev border border-line rounded px-2 py-0.5 text-[11px] font-mono text-fg-dim",children:[s,e.jsx("button",{onClick:()=>O(s),title:"Drop the local copy",className:"text-fg-faint hover:text-fg",children:"×"})]},s))}):e.jsx("div",{className:"text-xs text-fg-faint italic",children:i?"save first to see project overrides.":"no open project overlays this agent — the global definition applies everywhere."})})]}),e.jsx("div",{className:"px-4 py-2.5 border-t border-line flex items-center gap-2 flex-wrap",children:u?e.jsxs(e.Fragment,{children:[e.jsxs("span",{className:"text-xs text-fg-dim",children:["Delete ",e.jsx("strong",{className:"font-mono",children:t.name})," and its file?"]}),e.jsx(y,{variant:"danger",onClick:N,disabled:f,children:"Delete"}),e.jsx(y,{onClick:()=>c(!1),children:"Keep"})]}):e.jsxs(e.Fragment,{children:[e.jsx(y,{onClick:v,disabled:f,children:"Duplicate"}),!i&&e.jsx(y,{variant:"danger",onClick:()=>c(!0),disabled:f,children:"Delete"}),e.jsx("div",{className:"flex-1"}),k&&e.jsx("span",{className:"text-[11px] text-fg-faint",children:"unsaved changes"}),e.jsx(y,{onClick:a,disabled:!k||f,children:"Revert"}),e.jsx(y,{variant:"primary",onClick:j,disabled:!k||f,children:f?"Saving…":i?"Create":"Save"})]})})]})}function ce({obj:t,set:r}){const{projects:x}=ee(),g=x.map(a=>({cwd:a.cwd,name:a.name})),i=new Set(g.map(a=>a.cwd)),f=t.projects.filter(a=>a!==T&&!i.has(a)),u=t.projects.includes(T),c=a=>r({projects:t.projects.includes(a)?t.projects.filter(j=>j!==a):[...t.projects,a]}),v=t.projects.length>0;return e.jsxs(e.Fragment,{children:[e.jsx(h,{label:"action — appears in",hint:"Projects whose Sessions toolbar gets a one-click button for this agent. Pressing it starts a normal session: this persona is the ACTOR, its first tag is the MISSION, and the action text below is the opening goal. Leave empty for an agent that is only picked by hand in the New Session card.",children:e.jsxs("div",{className:"flex flex-wrap gap-1.5",children:[e.jsx("button",{onClick:()=>c(T),className:`px-2 py-0.5 rounded-full text-[11px] font-mono border ${u?"bg-accent/15 text-accent border-accent/40 font-semibold":"bg-bg-hi text-fg-faint border-line"}`,children:"every project"}),g.map(a=>{const j=t.projects.includes(a.cwd);return e.jsx("button",{title:a.cwd,onClick:()=>c(a.cwd),className:`px-2 py-0.5 rounded-full text-[11px] font-mono border ${j?"bg-sage/15 text-sage-dark border-sage/30":"bg-bg-hi text-fg-faint border-line"} ${u&&!j?"opacity-40":""}`,children:a.name},a.cwd)}),f.map(a=>e.jsxs("button",{title:`${a} — not a known project on this machine`,onClick:()=>c(a),className:"px-2 py-0.5 rounded-full text-[11px] font-mono border bg-bg-hi text-fg-faint border-dashed border-line",children:[a," ×"]},a))]})}),v&&e.jsxs(e.Fragment,{children:[e.jsx(h,{label:"action — button label",hint:"Caption on the Sessions toolbar button. Defaults to the agent name.",children:e.jsx("input",{value:t.actionLabel,onChange:a=>r({actionLabel:a.target.value}),placeholder:t.name,className:"w-full bg-bg border border-line rounded px-2 py-1 text-xs text-fg"})}),e.jsx(h,{label:"action — opening instruction",hint:"Sent as the session's first message, under the AIM framing. Falls back to this persona's description when empty; with neither, no button is shown.",children:e.jsx("textarea",{rows:3,value:t.action,onChange:a=>r({action:a.target.value}),placeholder:t.description||`e.g. /builder
|
|
2
|
-
|
|
3
|
-
Check git vs the published package and publish anything new.`,className:"w-full bg-bg border border-line rounded px-2 py-1.5 text-xs text-fg font-mono leading-relaxed resize-y"})})]})]})}function h({label:t,hint:r,children:x}){return e.jsxs("div",{children:[e.jsx("div",{className:"text-[10.5px] font-semibold uppercase tracking-wide text-fg-faint mb-1.5 font-mono",children:t}),x,r&&e.jsx("div",{className:"text-[11px] text-fg-faint mt-1 leading-snug",children:r})]})}function de({options:t,value:r,onChange:x,mono:g}){return e.jsx("div",{className:"flex flex-wrap gap-1",children:t.map(i=>{const f=i===r;return e.jsx("button",{onClick:()=>x(i),className:`px-2.5 py-1 rounded text-xs border ${g?"font-mono":""} ${f?"bg-accent/15 text-accent border-accent/40 font-semibold":"bg-bg-hi text-fg-dim border-line"}`,children:i},i)})})}export{pe as AgentLibrary};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a9 as i,F as r}from"./index-CXFQIPhO.js";function l({value:e,onChange:n,path:o,readOnly:a}){return i.jsx(r,{path:`file://${o}`,defaultLanguage:"markdown",value:e,onChange:t=>n(t??""),theme:"vs-dark",options:{minimap:{enabled:!1},fontSize:13,fontFamily:"JetBrains Mono, ui-monospace, Menlo, monospace",lineNumbers:"on",renderLineHighlight:"line",scrollBeyondLastLine:!1,tabSize:2,wordWrap:"on",readOnly:a,automaticLayout:!0}})}export{l as M};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{ai as d,ay as z,at as H,aA as W,av as N,a9 as e,j as w,P as D,V as q}from"./index-CXFQIPhO.js";import{P as A}from"./Panel-Cj2kw-Zv.js";import{S as G}from"./ScopeSwitcher-DVSyI44-.js";import{S as Q}from"./SaveBar-Cd_7U6Gb.js";import{P as U}from"./Library-DgI9oCCZ.js";import{u as X,m as Y,p as Z,g as ee,E as te,a as se,s as re}from"./settingsSchema-BJVciriw.js";import"./monaco-editor-BW5C4Iv1.js";const ne=["allow","deny","ask"];function ae(r){if(r.trim()==="")return{perms:{},full:{},err:null};try{const n=JSON.parse(r);return{perms:n.permissions??{},full:n,err:null}}catch(n){return{perms:{},full:{},err:n.message}}}function oe(r,n){const a={...r,permissions:n};return JSON.stringify(a,null,2)+`
|
|
2
|
-
`}function ie(){const r=z(),n=H(),a=(n==null?void 0:n.cwd)??null,s=W(t=>t.navFace),i=d.useRef(!1),u=d.useRef(s),[p,c]=d.useState(()=>s==="project"&&a?"project":"user"),[m,P]=d.useState("effective");d.useEffect(()=>{if(u.current!==s){if(u.current=s,i.current){i.current=!1;return}c(s==="project"&&a?"project":"user")}},[s,a]),d.useEffect(()=>{s==="project"&&m==="presets"&&P("effective")},[s,m]);const J=t=>{i.current=!0,c(t)},b=X(D,r,a),f=b[p]??null,j=N(t=>t.files),E=N(t=>t.setDraft),O=N(t=>t.saveJson),I=N(t=>t.revert),[M,y]=d.useState(null),k=e.jsx(q,{options:[{key:"effective",label:"Effective"},{key:"rules",label:"Rules"},...s==="home"?[{key:"presets",label:"Presets"}]:[]],active:m,onChange:P}),$=d.useMemo(()=>{const t=Y(Z(j,b));return ee(t,["permissions"])??{kind:"object",children:{}}},[j,b]),F=(t,l)=>{var R;const o=b[p];if(!o)return;y(null);const h=((R=j[o])==null?void 0:R.draftRaw)??"";let v={};if(h.trim())try{v=JSON.parse(h)}catch{v={}}const B=re(v.permissions??{},t,l),K={...v,permissions:B};E(o,JSON.stringify(K,null,2)+`
|
|
3
|
-
`)};if(!r)return e.jsx(w,{title:"loading…"});if(m==="presets")return e.jsx(A,{toolbar:k,children:e.jsx(U,{})});const C=t=>t!=="user"&&!a,g=f?j[f]:null,{perms:x,full:L,err:T}=g?ae(g.draftRaw):{perms:{},full:{},err:null},S=t=>{f&&(y(null),E(f,oe(L,t)))},_=(t,l)=>{const o=l.trim();if(!o)return;const h=x[t]??[];h.includes(o)||S({...x,[t]:[...h,o]})},V=(t,l)=>{const o=x[t]??[];S({...x,[t]:o.filter((h,v)=>v!==l)})};return e.jsx(A,{toolbar:e.jsxs(e.Fragment,{children:[k,e.jsx("span",{className:"mx-2 text-fg-faint",children:"·"}),e.jsx(G,{scopes:D.scopes,active:p,onChange:J,annotate:t=>{if(C(t))return{exists:!1};const l=b[t];if(!l)return null;const o=j[l];return{exists:(o==null?void 0:o.exists)??!1,dirty:(o==null?void 0:o.dirty)??!1}}}),e.jsx("span",{className:"ml-3 text-fg-faint truncate",children:f??"—"}),e.jsx("div",{className:"flex-1"}),e.jsx("a",{href:"https://code.claude.com/docs/en/settings#permissions",target:"_blank",rel:"noreferrer",className:"text-fg-faint hover:text-fg-dim underline-offset-2 hover:underline",children:"rule syntax ↗"})]}),footer:f&&g?e.jsx(Q,{dirty:g.dirty,busy:g.busy,parseError:M||T||g.parseError,lastSavedAt:g.lastSavedAt,onSave:async()=>{y(null);const t=await O(f);t.ok||y(t.error??"save failed")},onRevert:()=>{y(null),I(f)}}):null,children:C(p)?e.jsx(w,{title:"no active project",hint:"open a tab in a project directory to edit project or local permissions"}):m==="effective"?e.jsx(te,{node:$,targetScope:p,onOverride:F,schema:se().rootedAt(["permissions"]),rootLabel:"permissions"}):f&&g?e.jsxs("div",{className:"p-4 space-y-5 max-w-3xl",children:[e.jsx(le,{value:x.defaultMode??"default",onChange:t=>S({...x,defaultMode:t})}),ne.map(t=>e.jsx(ce,{list:t,rules:x[t]??[],onAdd:l=>_(t,l),onRemove:l=>V(t,l)},t)),e.jsx(de,{dirs:x.additionalDirectories??[],onChange:t=>S({...x,additionalDirectories:t})})]}):e.jsx(w,{title:"resolving…"})})}const ve=d.memo(ie);function le({value:r,onChange:n}){const a=["default","acceptEdits","plan","bypassPermissions"];return e.jsxs("section",{children:[e.jsx("h3",{className:"text-xs text-fg uppercase tracking-wider mb-2",children:"Default Mode"}),e.jsx("div",{className:"inline-flex rounded border border-line overflow-hidden",children:a.map(s=>e.jsx("button",{onClick:()=>n(s),className:`px-2.5 py-1 text-xs transition-colors ${r===s?"bg-bg-hi text-fg":"bg-bg-elev text-fg-dim hover:text-fg hover:bg-bg-hi"}`,children:s},s))})]})}function ce({list:r,rules:n,onAdd:a,onRemove:s}){const[i,u]=d.useState(""),p=r==="allow"?"text-green-400":r==="deny"?"text-red-400":"text-yellow-400";return e.jsxs("section",{children:[e.jsx("h3",{className:`text-xs uppercase tracking-wider mb-2 ${p}`,children:r}),e.jsxs("div",{className:"flex gap-2 mb-2",children:[e.jsx("input",{value:i,onChange:c=>u(c.target.value),onKeyDown:c=>{c.key==="Enter"&&(a(i),u(""))},placeholder:r==="allow"?"e.g. Bash(ls:*) or Read(~/.claude/**)":"e.g. Write(/etc/**)",className:"flex-1 bg-bg-elev border border-line rounded px-2 py-1 text-xs text-fg placeholder-fg-faint"}),e.jsx("button",{onClick:()=>{a(i),u("")},disabled:!i.trim(),className:"px-2.5 py-1 border border-line rounded text-xs text-fg-dim hover:text-fg hover:bg-bg-hi disabled:opacity-40",children:"add"})]}),n.length===0?e.jsx("div",{className:"text-fg-faint text-xs",children:"no rules"}):e.jsx("ul",{className:"space-y-0.5",children:n.map((c,m)=>e.jsxs("li",{className:"flex items-center gap-2 text-xs font-mono bg-bg-elev border border-line rounded px-2 py-1",children:[e.jsx("span",{className:"flex-1 text-fg-dim truncate",children:c}),e.jsx("button",{onClick:()=>s(m),className:"text-fg-faint hover:text-red-400",title:"remove",children:"×"})]},`${m}-${c}`))})]})}function de({dirs:r,onChange:n}){const[a,s]=d.useState("");return e.jsxs("section",{children:[e.jsx("h3",{className:"text-xs text-fg uppercase tracking-wider mb-2",children:"Additional Directories"}),e.jsx("div",{className:"flex gap-2 mb-2",children:e.jsx("input",{value:a,onChange:i=>s(i.target.value),onKeyDown:i=>{i.key==="Enter"&&a.trim()&&(n([...r,a.trim()]),s(""))},placeholder:"/absolute/path/to/dir",className:"flex-1 bg-bg-elev border border-line rounded px-2 py-1 text-xs text-fg placeholder-fg-faint"})}),e.jsx("ul",{className:"space-y-0.5",children:r.map((i,u)=>e.jsxs("li",{className:"flex items-center gap-2 text-xs font-mono bg-bg-elev border border-line rounded px-2 py-1",children:[e.jsx("span",{className:"flex-1 text-fg-dim truncate",children:i}),e.jsx("button",{onClick:()=>n(r.filter((p,c)=>c!==u)),className:"text-fg-faint hover:text-red-400",children:"×"})]},`${u}-${i}`))})]})}export{ve as Permissions};
|