@toddzheng024/dscode-bundle 0.7.27 → 0.7.29

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.
Files changed (103) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +10 -10
  3. package/bootstrap.mjs +1 -1
  4. package/cordis.patch.yml +433 -46
  5. package/package.json +280 -245
  6. package/plugins/account/index.mjs +178 -0
  7. package/plugins/account/state.mjs +123 -0
  8. package/plugins/auto-review/index.mjs +8 -5
  9. package/plugins/code-review/index.mjs +1 -1
  10. package/plugins/computer-use/index.mjs +37 -0
  11. package/plugins/cordis-config/plain.mjs +31 -0
  12. package/plugins/dscode/index.mjs +1 -1
  13. package/plugins/exec/cli.mjs +1 -1
  14. package/plugins/grok/index.mjs +28 -24
  15. package/plugins/i18n/messages.mjs +204 -0
  16. package/plugins/memory/index.mjs +1 -1
  17. package/plugins/message-source/kind.mjs +20 -0
  18. package/plugins/openrouter/adapter.mjs +29 -13
  19. package/plugins/openrouter/index.mjs +31 -28
  20. package/plugins/session-bridge/communication.mjs +6 -3
  21. package/plugins/session-bridge/server.mjs +3 -2
  22. package/plugins/session-bridge/tasks.mjs +3 -1
  23. package/plugins/session-cards/index.mjs +1 -1
  24. package/plugins/time-marks/index.mjs +3 -2
  25. package/plugins/time-marks/marks.mjs +6 -5
  26. package/plugins/triggers/config.mjs +1 -1
  27. package/plugins/triggers/tools.mjs +1 -1
  28. package/plugins/tui-tools/doctor.mjs +1 -1
  29. package/plugins/tui-tools/index.mjs +16 -4
  30. package/plugins/ultra/policy.mjs +16 -0
  31. package/vendor/bash/index.js +455 -186
  32. package/vendor/bash/types/background.d.ts +38 -7
  33. package/vendor/bash/types/index.d.ts +20 -4
  34. package/vendor/bash/types/render.d.ts +28 -9
  35. package/vendor/command-goal/index.js +3 -1
  36. package/vendor/deepseek/index.js +1378 -1246
  37. package/vendor/deepseek/types/adapter.d.ts +16 -155
  38. package/vendor/deepseek/types/config.d.ts +127 -0
  39. package/vendor/deepseek/types/defaults.d.ts +24 -0
  40. package/vendor/deepseek/types/file-store.d.ts +5 -3
  41. package/vendor/deepseek/types/files-api.d.ts +12 -9
  42. package/vendor/deepseek/types/image-tokens.d.ts +24 -9
  43. package/vendor/deepseek/types/images.d.ts +35 -0
  44. package/vendor/deepseek/types/index.d.ts +10 -94
  45. package/vendor/deepseek/types/messages-api.d.ts +10 -0
  46. package/vendor/deepseek/types/model-info.d.ts +16 -0
  47. package/vendor/deepseek/types/models.d.ts +4 -0
  48. package/vendor/deepseek/types/replay.d.ts +27 -0
  49. package/vendor/deepseek/types/request-extensions.d.ts +16 -0
  50. package/vendor/deepseek/types/request-files.d.ts +49 -0
  51. package/vendor/deepseek/types/request-pricing.d.ts +32 -20
  52. package/vendor/deepseek/types/serialize.d.ts +17 -82
  53. package/vendor/deepseek/types/sse.d.ts +6 -22
  54. package/vendor/deepseek/types/translate.d.ts +13 -36
  55. package/vendor/deepseek/types/transport.d.ts +15 -0
  56. package/vendor/deepseek/types/types.d.ts +106 -167
  57. package/vendor/deepseek/types/wire-types.d.ts +62 -0
  58. package/vendor/persistent/index.js +9 -3
  59. package/vendor/subagent/index.js +11 -8
  60. package/vendor/subagent/model-selection-settings.js +12 -38
  61. package/vendor/subagent/types/index.d.ts +4 -3
  62. package/vendor/subagent/types/model-selection-settings.d.ts +12 -10
  63. package/vendor/subagent-core/index.js +401 -264
  64. package/vendor/subagent-core/typert.host.js +103 -113
  65. package/vendor/subagent-core/typert.remote-client.d.ts +1 -3
  66. package/vendor/subagent-core/typert.remote-client.js +23 -69
  67. package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
  68. package/vendor/subagent-core/types/archive-admission.js +151 -0
  69. package/vendor/subagent-core/types/assistant-output.d.ts +2 -2
  70. package/vendor/subagent-core/types/catalog.d.ts +17 -10
  71. package/vendor/subagent-core/types/catalog.js +12 -6
  72. package/vendor/subagent-core/types/child-agent.d.ts +8 -5
  73. package/vendor/subagent-core/types/child-agent.js +11 -5
  74. package/vendor/subagent-core/types/continuation-activation.d.ts +17 -1
  75. package/vendor/subagent-core/types/continuation-activation.js +41 -4
  76. package/vendor/subagent-core/types/continuation-messages.d.ts +1 -1
  77. package/vendor/subagent-core/types/continuation-messages.js +7 -3
  78. package/vendor/subagent-core/types/continuation.d.ts +1 -1
  79. package/vendor/subagent-core/types/continuation.js +2 -2
  80. package/vendor/subagent-core/types/control-types.d.ts +29 -36
  81. package/vendor/subagent-core/types/control-types.js +2 -3
  82. package/vendor/subagent-core/types/control.d.ts +2 -28
  83. package/vendor/subagent-core/types/control.js +3 -40
  84. package/vendor/subagent-core/types/index.d.ts +45 -41
  85. package/vendor/subagent-core/types/index.js +46 -82
  86. package/vendor/subagent-core/types/internal.d.ts +2 -2
  87. package/vendor/subagent-core/types/internal.js +2 -2
  88. package/vendor/subagent-core/types/lifecycle.d.ts +1 -1
  89. package/vendor/subagent-core/types/lifecycle.js +5 -3
  90. package/vendor/subagent-core/types/list-children.d.ts +15 -32
  91. package/vendor/subagent-core/types/list-children.js +45 -49
  92. package/vendor/subagent-core/types/out-of-process.d.ts +1 -1
  93. package/vendor/subagent-core/types/projection-types.d.ts +9 -1
  94. package/vendor/subagent-core/types/projection.d.ts +5 -0
  95. package/vendor/subagent-core/types/projection.js +10 -4
  96. package/vendor/subagent-core/types/run-settlement.js +1 -1
  97. package/vendor/subagent-core/types/types.d.ts +2 -2
  98. package/vendor/terminal/index.js +94 -30
  99. package/vendor/tui/lib/index.mjs +2 -1
  100. package/vendor/tui/lib/kernel-panels.mjs +1 -1
  101. package/vendor/tui/lib/render/projection.mjs +50 -36
  102. package/presets/dscode/agent.cordis.yml +0 -321
  103. package/presets/dscode/preset.yml +0 -3
