claude-code-session-manager 0.55.1 → 0.57.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.
@@ -486,6 +486,31 @@ const projectBriefCwd = z.object({
486
486
  const projectPagesCwd = z.object({
487
487
  cwd: z.string().min(1).max(4096),
488
488
  }).strict();
489
+
490
+ // ──────────────────────────────────────────── Host on Bilko.run
491
+ // Same validation split: real path validation is config.cjs's validatePath
492
+ // at first fs access in bilkoHost.cjs — these schemas only bound wire shape.
493
+ const bilkoHostCwd = z.object({
494
+ cwd: z.string().min(1).max(4096),
495
+ }).strict();
496
+ const bilkoHostPrepareBundle = z.object({
497
+ cwd: z.string().min(1).max(4096),
498
+ slug: z.string().min(1).max(64).regex(/^[a-z0-9-]+$/, 'slug must be lowercase kebab-case'),
499
+ }).strict();
500
+ const BILKO_HOST_DOCUMENT_SOURCE = z.union([
501
+ z.object({ kind: z.literal('project-page-lens'), lens: z.enum(['home', 'marketing', 'feature', 'architecture']) }).strict(),
502
+ z.object({ kind: z.literal('file'), path: z.string().min(1).max(4096) }).strict(),
503
+ ]);
504
+ const bilkoHostAddDocument = z.object({
505
+ cwd: z.string().min(1).max(4096),
506
+ subpath: z.string().min(1).max(200),
507
+ title: z.string().min(1).max(200),
508
+ source: BILKO_HOST_DOCUMENT_SOURCE,
509
+ }).strict();
510
+ const bilkoHostRemoveDocument = z.object({
511
+ cwd: z.string().min(1).max(4096),
512
+ id: z.string().min(1).max(200),
513
+ }).strict();
489
514
  const projectBriefSetPin = z.object({
490
515
  cwd: z.string().min(1).max(4096),
491
516
  block: PROJECT_BRIEF_BLOCK,
@@ -937,6 +962,10 @@ module.exports = {
937
962
  memoryStale,
938
963
  projectBriefCwd,
939
964
  projectPagesCwd,
965
+ bilkoHostCwd,
966
+ bilkoHostPrepareBundle,
967
+ bilkoHostAddDocument,
968
+ bilkoHostRemoveDocument,
940
969
  projectBriefSetPin,
941
970
  projectBriefUpdate,
942
971
  promptSessionTranscriptAppend,
@@ -215,6 +215,12 @@ function withPathLock(lockPath, task) {
215
215
  */
216
216
  function ensureEpic(cwd, { goalText, tag, reuseByGoal = false, epicId: explicitEpicId, status = 'proposed', openingPrompt = null, mintIfMissing = true, source = null, forceNewEpic = false } = {}) {
217
217
  if (!cwd || typeof cwd !== 'string') throw new Error('ensureEpic: cwd is required');
218
+ // A relative cwd (e.g. a caller passing '.') would otherwise get stored
219
+ // verbatim on the minted Epic's `cwd` field — the renderer's EpicsWorkspace
220
+ // matches Epics to a tab by exact `s.cwd === effectiveCwd` string equality
221
+ // against the tab's absolute path, so a relative value silently produces an
222
+ // Epic that exists on disk but never renders in any project's queue.
223
+ cwd = path.resolve(cwd);
218
224
  return withPathLock(activeIndexPath(cwd), () => {
219
225
  const index = readActiveIndex(cwd);
220
226
 
@@ -48,12 +48,15 @@ const OWNERS = Object.freeze({
48
48
  'scheduler': 'scheduler',
49
49
  // Project Home owns the synthesized Brief (generate + hand-edit).
50
50
  'project-brief': 'project-home',
51
- // The feedback intake folder — written by the RCA hook and feedback filing.
52
- 'feedback': 'feedback',
53
51
  // Browser tab scratch saves (DOM captures, screenshots, recorded flows).
54
52
  'browser': 'browser',
55
53
  // Structured per-tab error log lines (JSONL), tagged for tracing/analysis.
56
54
  'logs': 'logs',
55
+ // Host on Bilko.run tab's deterministic bundle prep (dist/index.html +
56
+ // dist/manifest.json only — publish-state.json and anything the
57
+ // bilko-host-publisher Epic authors beyond dist/ is agent-Write-tool
58
+ // output, same unenforceable-by-construction class as project-pages/output.
59
+ 'bilko-host': 'bilko-host',
57
60
  });
58
61
 
59
62
  /**
@@ -21,7 +21,7 @@ function outputDir(cwd) {
21
21
  return path.join(cwd, 'session-manager-operations', 'project-pages', 'output');
22
22
  }
23
23
 
24
- const LENSES = ['marketing', 'feature', 'architecture'];
24
+ const LENSES = ['home', 'marketing', 'feature', 'architecture'];
25
25
 
26
26
  async function get({ cwd }) {
27
27
  const realCwd = config.validatePath(cwd);
@@ -38,7 +38,7 @@ async function get({ cwd }) {
38
38
  return { output: null };
39
39
  }
40
40
 
41
- const [marketing, feature, architecture] = htmlResults;
41
+ const [home, marketing, feature, architecture] = htmlResults;
42
42
  const generatedAt = typeof manifestResult.data.generatedAt === 'string' ? manifestResult.data.generatedAt : null;
43
43
  if (!generatedAt) {
44
44
  return { output: null };
@@ -46,6 +46,7 @@ async function get({ cwd }) {
46
46
 
47
47
  return {
48
48
  output: {
49
+ home: home.text,
49
50
  marketing: marketing.text,
50
51
  feature: feature.text,
51
52
  architecture: architecture.text,
@@ -1138,11 +1138,26 @@ async function reconcile(state) {
1138
1138
 
1139
1139
  const next = [];
1140
1140
  const seen = new Set();
1141
+ // Terminal (completed/failed) jobs whose .md vanished from disk BEFORE
1142
+ // their row ever crossed HISTORY_RETENTION_MS in partitionJobs — i.e. the
1143
+ // common case, since archiveCompletedPrd() (called right when a job
1144
+ // finishes, scheduler.cjs's `newlyCompletedPrds` handling) moves the PRD
1145
+ // file immediately, while partitionJobs' appendHistory only fires after 7
1146
+ // days. Bug found 2026-08-02: the old code below dropped these rows on the
1147
+ // very next reconcile() pass with NO history write at all — the file-gone
1148
+ // check couldn't tell "already recorded to history.jsonl on a prior pass"
1149
+ // apart from "never recorded anywhere", so a job that completed and had
1150
+ // its file auto-archived within minutes lost its history entirely. Both
1151
+ // Queue and History tabs then read as empty even for projects with real,
1152
+ // successfully-completed work. Collected here; reconciled against
1153
+ // historyTerminalBySlug() below and backfilled before being dropped.
1154
+ const terminalDroppedNeedingHistoryCheck = [];
1141
1155
  for (const job of state.jobs) {
1142
1156
  const p = onDisk.get(job.slug);
1143
1157
  if (!p) {
1144
1158
  // A terminal job whose .md is gone was archived on purpose — dropping
1145
- // its row is the intended end of the auto-archive flow.
1159
+ // its row is the intended end of the auto-archive flow, PROVIDED it's
1160
+ // already durably recorded in history.jsonl (checked below).
1146
1161
  //
1147
1162
  // A PENDING or RUNNING job whose .md is merely not VISIBLE is a
1148
1163
  // different thing entirely, and dropping it destroys queued work: the
@@ -1163,6 +1178,8 @@ async function reconcile(state) {
1163
1178
  seen.add(job.slug);
1164
1179
  next.push({ ...job });
1165
1180
  console.warn(`[scheduler] reconcile: keeping ${job.status} job ${job.slug} — PRD source not visible in any candidate dir`);
1181
+ } else if (job.status === 'completed' || job.status === 'failed') {
1182
+ terminalDroppedNeedingHistoryCheck.push(job);
1166
1183
  }
1167
1184
  continue;
1168
1185
  }
@@ -1198,10 +1215,24 @@ async function reconcile(state) {
1198
1215
  for (const [slug] of onDisk) {
1199
1216
  if (!seen.has(slug)) unmatchedSlugs.push(slug);
1200
1217
  }
1201
- const historyBySlug = unmatchedSlugs.length > 0
1218
+ const historyBySlug = (unmatchedSlugs.length > 0 || terminalDroppedNeedingHistoryCheck.length > 0)
1202
1219
  ? await queueHistory.historyTerminalBySlug()
1203
1220
  : new Map();
1204
1221
 
1222
+ // Backfill: any terminal job dropped above whose slug isn't already in
1223
+ // history.jsonl gets written now, before its row is gone for good. This is
1224
+ // the fix for the bug described where the loop collects
1225
+ // terminalDroppedNeedingHistoryCheck — without it, a job whose PRD file was
1226
+ // auto-archived immediately on completion (the normal case) is lost from
1227
+ // both jobs[] and history.jsonl in the same reconcile pass.
1228
+ if (terminalDroppedNeedingHistoryCheck.length > 0) {
1229
+ const missing = terminalDroppedNeedingHistoryCheck.filter((j) => !historyBySlug.has(j.slug));
1230
+ if (missing.length > 0) {
1231
+ await queueHistory.appendHistory(missing);
1232
+ console.log(`[scheduler] reconcile: backfilled ${missing.length} completed/failed job(s) into history.jsonl (PRD file already archived, row about to drop)`);
1233
+ }
1234
+ }
1235
+
1205
1236
  // Terminal-in-history slugs whose .md file is still on disk: fed into the
1206
1237
  // auto-archive selection pass below (as synthetic completed entries) so
1207
1238
  // their file can still be swept, without ever creating a live job row
@@ -133,7 +133,7 @@ export interface ImportRef {
133
133
  /** Declared owner ids for the single-writer law over a project's
134
134
  * session-manager-operations/ root. Mirrors OWNERS in
135
135
  * src/main/lib/opsOwnership.cjs — one writer per namespace; everyone reads. */
136
- export type OpsWriter = 'epics' | 'scheduler' | 'project-home' | 'feedback' | 'browser';
136
+ export type OpsWriter = 'epics' | 'scheduler' | 'project-home' | 'browser';
137
137
 
138
138
  export interface WriteResult {
139
139
  ok: boolean;
@@ -267,6 +267,9 @@ export interface AgentPersona {
267
267
  color: string | null;
268
268
  /** Epic intent tags (tagLibrary.ts's TAG_LIBRARY) this persona is associated with. */
269
269
  tags: string[];
270
+ /** "<Department> — <Human title>" (e.g. "Engineering — Architect"), free text — drives
271
+ * department grouping in the New Epic Agent picker. Optional/null when not set. */
272
+ title?: string | null;
270
273
  path: string;
271
274
  body: string;
272
275
  /** Project names (basename of cwd) whose currently-open tab overlays this agent. */
@@ -283,6 +286,7 @@ export interface AgentPersonaSaveInput {
283
286
  model: string;
284
287
  color: string;
285
288
  tags: string[];
289
+ title?: string;
286
290
  body: string;
287
291
  }
288
292
 
@@ -987,6 +991,7 @@ export type ProjectBriefUpdateResult =
987
991
 
988
992
  // ────────────────────────────────────────────── Project Pages (PRD 929-932)
989
993
  export interface ProjectPagesOutput {
994
+ home: string;
990
995
  marketing: string;
991
996
  feature: string;
992
997
  architecture: string;
@@ -998,6 +1003,76 @@ export interface ProjectPagesGetResult {
998
1003
  output: ProjectPagesOutput | null;
999
1004
  }
1000
1005
 
1006
+ // ────────────────────────────────────────────── Host on Bilko.run
1007
+ export interface BilkoHostBundleManifest {
1008
+ schemaVersion: 1;
1009
+ slug: string;
1010
+ version: string;
1011
+ builtAt: string;
1012
+ gitSha: string;
1013
+ gitBranch: string;
1014
+ hostKit: { version: string };
1015
+ golden: { path: string; expect: string };
1016
+ health: Record<string, unknown>;
1017
+ bundle: { sizeBytesGz: number; fileCount: number };
1018
+ /** Not part of the host-contract schema itself — this app's own staleness bookkeeping (see BilkoHostGetResult.bundleStale). */
1019
+ documentCount: number;
1020
+ documents: Array<{ subpath: string; title: string }>;
1021
+ }
1022
+
1023
+ export type BilkoHostPublishStatus =
1024
+ | 'not-published'
1025
+ | 'bundle-ready'
1026
+ | 'publishing'
1027
+ | 'published'
1028
+ | 'publish-failed';
1029
+
1030
+ export interface BilkoHostPublishState {
1031
+ status: BilkoHostPublishStatus;
1032
+ slug: string;
1033
+ url?: string;
1034
+ lastAttemptAt?: string;
1035
+ lastError?: string;
1036
+ }
1037
+
1038
+ export type BilkoHostDocumentSource =
1039
+ | { kind: 'project-page-lens'; lens: 'home' | 'marketing' | 'feature' | 'architecture' }
1040
+ | { kind: 'file'; path: string };
1041
+
1042
+ export interface BilkoHostDocument {
1043
+ id: string;
1044
+ /** '' for the root document (dist/index.html); otherwise a '/'-joined lowercase-kebab path, e.g. 'special-doc/01'. */
1045
+ subpath: string;
1046
+ title: string;
1047
+ source: BilkoHostDocumentSource;
1048
+ addedAt: string;
1049
+ /** Where this document resolves once published, given the project's current/derived slug. */
1050
+ url: string;
1051
+ }
1052
+
1053
+ export interface BilkoHostGetResult {
1054
+ hasMarketingPage: boolean;
1055
+ projectName: string;
1056
+ packagePrivate: boolean;
1057
+ packageHomepage: string | null;
1058
+ packageVersion: string;
1059
+ defaultSlug: string;
1060
+ documents: BilkoHostDocument[];
1061
+ /** True when the on-disk dist/ bundle doesn't reflect the current document list — Prepare Bundle (then Publish) is owed. */
1062
+ bundleStale: boolean;
1063
+ bundleManifest: BilkoHostBundleManifest | null;
1064
+ publishState: BilkoHostPublishState | null;
1065
+ }
1066
+
1067
+ export interface BilkoHostPrepareBundleResult {
1068
+ distPath: string;
1069
+ manifest: BilkoHostBundleManifest;
1070
+ }
1071
+
1072
+ export interface BilkoHostDocumentListResult {
1073
+ documents: Array<Omit<BilkoHostDocument, 'url'>>;
1074
+ }
1075
+
1001
1076
  // ────────────────────────────────────────────── Per-subagent memory
1002
1077
  // Stored at ~/.claude/session-manager/agent-memory/<agentId>.json. Keyed by
1003
1078
  // agent name (the .md filename in ~/.claude/agents/), not by workspace cwd.
@@ -1148,7 +1223,7 @@ export interface ChatCreatePrdPayload {
1148
1223
  /** Originating tab id — used at job completion to route a status prompt back into the tab. */
1149
1224
  sourceTabId?: string;
1150
1225
  /** User-selected Feature/Bug tag (PRD 774) carried from the originating PromptTicket. */
1151
- tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder';
1226
+ tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder' | 'bilko-host-publisher';
1152
1227
  }
1153
1228
 
1154
1229
  export type ChatCreatePrdResult =
@@ -1568,6 +1643,16 @@ export interface SessionManagerAPI {
1568
1643
  /** Read output/*.html + manifest.json (or `{output: null}` if none exist yet). Never fires an LLM call. */
1569
1644
  get: (cwd: string) => Promise<ProjectPagesGetResult>;
1570
1645
  };
1646
+ bilkoHost: {
1647
+ /** Read compatibility-gate inputs + any existing bundle/publish state. Never fires an LLM call. */
1648
+ get: (cwd: string) => Promise<BilkoHostGetResult>;
1649
+ /** Stage A: rebuild the whole dist/ tree + dist/manifest.json from the current document list. Pure, no cost, idempotent. */
1650
+ prepareBundle: (cwd: string, slug: string) => Promise<BilkoHostPrepareBundleResult>;
1651
+ /** Add a hosted sub-path document (never the root, which always exists). Does not touch dist/ — Prepare Bundle picks it up. */
1652
+ addDocument: (cwd: string, subpath: string, title: string, source: BilkoHostDocumentSource) => Promise<BilkoHostDocumentListResult>;
1653
+ /** Remove a hosted document (never the root). Only updates documents.json — the file stays live until the next Prepare Bundle + Publish. */
1654
+ removeDocument: (cwd: string, id: string) => Promise<BilkoHostDocumentListResult>;
1655
+ };
1571
1656
  promptSessionTranscript: {
1572
1657
  /** Append one full-text turn to an Epic's durable JSONL transcript. Best-effort — resolves `{ok:false}` rather than throwing on failure. */
1573
1658
  append: (
@@ -343,6 +343,13 @@ contextBridge.exposeInMainWorld('api', {
343
343
  projectPages: {
344
344
  get: (cwd) => ipcRenderer.invoke('project-pages:get', { cwd }),
345
345
  },
346
+ bilkoHost: {
347
+ get: (cwd) => ipcRenderer.invoke('bilko-host:get', { cwd }),
348
+ prepareBundle: (cwd, slug) => ipcRenderer.invoke('bilko-host:prepare-bundle', { cwd, slug }),
349
+ addDocument: (cwd, subpath, title, source) =>
350
+ ipcRenderer.invoke('bilko-host:add-document', { cwd, subpath, title, source }),
351
+ removeDocument: (cwd, id) => ipcRenderer.invoke('bilko-host:remove-document', { cwd, id }),
352
+ },
346
353
  promptSessionTranscript: {
347
354
  append: (cwd, epicId, turn) =>
348
355
  ipcRenderer.invoke('promptSessionTranscript:append', { cwd, epicId, ...turn }),