@khorsheed/dsh-file-preview 0.1.1 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAiB,MAAM,wBAAwB,CAAA;AAGlE,OAAO,EAAU,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC9E,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAIxC,OAAO,KAAK,EAAE,iBAAiB,EAAoB,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAuB,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEnK,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,WAAW,EAAE,kBAAkB,CAAA;KAChC;CACF;AA4HD;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,mBAAmB;IACzD,MAAM,CAAC,MAAM,WAAS;IAEtB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAKjC;IAEF,gDAAgD;IAChD,MAAM,CAAC,QAAQ,CAAC,WAAW,yBAAwB;IAEnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD;iFAC6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsD;IACnF,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkE;IAE5F;;;;;;OAMG;IACH,YACE,GAAG,EAAE,OAAO,EACZ,MAAM,GAAE,iBAAsB,EAC9B,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAO,EAoCnF;IAED,OAAO,KAAK,EAAE,GAEb;IAED;;;;;;OAMG;YACW,UAAU;IAmExB;;;;;;OAMG;IAEH,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe,CAgBlC;IAED;;;;;;;;;;;OAWG;IAEH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB,CAiC1C;IAED;;;;;;;;OAQG;IAEG,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA6DpF;IAED;;;;;;;;;;;;OAYG;IAEG,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgCxF;CACF;eAEc,kBAAkB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAiB,MAAM,wBAAwB,CAAA;AAGlE,OAAO,EAAU,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAC9E,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAIxC,OAAO,KAAK,EAAE,iBAAiB,EAAoB,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAuB,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEnK,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,WAAW,EAAE,kBAAkB,CAAA;KAChC;CACF;AAiJD;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,mBAAmB;IACzD,MAAM,CAAC,MAAM,WAAS;IAEtB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAKjC;IAEF,gDAAgD;IAChD,MAAM,CAAC,QAAQ,CAAC,WAAW,yBAAwB;IAEnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD;iFAC6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsD;IACnF,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkE;IAE5F;;;;;;OAMG;IACH,YACE,GAAG,EAAE,OAAO,EACZ,MAAM,GAAE,iBAAsB,EAC9B,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAO,EAoCnF;IAED,OAAO,KAAK,EAAE,GAEb;IAED;;;;;;OAMG;YACW,UAAU;IAmExB;;;;;;;;;;OAUG;YACW,cAAc;IAiB5B;;;;;;;;OAQG;IAEG,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,CAAC,CAmBjD;IAED;;;;;;;;;;;;;;OAcG;IAEG,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAyCzD;IAED;;;;;;;;OAQG;IAEG,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA6DpF;IAED;;;;;;;;;;;;OAYG;IAEG,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgCxF;CACF;eAEc,kBAAkB"}
@@ -67,7 +67,8 @@ function registerArtifactSkill(ctx) {
67
67
  return;
68
68
  }
69
69
  try {
70
- const skillFile = join(dirname(fileURLToPath(import.meta.url)), '..', 'skills', '3d-artifact', 'SKILL.md');
70
+ const skillDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'skills', '3d-artifact');
71
+ const skillFile = join(skillDir, 'SKILL.md');
71
72
  const raw = readFileSync(skillFile, 'utf8');
72
73
  const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(raw);
73
74
  const name = /^name: (.+)$/m.exec(match?.[1] ?? '')?.[1]?.trim();
@@ -77,7 +78,16 @@ function registerArtifactSkill(ctx) {
77
78
  ctx.logger.warn('file-preview: shipped SKILL.md is malformed — the 3d-artifact skill is not registered');
78
79
  return;
79
80
  }
80
- ctx.effect(() => skills.register({ name, description, content, source: 'runtime' }));
81
+ ctx.effect(() => skills.register({
82
+ name,
83
+ description,
84
+ content,
85
+ source: 'runtime',
86
+ provider: 'file-preview',
87
+ // Content-only (only SKILL.md in the bundle): no `resourceBase`, so the
88
+ // catalog renders a virtual single-SKILL.md node. Add `resourceBase`
89
+ // only when the bundle later gains scripts/assets beside SKILL.md.
90
+ }));
81
91
  }