@@ -14,15 +14,18 @@ const activeIntervalSchema = z.object({
14
14
  const projectionSchema = z.object({
15
15
  settledMs: z.number().int().nonnegative(),
16
16
  active: activeIntervalSchema.optional(),
17
- }).strict().transform(({ settledMs, active }) => ({
17
+ lastTurnCompleted: z.boolean().optional(),
18
+ }).strict().transform(({ settledMs, active, lastTurnCompleted }) => ({
18
19
  settledMs,
19
20
  ...active === undefined ? {} : { active },
21
+ ...lastTurnCompleted === undefined ? {} : { lastTurnCompleted },
20
22
  }));
21
23
  const timingStateSchema = z.object({
22
24
  settledMs: z.number().int().nonnegative(),
23
25
  active: activeIntervalSchema.optional(),
24
26
  pendingTurnStart: z.number().int().nonnegative().optional(),
25
27
  descriptorSeen: z.boolean(),
28
+ lastTurnCompleted: z.boolean().optional(),
26
29
  }).strict();
27
30
  /**
28
31
  * Fold turn boundaries around the child's own durable descriptor.
@@ -38,9 +41,10 @@ export const subagentTimingProjectionDefinition = {
38
41
  init: () => ({ descriptorSeen: false, settledMs: 0 }),
39
42
  apply: (state, event) => {
40
43
  if (event.type === 'turn/start') {
44
+ const { lastTurnCompleted: _closed, ...openState } = state;
41
45
  return state.descriptorSeen
42
- ? { ...state, active: { since: event.time, through: event.time } }
43
- : { ...state, pendingTurnStart: event.time };
46
+ ? { ...openState, active: { since: event.time, through: event.time } }
47
+ : { ...openState, pendingTurnStart: event.time };
44
48
  }
45
49
  if (event.type === 'subagent/descriptor') {
46
50
  const activeSince = state.active?.since ?? state.pendingTurnStart;
@@ -65,6 +69,7 @@ export const subagentTimingProjectionDefinition = {
65
69
  return {
66
70
  ...rest,
67
71
  settledMs: state.settledMs + Math.max(0, event.time - active.since),
72
+ lastTurnCompleted: event.data.reason.kind === 'completed',
68
73
  };
69
74
  }
70
75
  if (state.active === undefined)
@@ -76,9 +81,10 @@ export const subagentTimingProjectionDefinition = {
76
81
  view: state => ({
77
82
  settledMs: state.settledMs,
78
83
  ...(state.active === undefined ? {} : { active: state.active }),
84
+ ...(state.lastTurnCompleted === undefined ? {} : { lastTurnCompleted: state.lastTurnCompleted }),
79
85
  }),
80
86
  },
81
- stateVersion: 2,
87
+ stateVersion: 3,
82
88
  };
83
89
  // The cast bridges only the optional-label arm: Zod's optional output
84
90
  // includes explicit `undefined`, which exactOptionalPropertyTypes excludes
@@ -30,7 +30,7 @@ function failureDetail(result) {
30
30
  function runOutcome(result) {
31
31
  switch (result.stopReason) {
32
32
  case 'completed':
33
- return { status: 'completed', output: finalText(result.output) };
33
+ return { status: 'completed', result: finalText(result.output) };
34
34
  case 'aborted':
35
35
  return result.diagnostic === undefined
36
36
  ? { status: 'killed' }
@@ -106,7 +106,7 @@ export interface SubagentRunEndInfo {
106
106
  * {@link SubagentResult.output}; absent on infrastructure rejection or when
107
107
  * the child produced none.
108
108
  */
109
- readonly lastAssistantMessage?: ContentBlock[];
109
+ readonly lastAssistantMessage?: readonly ContentBlock[];
110
110
  }
111
111
  /**
112
112
  * Which START-TIME features a provider supports. Checked by the service before delegating to
@@ -260,7 +260,7 @@ export interface SubagentResult {
260
260
  * are skipped. Without a non-empty message, the output is its accumulated
261
261
  * assistant text stream, or `[]` when the child produced neither.
262
262
  */
263
- readonly output: ContentBlock[];
263
+ readonly output: readonly ContentBlock[];
264
264
  /**
265
265
  * The structured result after a requested `outputSchema` was successfully
266
266
  * satisfied. Requesting a schema does not guarantee presence: a provider can
@@ -1,10 +1,10 @@
1
1
  // dscode-shell-capture-v1
2
2
  // dscode-no-history-expansion-v1
3
- import { createRequire } from "node:module";
4
3
  import { TerminalBackendCleanupError, TerminalError } from "@deepseek-ai/dsh-terminal";
5
4
  import { ENCODING_PREAMBLE, resolvePwshPath } from "@deepseek-ai/dsh-pwsh-local";
6
5
  import z from "@deepseek-ai/schemastery";
7
6
  import { Buffer } from "node:buffer";
7
+ import { createLazyRequire } from "@deepseek-ai/dsh-lazy-require";
8
8
  //#region lib/types/config.js
9
9
  /** Validated configuration for the local PTY backend. */
10
10
  /** Bash dialect default executable. */
@@ -235,7 +235,7 @@ function normalizeTerminalText(text) {
235
235
  //#endregion
236
236
  //#region lib/types/session.js
237
237
  /** Persistent PTY session with bounded output, readiness, and terminal-protocol replies. */
238
- const { Terminal: HeadlessTerminal } = createRequire(import.meta.url)("@xterm/headless");
238
+ const requireHeadless = createLazyRequire("@xterm/headless", import.meta.url);
239
239
  function utf8Tail(text, maxBytes) {
240
240
  if (Buffer.byteLength(text) <= maxBytes) return {
241
241
  text,
@@ -255,34 +255,84 @@ function utf8Tail(text, maxBytes) {
255
255
  truncated: true
256
256
  };
257
257
  }
258
+ const COALESCED_CHUNK_UNITS = 4096;
259
+ /** Retention work is amortized over appended text; reads assemble the retained chunks. */
258
260
  const CAPTURE_MAX_BYTES = 4194304;
259
261
  var BoundedTextBuffer = class {
260
262
  maxBytes;
261
263
  maxLines;
262
- value = "";
264
+ head;
265
+ tail;
266
+ bytes = 0;
267
+ newlines = 0;
268
+ lastCodeUnit = 0;
263
269
  dropped = false;
264
270
  constructor(maxBytes, maxLines) {
265
271
  this.maxBytes = maxBytes;
266
272
  this.maxLines = maxLines;
267
273
  }
274
+ get truncated() {
275
+ return this.dropped;
276
+ }
277
+ get isEmpty() {
278
+ return this.head === void 0;
279
+ }
268
280
  append(text) {
269
281
  if (text.length === 0) return;
270
- this.value += text;
271
- if (this.maxLines !== void 0) {
272
- const lines = this.value.split("\n");
273
- if (lines.length > this.maxLines) {
274
- this.value = lines.slice(lines.length - this.maxLines).join("\n");
275
- this.dropped = true;
276
- }
282
+ text = Buffer.from(text, "utf16le").toString("utf16le");
283
+ this.bytes += Buffer.byteLength(text);
284
+ const tail = this.tail;
285
+ if (tail !== void 0) {
286
+ const last = this.lastCodeUnit;
287
+ const first = text.charCodeAt(0);
288
+ if (last >= 55296 && last <= 56319 && first >= 56320 && first <= 57343) this.bytes -= 2;
289
+ }
290
+ for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1)) this.newlines += 1;
291
+ this.lastCodeUnit = text.charCodeAt(text.length - 1);
292
+ if (tail !== void 0 && tail !== this.head && tail.text.length + text.length <= COALESCED_CHUNK_UNITS) tail.text += text;
293
+ else {
294
+ if (tail !== void 0 && tail.text.length <= COALESCED_CHUNK_UNITS) tail.text = Buffer.from(tail.text, "utf16le").toString("utf16le");
295
+ const chunk = {
296
+ text,
297
+ start: 0,
298
+ next: void 0
299
+ };
300
+ if (tail === void 0) this.head = chunk;
301
+ else tail.next = chunk;
302
+ this.tail = chunk;
303
+ }
304
+ while (this.head !== void 0 && (this.bytes > this.maxBytes || this.maxLines !== void 0 && this.newlines >= this.maxLines)) {
305
+ const head = this.head;
306
+ const first = head.text.charCodeAt(head.start);
307
+ const second = head.start + 1 < head.text.length ? head.text.charCodeAt(head.start + 1) : head.next?.text.charCodeAt(0);
308
+ const paired = first >= 55296 && first <= 56319 && second !== void 0 && second >= 56320 && second <= 57343;
309
+ this.bytes -= paired ? 4 : first < 128 ? 1 : first < 2048 ? 2 : 3;
310
+ if (first === 10) this.newlines -= 1;
311
+ this.advance(paired ? 2 : 1);
312
+ this.dropped = true;
313
+ }
314
+ const head = this.head;
315
+ if (head !== void 0 && head.start >= head.text.length / 2) {
316
+ head.text = Buffer.from(head.text.slice(head.start), "utf16le").toString("utf16le");
317
+ head.start = 0;
318
+ }
319
+ }
320
+ advance(units) {
321
+ while (units > 0 && this.head !== void 0) {
322
+ const head = this.head;
323
+ const count = Math.min(units, head.text.length - head.start);
324
+ head.start += count;
325
+ units -= count;
326
+ if (head.start === head.text.length) this.head = head.next;
277
327
  }
278
- const tail = utf8Tail(this.value, this.maxBytes);
279
- this.value = tail.text;
280
- this.dropped ||= tail.truncated;
328
+ if (this.head === void 0) this.tail = void 0;
281
329
  }
282
330
  consume() {
283
- const delta = this.value;
284
- const truncated = this.dropped;
285
- this.value = "";
331
+ const { text: delta, truncated } = this.snapshot();
332
+ this.head = void 0;
333
+ this.tail = void 0;
334
+ this.bytes = 0;
335
+ this.newlines = 0;
286
336
  this.dropped = false;
287
337
  return {
288
338
  delta,
@@ -290,8 +340,10 @@ var BoundedTextBuffer = class {
290
340
  };
291
341
  }
292
342
  snapshot() {
343
+ const chunks = [];
344
+ for (let chunk = this.head; chunk !== void 0; chunk = chunk.next) chunks.push(chunk.text.slice(chunk.start));
293
345
  return {
294
- text: this.value,
346
+ text: chunks.join(""),
295
347
  truncated: this.dropped
296
348
  };
297
349
  }
@@ -402,6 +454,7 @@ var LocalPtySession = class {
402
454
  this.terminal = terminal;
403
455
  this.config = config;
404
456
  this.pid = terminal.pid;
457
+ const { Terminal: HeadlessTerminal } = requireHeadless();
405
458
  this.emulator = new HeadlessTerminal({
406
459
  cols: config.cols,
407
460
  rows: config.rows,
@@ -661,7 +714,7 @@ var LocalPtySession = class {
661
714
  return;
662
715
  }
663
716
  const elapsed = Date.now() - operation.startedAt;
664
- const startupHasOutput = !this.initializing || this.scrollback.snapshot().text.length > 0;
717
+ const startupHasOutput = !this.initializing || !this.scrollback.isEmpty;
665
718
  const acceptsStdinWait = startupHasOutput && foreground !== void 0 && operation.acceptsStdinWait(foreground.processGroupId, foreground.inputWaiting);
666
719
  if (elapsed >= this.config.exactProbeAfterMs && acceptsStdinWait) {
667
720
  this.settleActive("stdin_read");
@@ -766,7 +819,7 @@ var LocalPtySession = class {
766
819
  settleActive(waitReason, retainOwnership = false) {
767
820
  const operation = this.active;
768
821
  if (operation === void 0) return;
769
- const scrollbackTruncated = this.scrollback.snapshot().truncated;
822
+ const scrollbackTruncated = this.scrollback.truncated;
770
823
  if (retainOwnership) {
771
824
  this.stopPolling();
772
825
  this.activeAbort?.();
@@ -905,15 +958,15 @@ function childEnvironment(spec, dialect) {
905
958
  * input are unreliable under PSReadLine.
906
959
  */
907
960
  const PWSH_PROMPT_SETUP = "function prompt { [Console]::Write([char]27 + ']133;D;' + [int]$LASTEXITCODE + [char]7); 'dsh> ' }";
908
- function spawnArgv(ctx, config, policy) {
961
+ async function spawnArgv(ctx, config, policy, signal) {
909
962
  const argv = [config.shellPath, ...config.shellArgs];
910
963
  if (policy.mode === "danger-full-access") return argv;
911
964
  const sandbox = ctx.get("sandbox");
912
965
  if (sandbox === void 0) throw new Error(`terminal-bash: sandbox mode "${policy.mode}" requires a ctx.sandbox provider in the execution world`);
913
- return sandbox.confine(argv, {
966
+ return (await sandbox.confine(argv, {
914
967
  ...policy,
915
968
  mode: policy.mode
916
- }).argv;
969
+ }, signal)).argv;
917
970
  }
918
971
  async function startupSession(session, dialect, timeoutMs, signal) {
919
972
  let startupOperation;
@@ -965,6 +1018,15 @@ async function startupSession(session, dialect, timeoutMs, signal) {
965
1018
  if (signal !== void 0 && onAbort !== void 0) signal.removeEventListener("abort", onAbort);
966
1019
  }
967
1020
  }
1021
+ /** Reject a failed startup only after its unpublished resources reach quiescence. */
1022
+ async function rejectAfterStartupCleanup(error, cleanup) {
1023
+ try {
1024
+ await cleanup();
1025
+ } catch (cleanupError) {
1026
+ throw new TerminalBackendCleanupError(error, cleanupError);
1027
+ }
1028
+ throw error;
1029
+ }
968
1030
  /** Local shell backend registered under the configured type. */
969
1031
  var BashTerminalBackend = class {
970
1032
  ctx;
@@ -983,7 +1045,8 @@ var BashTerminalBackend = class {
983
1045
  spec.signal?.throwIfAborted();
984
1046
  ensureSandboxModeFence(this.ctx, spec.owner);
985
1047
  const policy = this.ctx.sandboxPolicy.resolve({ session: spec.owner.session });
986
- const argv = spawnArgv(this.ctx, this.config, policy);
1048
+ const argv = await spawnArgv(this.ctx, this.config, policy, spec.signal);
1049
+ spec.signal?.throwIfAborted();
987
1050
  if (argv[0] === void 0) throw new Error("terminal-bash: sandbox returned empty argv");
988
1051
  const terminal = await this.spawnTerminal({
989
1052
  argv,
@@ -991,20 +1054,21 @@ var BashTerminalBackend = class {
991
1054
  env: childEnvironment(spec, this.config.shellDialect),
992
1055
  rows: this.config.rows,
993
1056
  cols: this.config.cols,
1057
+ terminalType: "dumb",
994
1058
  graceMs: this.config.disposeGraceMs,
995
1059
  signal: spec.signal
996
1060
  });
997
- const session = this.createSession(terminal, this.config);
1061
+ let session;
1062
+ try {
1063
+ session = this.createSession(terminal, this.config);
1064
+ } catch (error) {
1065
+ return rejectAfterStartupCleanup(error, () => terminal.terminate());
1066
+ }
998
1067
  try {
999
1068
  await startupSession(session, this.config.shellDialect, this.config.timeoutMs, spec.signal);
1000
1069
  return session;
1001
1070
  } catch (error) {
1002
- try {
1003
- await session.close("PTY startup failed");
1004
- } catch (closeError) {
1005
- throw new TerminalBackendCleanupError(error, closeError);
1006
- }
1007
- throw error;
1071
+ return rejectAfterStartupCleanup(error, () => session.close("PTY startup failed"));
1008
1072
  }
1009
1073
  }
1010
1074
  };
@@ -96,7 +96,8 @@ function listJobs(ctx, caller) {
96
96
  if (jobs === undefined)
97
97
  return [];
98
98
  try {
99
- return jobs.list(caller).map((job) => ({
99
+ // DSH 0.1.7 fences jobs by the owning session id rather than the calling Agent.
100
+ return jobs.list(caller?.session.id).map((job) => ({
100
101
  id: job.id,
101
102
  kind: job.kind,
102
103
  label: job.label,
@@ -120,7 +120,7 @@ export function ModePanel({ current, load, select, close }) {
120
120
  });
121
121
  return createElement(ListFrame, {
122
122
  title: t('panel.mode.title', { current }),
123
- rows: visible.map(row => ({ key: row.id, disabled: row.broken !== undefined, text: `${row.id === current ? '●' : '○'} ${row.name ?? row.id} · ${row.description ?? row.trust}${row.broken === undefined ? '' : ` · broken: ${row.broken}`}` })),
123
+ rows: visible.map(row => ({ key: row.id, disabled: row.broken !== undefined, text: `${row.id === current ? '●' : '○'} ${row.name ?? row.id} · ${row.description ?? row.id}${row.broken === undefined ? '' : ` · broken: ${row.broken}`}` })),
124
124
  cursor, loading, error, query, footer: t('panel.footer.chooseSwitch'),
125
125
  });
126
126
  }
@@ -123,10 +123,29 @@ export function nextEveryTarget(previousTarget, acceptedAt, everySeconds) {
123
123
  const missed = Math.ceil((acceptedAt - previousTarget + 1) / interval);
124
124
  return previousTarget + missed * interval;
125
125
  }
126
- /** Plugin snapshot sources folded into token stats but never rendered as rows. */
127
- const HIDDEN_SNAPSHOT_PLUGINS = new Set(['time-context', 'tmux-context', 'dscode-time-marks']);
128
- /** Plugin prompt sources rendered as full user rows (they ARE the conversation). */
129
- const REMINDER_PLUGINS = new Set(['schedule']);
126
+ /** Snapshot producers folded into token stats but never rendered as rows. */
127
+ const HIDDEN_SNAPSHOT_KINDS = new Set(['time-context', 'tmux-context', 'dscode-time-marks']);
128
+ /** Producers whose prompts are rendered as full user rows (they ARE the conversation). */
129
+ const REMINDER_KINDS = new Set(['schedule']);
130
+ /**
131
+ * The producing subsystem behind one message source.
132
+ *
133
+ * DSH 0.1.7 retired the shared `plugin` kind: every producer declares its own `kind`.
134
+ * A resumed transcript also carries the two older shapes — the raw
135
+ * `{ kind: 'plugin', plugin: '<producer>' }` of a session written before the upgrade and
136
+ * the `plugin:<producer>` kind the v3-to-v4 session migration writes for a producer it
137
+ * does not know — so every rule below asks this function instead of the raw kind.
138
+ */
139
+ function producerKind(source) {
140
+ const legacy = source;
141
+ if (legacy.kind === 'plugin')
142
+ return legacy.plugin ?? '';
143
+ return source.kind.startsWith('plugin:') ? source.kind.slice('plugin:'.length) : source.kind;
144
+ }
145
+ /** One-line account a `notice`-form context committed, when it carries one. */
146
+ function noticeSummary(source) {
147
+ return 'form' in source && source.form === 'notice' ? source.summary : undefined;
148
+ }
130
149
  /** Assemble the effective system prompt from surface nodes: head text plus every later non-empty node. */
131
150
  function assembleSystemPrompt(nodes) {
132
151
  if (nodes.size === 0)
@@ -358,7 +377,7 @@ export function projectEvent(view, event) {
358
377
  // Snapshot injections (time/tmux context) still spend model context but
359
378
  // render nothing; the schedule reminder is a real prompt and renders in
360
379
  // full — the model acts on it, so the transcript must show it.
361
- if (message.source.kind === 'plugin' && HIDDEN_SNAPSHOT_PLUGINS.has(message.source.plugin)) {
380
+ if (HIDDEN_SNAPSHOT_KINDS.has(producerKind(message.source))) {
362
381
  return {
363
382
  ...view,
364
383
  pending,
@@ -373,7 +392,7 @@ export function projectEvent(view, event) {
373
392
  },
374
393
  };
375
394
  }
376
- if (message.source.kind === 'plugin' && REMINDER_PLUGINS.has(message.source.plugin)) {
395
+ if (REMINDER_KINDS.has(producerKind(message.source))) {
377
396
  return {
378
397
  ...view,
379
398
  pending,
@@ -388,11 +407,7 @@ export function projectEvent(view, event) {
388
407
  },
389
408
  };
390
409
  }
391
- const notice = message.source.kind === 'plugin' && message.source.form === 'notice'
392
- ? message.source.summary
393
- : message.source.kind === 'plugin'
394
- ? message.source.plugin
395
- : message.source.kind;
410
+ const notice = noticeSummary(message.source) ?? producerKind(message.source);
396
411
  const summary = boundContextSummary(notice);
397
412
  return {
398
413
  ...view,
@@ -625,18 +640,20 @@ export function projectEvent(view, event) {
625
640
  };
626
641
  }
627
642
  case 'tool/result': {
628
- const block = event.data.message.content[0];
629
- const started = view.anchors.toolStart.get(block.toolCallId);
630
- view.anchors.toolStart.delete(block.toolCallId);
643
+ // DSH 0.1.7 moved the answered call id, the error flag and the result text from
644
+ // the first content block onto the tool-result message itself.
645
+ const result = event.data.message;
646
+ const started = view.anchors.toolStart.get(result.toolCallId);
647
+ view.anchors.toolStart.delete(result.toolCallId);
631
648
  // Deregister the call from its turn's registry so `turn/end` does not
632
649
  // sweep a start that already paired with a result.
633
650
  const turnTools = view.anchors.turnTools.get(event.data.turn);
634
651
  if (turnTools !== undefined) {
635
- turnTools.delete(block.toolCallId);
652
+ turnTools.delete(result.toolCallId);
636
653
  if (turnTools.size === 0)
637
654
  view.anchors.turnTools.delete(event.data.turn);
638
655
  }
639
- const rawText = textOf(block.content);
656
+ const rawText = textOf(result.content);
640
657
  const summary = boundContextSummary(rawText);
641
658
  // The verbose expansion self-serves from the persisted presentation
642
659
  // metadata (diffs, read windows, web sources) with the bounded raw text
@@ -650,9 +667,9 @@ export function projectEvent(view, event) {
650
667
  view.anchors.turnFiles.set(event.data.turn, set);
651
668
  }
652
669
  const entries = view.entries.map((entry) => {
653
- if (entry.kind !== 'tool' || entry.callId !== block.toolCallId)
670
+ if (entry.kind !== 'tool' || entry.callId !== result.toolCallId)
654
671
  return entry;
655
- return { ...entry, state: block.isError === true ? 'error' : 'done', summary, detail };
672
+ return { ...entry, state: result.isError === true ? 'error' : 'done', summary, detail };
656
673
  });
657
674
  return {
658
675
  ...view,
@@ -1172,7 +1189,7 @@ export function replayProjectEvent(acc, event) {
1172
1189
  const origin = acc.claimOrigin.get(message.id);
1173
1190
  const delivery = origin === undefined ? {} : { delivery: origin === 'next-turn' ? 'queued' : 'steered' };
1174
1191
  acc.claimOrigin.delete(message.id);
1175
- if (message.source.kind === 'user' || (message.source.kind === 'plugin' && REMINDER_PLUGINS.has(message.source.plugin))) {
1192
+ if (message.source.kind === 'user' || REMINDER_KINDS.has(producerKind(message.source))) {
1176
1193
  appendReplayEntry(acc, { kind: 'user', text, notice: false, ...delivery, ...(images.length === 0 ? {} : { images }), ...(files.length === 0 ? {} : { files }) });
1177
1194
  acc.stats = {
1178
1195
  ...acc.stats,
@@ -1183,7 +1200,7 @@ export function replayProjectEvent(acc, event) {
1183
1200
  };
1184
1201
  return true;
1185
1202
  }
1186
- if (message.source.kind === 'plugin' && HIDDEN_SNAPSHOT_PLUGINS.has(message.source.plugin)) {
1203
+ if (HIDDEN_SNAPSHOT_KINDS.has(producerKind(message.source))) {
1187
1204
  acc.stats = {
1188
1205
  ...acc.stats,
1189
1206
  contextSegments: {
@@ -1193,11 +1210,7 @@ export function replayProjectEvent(acc, event) {
1193
1210
  };
1194
1211
  return true;
1195
1212
  }
1196
- const notice = message.source.kind === 'plugin' && message.source.form === 'notice'
1197
- ? message.source.summary
1198
- : message.source.kind === 'plugin'
1199
- ? message.source.plugin
1200
- : message.source.kind;
1213
+ const notice = noticeSummary(message.source) ?? producerKind(message.source);
1201
1214
  const summary = boundContextSummary(notice);
1202
1215
  appendReplayEntry(acc, { kind: 'user', text: summary, notice: true });
1203
1216
  acc.stats = {
@@ -1379,16 +1392,16 @@ export function replayProjectEvent(acc, event) {
1379
1392
  return true;
1380
1393
  }
1381
1394
  case 'tool/result': {
1382
- const block = event.data.message.content[0];
1383
- const started = acc.toolStart.get(block.toolCallId);
1384
- acc.toolStart.delete(block.toolCallId);
1395
+ const result = event.data.message;
1396
+ const started = acc.toolStart.get(result.toolCallId);
1397
+ acc.toolStart.delete(result.toolCallId);
1385
1398
  const turnTools = acc.turnTools.get(event.data.turn);
1386
1399
  if (turnTools !== undefined) {
1387
- turnTools.delete(block.toolCallId);
1400
+ turnTools.delete(result.toolCallId);
1388
1401
  if (turnTools.size === 0)
1389
1402
  acc.turnTools.delete(event.data.turn);
1390
1403
  }
1391
- const rawText = textOf(block.content);
1404
+ const rawText = textOf(result.content);
1392
1405
  const summary = boundContextSummary(rawText);
1393
1406
  const detail = toolResultDetail(event.data.meta, rawText);
1394
1407
  if (detail?.kind === 'diff') {
@@ -1399,13 +1412,13 @@ export function replayProjectEvent(acc, event) {
1399
1412
  }
1400
1413
  const update = (entry) => ({
1401
1414
  ...entry,
1402
- state: block.isError === true ? 'error' : 'done',
1415
+ state: result.isError === true ? 'error' : 'done',
1403
1416
  summary,
1404
1417
  detail,
1405
1418
  });
1406
1419
  // Every matching row updates (duplicate callIds included); an id with no
1407
1420
  // registered index is a provable no-op — no full-array fallback scan.
1408
- updateReplayById(acc, acc.toolIndex, block.toolCallId, entry => entry.callId === block.toolCallId, update);
1421
+ updateReplayById(acc, acc.toolIndex, result.toolCallId, entry => entry.callId === result.toolCallId, update);
1409
1422
  acc.stats = {
1410
1423
  ...acc.stats,
1411
1424
  toolMs: acc.stats.toolMs + (started === undefined ? 0 : Math.max(0, event.time - started)),
@@ -1860,10 +1873,11 @@ function dscodeCloseTurn(entries) {
1860
1873
  return [...entries.slice(0, -1), { ...last, turnEnded: true }];
1861
1874
  }
1862
1875
  /**
1863
- * dscode: session-bridge relays are plugin messages that must render as visible
1864
- * user turns (the bridge delivers a peer session's message), so the replay
1865
- * branch admits them alongside direct human prompts.
1876
+ * dscode: session-bridge relays are injected messages that must render as visible
1877
+ * user turns (the bridge delivers a peer session's message), so the replay branch
1878
+ * admits them alongside direct human prompts.
1866
1879
  */
1867
1880
  function dscodeVisibleRelay(message) {
1868
- return message.source.kind === 'plugin' && message.source.plugin === 'dscode-session-bridge' && message.source.form === 'relay';
1881
+ return producerKind(message.source) === 'dscode-session-bridge'
1882
+ && 'form' in message.source && message.source.form === 'relay';
1869
1883
  }