82
92
  catch (error) {
83
93
  ctx.logger.warn(`file-preview: shipped SKILL.md unreadable (${String(error)}) — the 3d-artifact skill is not registered`);
@@ -145,8 +155,9 @@ function readErrorMessage(error) {
145
155
  }
146
156
  /**
147
157
  * The `filePreview` Remote service: session-scoped read-only file inspection
148
- * for web surfaces. `list` is a pure fold over the session log (no filesystem
149
- * access); `read` resolves the path against the session cwd and serves current
158
+ * for web surfaces. `list` is a fold over the session log, then filtered to the
159
+ * paths that still exist on disk (resolved against the session cwd and stat-ed
160
+ * per call); `read` resolves the path against the session cwd and serves current
150
161
  * text content through `ctx.fs` (or a browser URL for images), capped by
151
162
  * config. Image bytes ride a dedicated host route so the browser loads them
152
163
  * natively without bloating the RPC channel.
@@ -310,31 +321,65 @@ let FilePreviewService = (() => {
310
321
  });
311
322
  res.end(Buffer.from(bytes));
312
323
  }
324
+ /** Resolve the session's recorded display paths against its cwd and keep only
325
+ * those that currently exist as a regular file. The products surfaces fold
326
+ * the session log (which is history, not disk state): a path the log wrote
327
+ * may have been cleaned up since (a temp script the turn deleted), and such
328
+ * a path is not a product to list. Existence is checked fresh on every call
329
+ * (never cached), so a deletion that lands after a fold is visible on the
330
+ * next fetch. Unresolvable/unstat-able paths are treated as absent.
331
+ * @param agent - owning live agent; its session cwd anchors relative paths.
332
+ * @param paths - the display paths to probe.
333
+ * @returns the subset that resolves to an existing regular file.
334
+ */
335
+ async retainExisting(agent, paths) {
336
+ if (paths.length === 0)
337
+ return new Set();
338
+ const cwd = agent.session.header.cwd;
339
+ const existing = new Set();
340
+ for (const path of paths) {
341
+ if (existing.has(path))
342
+ continue;
343
+ try {
344
+ const target = await this.fs.resolve(path, cwd === undefined ? {} : { cwd });
345
+ const info = await this.fs.stat(target);
346
+ if (info?.type === 'file')
347
+ existing.add(path);
348
+ }
349
+ catch {
350
+ // Unresolvable or stat failed: the path is not present to list.
351
+ }
352
+ }
353
+ return existing;
354
+ }
313
355
  /**
314
356
  * List the files one session wrote or edited: the log fold plus any
315
357
  * bash-written files the collector verified (the S2 seam), merged so a
316
- * captured path the fold already knows keeps its log-derived entry.
358
+ * captured path the fold already knows keeps its log-derived entry. Only
359
+ * paths that currently exist on disk are returned — a file the session
360
+ * wrote and later deleted is history, not a product.
317
361
  * @param agent - owning live agent; its session log is the data source.
318
- * @returns first-seen files capped by `maxFiles`, with the log watermark.
362
+ * @returns existing first-seen files capped by `maxFiles`, with the log watermark.
319
363
  */
320
- list(agent) {
321
- const folded = foldFilePreview(agent.session.events, this.resolved.maxFiles);
364
+ async list(agent) {
365
+ const folded = foldFilePreview(agent.session.snapshotEvents(), this.resolved.maxFiles);
322
366
  const captured = this.collector?.captured(agent.session.id);
323
- if (captured === undefined || captured.size === 0)
324
- return folded;
325
- const seen = new Set(folded.entries.map(entry => entry.path));
326
- const extra = [];
327
- for (const [path, write] of captured) {
328
- if (seen.has(path) || folded.entries.length + extra.length >= this.resolved.maxFiles)
329
- continue;
330
- extra.push({ path, op: 'write', seq: write.seq, turn: write.turn, step: write.step, diffs: [] });
367
+ let entries = folded.entries;
368
+ if (captured !== undefined && captured.size > 0) {
369
+ const seen = new Set(entries.map(entry => entry.path));
370
+ const extra = [];
371
+ for (const [path, write] of captured) {
372
+ if (seen.has(path) || entries.length + extra.length >= this.resolved.maxFiles)
373
+ continue;
374
+ extra.push({ path, op: 'write', seq: write.seq, turn: write.turn, step: write.step, diffs: [] });
375
+ }
376
+ entries = [...entries, ...extra];
331
377
  }
332
- if (extra.length === 0)
333
- return folded;
378
+ const existing = await this.retainExisting(agent, entries.map(entry => entry.path));
334
379
  return {
335
- entries: [...folded.entries, ...extra],
380
+ entries: entries.filter(entry => existing.has(entry.path)),
336
381
  asOfSeq: folded.asOfSeq,
337
- truncated: folded.truncated || folded.entries.length + extra.length >= this.resolved.maxFiles,
382
+ truncated: folded.truncated || entries.length >= this.resolved.maxFiles,
338
383
  };
339
384
  }
340
385
  /**
@@ -345,13 +390,16 @@ let FilePreviewService = (() => {
345
390
  * result diff meta) and merging the collector's bash-written captures by
346
391
  * their own turn. The fold is cached per session and invalidated by the log
347
392
  * watermark, so repeated card fetches do not refold. A path touched in two
348
- * turns appears in BOTH turn groups — per-turn attribution is exact.
393
+ * turns appears in BOTH turn groups — per-turn attribution is exact. Only
394
+ * paths that currently exist on disk are returned (a deleted temp script is
395
+ * history, not a product); existence is probed fresh on every call, so the
396
+ * cached fold is filtered per request rather than cached.
349
397
  * @param agent - owning live agent; its session log is the data source.
350
- * @returns per-turn file groups plus the scanned watermark.
398
+ * @returns existing per-turn file groups plus the scanned watermark.
351
399
  */
352
- turnFiles(agent) {
400
+ async turnFiles(agent) {
353
401
  const session = agent.session;
354
- const events = session.events;
402
+ const events = session.snapshotEvents();
355
403
  const asOfSeq = events.length === 0 ? -1 : events[events.length - 1].seq;
356
404
  const cached = this.turnCache.get(session.id);
357
405
  let byTurn;
@@ -386,7 +434,15 @@ let FilePreviewService = (() => {
386
434
  continue;
387
435
  turns.push({ turn, files: files.sort((a, b) => a.seq - b.seq) });
388
436
  }
389
- return { turns, asOfSeq };
437
+ // Filter the assembled groups to paths that still exist on disk (fresh per
438
+ // call — the fold cache is by log watermark, existence moves independently).
439
+ const existing = await this.retainExisting(agent, turns.flatMap(group => group.files.map(file => file.path)));
440
+ return {
441
+ turns: turns
442
+ .map(group => ({ turn: group.turn, files: group.files.filter(file => existing.has(file.path)) }))
443
+ .filter(group => group.files.length > 0),
444
+ asOfSeq,
445
+ };
390
446
  }
391
447
  /**
392
448
  * Read the current content of one recorded file, resolved against the
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "cordis",
8
8
  "file-preview"
9
9
  ],
10
- "version": "0.1.1",
10
+ "version": "0.2.0",
11
11
  "type": "module",
12
12
  "main": "lib/index.js",
13
13
  "types": "lib/types/index.d.ts",
@@ -59,24 +59,25 @@
59
59
  },
60
60
  "devDependencies": {
61
61
  "@deepseek-ai/cordis": "^4.0.1",
62
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.1",
63
- "@deepseek-ai/dsh-fs": "^0.1.1-rc.1",
64
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
65
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
66
- "@deepseek-ai/dsh-loader-smoke": "^0.1.1-rc.1",
67
- "@deepseek-ai/dsh-native-command": "^0.1.1-rc.1",
68
- "@deepseek-ai/dsh-session": "^0.1.1-rc.1",
69
- "@deepseek-ai/dsh-skill": "^0.1.1-rc.1",
70
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
71
- "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.1"
62
+ "@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
63
+ "@deepseek-ai/dsh-fs": "^0.1.2-rc.1",
64
+ "@deepseek-ai/dsh-invariants": "^0.1.2-rc.1",
65
+ "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
66
+ "@deepseek-ai/dsh-loader-smoke": "^0.1.2-rc.1",
67
+ "@deepseek-ai/dsh-native-command": "^0.1.2-rc.1",
68
+ "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
69
+ "@deepseek-ai/dsh-skill": "^0.1.2-rc.1",
70
+ "@deepseek-ai/dsh-tools": "^0.1.2-rc.1",
71
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1",
72
+ "@deepseek-ai/dsh-util-values": "^0.1.2-rc.1"
72
73
  },
73
74
  "dsh": {
74
75
  "bundle": {
75
76
  "patch": "./cordis.patch.yml"
76
77
  },
77
78
  "compat": {
78
- "minHost": "0.1.0-rc.6",
79
- "verifiedHost": "0.1.1-rc.2"
79
+ "minHost": "0.1.2-rc.1",
80
+ "verifiedHost": "0.1.2-rc.1"
80
81
  }
81
82
  }
82
83
  }