dsh-context-compression-improved 0.2.1 → 0.3.1

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 (80) hide show
  1. package/.gitattributes +1 -1
  2. package/.github/workflows/ci.yml +1 -7
  3. package/CHANGELOG.ja.md +18 -21
  4. package/CHANGELOG.ko.md +17 -21
  5. package/CHANGELOG.md +16 -17
  6. package/CHANGELOG.zh.md +15 -18
  7. package/CONTRIBUTING.md +1 -1
  8. package/README.ja.md +1 -20
  9. package/README.ko.md +12 -30
  10. package/README.md +1 -19
  11. package/README.zh.md +5 -21
  12. package/THIRD_PARTY_NOTICES.md +2 -2
  13. package/docs/compat-0.1.5-report.md +77 -0
  14. package/docs/installation.ja.md +6 -4
  15. package/docs/installation.ko.md +9 -7
  16. package/docs/installation.md +3 -1
  17. package/docs/installation.zh.md +3 -2
  18. package/docs/repair-log.md +54 -33
  19. package/eslint.config.js +2 -2
  20. package/package.json +56 -36
  21. package/packages/selector/README.md +7 -12
  22. package/packages/selector/README.zh.md +6 -13
  23. package/packages/selector/THIRD_PARTY_NOTICES.md +2 -8
  24. package/packages/selector/cordis.patch.yml +7 -5
  25. package/packages/selector/dsh.plugin.json +2 -2
  26. package/packages/selector/lib/client.d.ts +31 -5
  27. package/packages/selector/lib/client.js +100 -82
  28. package/packages/selector/lib/index.d.ts +8 -20
  29. package/packages/selector/lib/index.js +9 -14
  30. package/packages/selector/lib/invariant.js +4 -4
  31. package/packages/selector/lib/pruner.d.ts +13 -4
  32. package/packages/selector/lib/pruner.js +91 -46
  33. package/packages/selector/lib/tail-trim.js +13 -4
  34. package/packages/selector/package.json +23 -23
  35. package/packages/selector/screenshots.json +1 -4
  36. package/packages/selector/src/client/CompressionProfileControls.tsx +5 -8
  37. package/packages/selector/src/client/CompressionProfileSelector.tsx +9 -5
  38. package/packages/selector/src/client/EstimatorControls.tsx +28 -32
  39. package/packages/selector/src/client/index.ts +101 -63
  40. package/packages/selector/src/client/locales.ts +0 -2
  41. package/packages/selector/src/client/preset-options.ts +49 -35
  42. package/packages/selector/src/client/settings-section.tsx +4 -6
  43. package/packages/selector/src/deepseek-v4-tokenizer.ts +0 -5
  44. package/packages/selector/src/index.ts +41 -46
  45. package/packages/selector/src/invariant.ts +5 -5
  46. package/packages/selector/src/pruner.ts +67 -36
  47. package/packages/selector/src/runtime/measurement.ts +6 -2
  48. package/packages/selector/src/runtime/session-events.ts +14 -1
  49. package/packages/selector/src/runtime/tail-trim.ts +4 -4
  50. package/packages/selector/src/runtime/tokenpilot/estimator.ts +34 -3
  51. package/packages/selector/src/runtime/types.ts +1 -1
  52. package/packages/selector/tests/auto-compact.client.spec.tsx +0 -13
  53. package/packages/selector/tests/built/client-artifact.spec.ts +5 -1
  54. package/packages/selector/tests/code-skeleton.client.spec.ts +2 -1
  55. package/packages/selector/tests/custom-contract.client.spec.ts +2 -1
  56. package/packages/selector/tests/estimator-channel.client.spec.tsx +16 -11
  57. package/packages/selector/tests/estimator-route-registration.host.spec.ts +12 -0
  58. package/packages/selector/tests/host-preset-overlay.host.spec.ts +6 -4
  59. package/packages/selector/tests/preset-options-write.client.spec.ts +87 -79
  60. package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +17 -12
  61. package/packages/selector/tests/preset-overlay.host.spec.ts +5 -2
  62. package/packages/selector/tests/profiles.client.spec.tsx +3 -3
  63. package/packages/selector/tests/public/package-contract.client.spec.ts +27 -3
  64. package/packages/selector/tests/runtime/public/public-runtime.spec.ts +103 -81
  65. package/packages/selector/tests/runtime/session-events.spec.ts +1 -1
  66. package/packages/selector/tests/settings-seat.client.spec.ts +86 -0
  67. package/packages/selector/tests/standing-generation.host.spec.ts +88 -71
  68. package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +24 -21
  69. package/pnpm-workspace.yaml +52 -5
  70. package/scripts/capture-profile-baseline.mjs +42 -0
  71. package/scripts/generate-tokenizer-fixtures.py +5 -5
  72. package/scripts/generate-vision-fixtures.py +2 -2
  73. package/scripts/{packed-components-smoke.ts → packed-components-smoke.mjs} +116 -119
  74. package/scripts/{packed-install-e2e.ts → packed-install-e2e.mjs} +188 -160
  75. package/scripts/{verify-release.ts → verify-release.mjs} +70 -94
  76. package/tests/TEST_INVENTORY.md +6 -7
  77. package/scripts/capture-profile-baseline.ts +0 -80
  78. package/tsconfig.scripts.json +0 -13
  79. /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-profiles.jpg +0 -0
  80. /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-settings.png +0 -0
@@ -1,10 +1,10 @@
1
1
  import { _ as COMPRESSION_PROFILES, a as PRUNE_MARKER, c as isValidAutoCompactThresholdPercent, d as resolvePolicy, f as CustomCompressionPolicySchema, g as deepFreeze, h as assertNever, i as DEFAULTS, l as parseContextCompressionSettings, m as resolveCustomPolicy, n as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, o as codePointLength, p as DEFAULT_CUSTOM_COMPRESSION_POLICY, r as ContextCompressionSettingsSchema, s as isCompressionProfile, t as AUTO_COMPACT_THRESHOLD_LIMITS, u as resolveConfig } from "./config.js";
2
- import { a as validatePublishedTailTrim, i as tailTrimStub, n as tailTrimMessage, o as sessionEvents, r as tailTrimRef, t as parseTailTrimRef } from "./tail-trim.js";
2
+ import { a as validatePublishedTailTrim, i as tailTrimStub, n as tailTrimMessage, o as eventBySeq, r as tailTrimRef, s as sessionEvents, t as parseTailTrimRef } from "./tail-trim.js";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { createHash } from "node:crypto";
5
5
  import { Service } from "@deepseek-ai/cordis";
6
6
  import { createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
7
- import { deriveEventMessage } from "@deepseek-ai/dsh-session";
7
+ import { SessionSeq, deriveEventMessage } from "@deepseek-ai/dsh-session";
8
8
  import { readFileSync } from "node:fs";
9
9
  import { Tokenizer } from "@huggingface/tokenizers";
10
10
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -45,11 +45,6 @@ const DEEPSEEK_VISION_TOKENIZER_ARTIFACT = Object.freeze({
45
45
  * Every bundled artifact is registered with independent asset roots, integrity
46
46
  * manifests, and cache entries: one corrupted artifact must never disable the
47
47
  * tokenizer serving the other model family.
48
- *
49
- * This module deliberately sits at the package's `src/` root rather than in
50
- * `src/runtime/`: the build flattens the runtime into `lib/*.js`, so only a
51
- * module one level below the package root resolves `../assets/` identically
52
- * from source and from the published artifact.
53
48
  */
54
49
  const ARTIFACTS = Object.freeze([Object.freeze({
55
50
  origin: DEEPSEEK_V4_TOKENIZER_ARTIFACT,
@@ -374,8 +369,9 @@ function measureForCompaction(ctx, session) {
374
369
  const target = header?.config;
375
370
  const counter = bindCounter(target?.provider, target?.model);
376
371
  const events = sessionEvents(session);
372
+ const eventsBySeq = new Map(events.map((event) => [Number(event.seq), event]));
377
373
  const measuredNodes = measurement.nodes.map((node) => {
378
- const event = events[node.seq];
374
+ const event = eventsBySeq.get(Number(node.seq));
379
375
  if (event === void 0) return {
380
376
  seq: node.seq,
381
377
  count: unavailableTokenCount(`surface node ${String(node.seq)} is missing`)
@@ -1311,6 +1307,30 @@ var Estimator = class {
1311
1307
  return;
1312
1308
  }
1313
1309
  }
1310
+ /**
1311
+ * Resolve the host LLM route. Explicit estimator provider/model win; with
1312
+ * them empty, reuse the route the harness already has configured via the
1313
+ * optional `agentDefaultModel` service's current selection (same seam as
1314
+ * dsh-prime-memory's resolveModelRoute) — the user must not re-enter a
1315
+ * provider/model the host already knows.
1316
+ */
1317
+ resolveHostRoute() {
1318
+ const provider = this.options.estimatorProvider ?? "";
1319
+ const model = this.options.estimatorModel ?? "";
1320
+ if (provider.length > 0 && model.length > 0) return {
1321
+ provider,
1322
+ model
1323
+ };
1324
+ try {
1325
+ const selected = this.ctx.get("agentDefaultModel")?.currentSelection?.();
1326
+ const selectedProvider = selected?.provider ?? "";
1327
+ const selectedModel = selected?.model ?? "";
1328
+ if (selectedProvider.length > 0 && selectedModel.length > 0) return {
1329
+ provider: provider.length > 0 ? provider : selectedProvider,
1330
+ model: model.length > 0 ? model : selectedModel
1331
+ };
1332
+ } catch {}
1333
+ }
1314
1334
  async askHost(system, user, signal) {
1315
1335
  let llm;
1316
1336
  try {
@@ -1319,9 +1339,10 @@ var Estimator = class {
1319
1339
  return;
1320
1340
  }
1321
1341
  if (llm?.stream === void 0) return void 0;
1322
- const provider = this.options.estimatorProvider ?? "";
1323
- const model = this.options.estimatorModel ?? "";
1324
- if (provider.length === 0 || model.length === 0) return void 0;
1342
+ const route = this.resolveHostRoute();
1343
+ if (route === void 0) return void 0;
1344
+ const provider = route.provider;
1345
+ const model = route.model;
1325
1346
  let text = "";
1326
1347
  const stream = llm.stream({
1327
1348
  provider,
@@ -2448,6 +2469,8 @@ var ToolResultPruner = class extends Service {
2448
2469
  });
2449
2470
  /** Consolidated per-session mutable state. */
2450
2471
  state;
2472
+ /** 0.1.5-specific: per-session sets of already-audited native summary seqs. */
2473
+ auditedNativeSummaries = /* @__PURE__ */ new WeakMap();
2451
2474
  constructor(ctx, config = {}) {
2452
2475
  super(ctx, "toolResultPruner");
2453
2476
  ctx.inject(["tools", "systemPrompt"], (recoveryCtx) => {
@@ -2468,19 +2491,9 @@ var ToolResultPruner = class extends Service {
2468
2491
  policyResolutionAudits: /* @__PURE__ */ new WeakMap()
2469
2492
  };
2470
2493
  ctx.on("session/event", (session, event) => {
2494
+ this.scanForSeededNativeSummary(session);
2471
2495
  if (event.type === "compaction/summary") {
2472
- emitCompressionAudit(ctx.logger, {
2473
- schemaVersion: 1,
2474
- kind: "native-auto-compact",
2475
- sessionId: String(session.id),
2476
- manifestEventType: "compaction/summary",
2477
- manifestSeq: event.seq,
2478
- reducer: "llm-summary",
2479
- provider: event.data.provider,
2480
- model: event.data.model,
2481
- tokensBefore: event.data.shadowedTokenCount,
2482
- tokensAfter: null
2483
- });
2496
+ this.emitNativeSummaryAudit(session, event.seq, event.data);
2484
2497
  return;
2485
2498
  }
2486
2499
  if (event.type === "compaction/end") try {
@@ -2500,7 +2513,9 @@ var ToolResultPruner = class extends Service {
2500
2513
  this.auditFailure(agent.session, "fresh", "request-boundary", error);
2501
2514
  ctx.logger.warn("context-compression fresh pass failed open: %o", error);
2502
2515
  }
2503
- return await next();
2516
+ const outcome = await next();
2517
+ this.scanForSeededNativeSummary(agent.session);
2518
+ return outcome;
2504
2519
  } finally {
2505
2520
  if (this.state.activeRequestBoundaries.get(agent.session) === boundary) this.state.activeRequestBoundaries.delete(agent.session);
2506
2521
  }
@@ -2522,9 +2537,6 @@ var ToolResultPruner = class extends Service {
2522
2537
  * @param blocks - tool-result content to measure.
2523
2538
  * @returns total Unicode code points across text blocks.
2524
2539
  */
2525
- measureContent(blocks) {
2526
- return measureContent(blocks);
2527
- }
2528
2540
  /**
2529
2541
  * Apply the configured native head/middle/tail transform.
2530
2542
  * @param blocks - original tool-result content.
@@ -2542,6 +2554,7 @@ var ToolResultPruner = class extends Service {
2542
2554
  * @returns landed replacements and aggregate Unicode-code-point savings.
2543
2555
  */
2544
2556
  pruneSession(session, options = {}) {
2557
+ this.scanForSeededNativeSummary(session);
2545
2558
  const stage = options.stage ?? "pressure";
2546
2559
  const contextWindowTokens = options.contextWindowTokens ?? this.contextWindowForRequest(session);
2547
2560
  const policy = this.activePolicy(session, contextWindowTokens, stage);
@@ -2651,7 +2664,7 @@ var ToolResultPruner = class extends Service {
2651
2664
  const block = located.text;
2652
2665
  let checkpointSeq;
2653
2666
  for (const seq of [...session.surface.nodes].reverse()) {
2654
- const event = events[seq];
2667
+ const event = eventBySeq(events, seq);
2655
2668
  if (event === void 0 || event.type !== "user/message") continue;
2656
2669
  const source = event.data.source;
2657
2670
  if (source === void 0 || source === null) continue;
@@ -2678,10 +2691,10 @@ var ToolResultPruner = class extends Service {
2678
2691
  session.append("user/message", replacement, {
2679
2692
  surfaceOp: {
2680
2693
  op: "replace",
2681
- start: checkpointSeq,
2682
- end: checkpointSeq
2694
+ startSeq: SessionSeq(checkpointSeq),
2695
+ endSeq: SessionSeq(checkpointSeq)
2683
2696
  },
2684
- sourceEventSeqs: [checkpointSeq]
2697
+ sourceEventSeqs: [SessionSeq(checkpointSeq)]
2685
2698
  });
2686
2699
  emitCompressionAudit(this.ctx.logger, {
2687
2700
  schemaVersion: 1,
@@ -3116,7 +3129,7 @@ var ToolResultPruner = class extends Service {
3116
3129
  const measured = new Map(view.measuredNodes.map((node) => [node.seq, node.count]));
3117
3130
  const projectionPrices = new Map(view.nodes.map((node) => [node.seq, node.tokens]));
3118
3131
  for (const seq of [...session.surface.nodes]) {
3119
- const event = events[seq];
3132
+ const event = eventBySeq(events, seq);
3120
3133
  if (event?.type !== "tool/result") continue;
3121
3134
  const shadowedHeuristicTokenCount = projectionPrices.get(seq);
3122
3135
  if (shadowedHeuristicTokenCount === void 0) throw new Error(`surface node ${String(seq)} is absent from the atomic legacy projection`);
@@ -3441,12 +3454,12 @@ var ToolResultPruner = class extends Service {
3441
3454
  const completedSteps = /* @__PURE__ */ new Set();
3442
3455
  for (const event of events) if (event.type === "turn/end") completedTurns.add(event.data.turn);
3443
3456
  else if (event.type === "step/end") completedSteps.add(`${String(event.data.turn)}:${String(event.data.step)}`);
3444
- const firstCompletedSurfaceTurn = session.surface.nodes.map((seq) => events[seq]).filter((event) => (event?.type === "assistant/message" || event?.type === "tool/result") && completedTurns.has(event.data.turn)).reduce((first, event) => first === void 0 ? event.data.turn : Math.min(first, event.data.turn), void 0);
3457
+ const firstCompletedSurfaceTurn = session.surface.nodes.map((seq) => eventBySeq(events, seq)).filter((event) => (event?.type === "assistant/message" || event?.type === "tool/result") && completedTurns.has(event.data.turn)).reduce((first, event) => first === void 0 ? event.data.turn : Math.min(first, event.data.turn), void 0);
3445
3458
  const nodes = [...session.surface.nodes];
3446
3459
  for (let index = 0; index < nodes.length; index++) {
3447
3460
  const assistantSeq = nodes[index];
3448
3461
  if (assistantSeq === void 0) continue;
3449
- const assistant = events[assistantSeq];
3462
+ const assistant = eventBySeq(events, assistantSeq);
3450
3463
  if (assistant?.type !== "assistant/message" || assistant.data.interrupted === true || assistant.data.message.content.length === 0 || assistant.data.message.content.some((block) => block.type !== "tool-call") || assistant.data.turn === firstCompletedSurfaceTurn || !completedTurns.has(assistant.data.turn) || !completedSteps.has(`${String(assistant.data.turn)}:${String(assistant.data.step)}`)) continue;
3451
3464
  const calls = assistant.data.message.content;
3452
3465
  if (calls.some((call) => call.name === "context_compression_retrieve")) continue;
@@ -3483,8 +3496,13 @@ var ToolResultPruner = class extends Service {
3483
3496
  if (stubCount.kind !== "exact-tokenizer" || stubCount.tokenizerId !== surfaceCount.tokenizerId || stubCount.tokenizerRevision !== surfaceCount.tokenizerRevision || stubCount.tokens <= 0 || tokensBefore - stubCount.tokens < policy.historyMinReclaimTokens) continue;
3484
3497
  const heuristicTokens = shadowedSeqs.reduce((sum, seq) => sum + (heuristic.get(seq) ?? 0), 0);
3485
3498
  const range = {
3486
- start: assistantSeq,
3487
- end: resultSeqs.at(-1) ?? assistantSeq
3499
+ start: SessionSeq(assistantSeq),
3500
+ end: SessionSeq(resultSeqs.at(-1) ?? assistantSeq)
3501
+ };
3502
+ const surfaceRange = {
3503
+ op: "replace",
3504
+ startSeq: range.start,
3505
+ endSeq: range.end
3488
3506
  };
3489
3507
  if (!this.reserveTailTrimBoundaryAttempt(session)) {
3490
3508
  this.auditComponent(session, policy, "tail-trim", "pressure", "skipped", "already-attempted-at-request-boundary", {
@@ -3502,10 +3520,7 @@ var ToolResultPruner = class extends Service {
3502
3520
  let replacement;
3503
3521
  try {
3504
3522
  replacement = session.append("user/message", tailTrimMessage(stub), {
3505
- surfaceOp: {
3506
- op: "replace",
3507
- ...range
3508
- },
3523
+ surfaceOp: surfaceRange,
3509
3524
  sourceEventSeqs: [manifest.seq, ...shadowedSeqs]
3510
3525
  });
3511
3526
  } catch (error) {
@@ -3619,10 +3634,10 @@ var ToolResultPruner = class extends Service {
3619
3634
  });
3620
3635
  const manifest = session.append("compaction/prune", {
3621
3636
  shadowedRange: {
3622
- start: candidate.seq,
3623
- end: candidate.seq
3637
+ start: SessionSeq(candidate.seq),
3638
+ end: SessionSeq(candidate.seq)
3624
3639
  },
3625
- shadowedSeqs: [candidate.seq],
3640
+ shadowedSeqs: [SessionSeq(candidate.seq)],
3626
3641
  shadowedTokenCount: candidate.shadowedHeuristicTokenCount
3627
3642
  });
3628
3643
  let replacement;
@@ -3633,10 +3648,10 @@ var ToolResultPruner = class extends Service {
3633
3648
  }, {
3634
3649
  surfaceOp: {
3635
3650
  op: "replace",
3636
- start: candidate.seq,
3637
- end: candidate.seq
3651
+ startSeq: SessionSeq(candidate.seq),
3652
+ endSeq: SessionSeq(candidate.seq)
3638
3653
  },
3639
- sourceEventSeqs: [candidate.seq]
3654
+ sourceEventSeqs: [SessionSeq(candidate.seq)]
3640
3655
  });
3641
3656
  } catch (error) {
3642
3657
  this.auditPublicationFailure(session, plan.stage, plan.component, manifest.seq, error);
@@ -3763,6 +3778,36 @@ var ToolResultPruner = class extends Service {
3763
3778
  ...detail
3764
3779
  });
3765
3780
  }
3781
+ /** Emit the native-auto-compact audit for one summary manifest, once. */
3782
+ emitNativeSummaryAudit(session, manifestSeq, data) {
3783
+ let audited = this.auditedNativeSummaries.get(session);
3784
+ if (audited === void 0) {
3785
+ audited = /* @__PURE__ */ new Set();
3786
+ this.auditedNativeSummaries.set(session, audited);
3787
+ }
3788
+ if (audited.has(manifestSeq)) return;
3789
+ audited.add(manifestSeq);
3790
+ emitCompressionAudit(this.ctx.logger, {
3791
+ schemaVersion: 1,
3792
+ kind: "native-auto-compact",
3793
+ sessionId: String(session.id),
3794
+ manifestEventType: "compaction/summary",
3795
+ manifestSeq,
3796
+ reducer: "llm-summary",
3797
+ provider: data.provider === void 0 ? "unknown" : String(data.provider),
3798
+ model: data.model === void 0 ? "unknown" : String(data.model),
3799
+ tokensBefore: typeof data.shadowedTokenCount === "number" ? data.shadowedTokenCount : null,
3800
+ tokensAfter: null
3801
+ });
3802
+ }
3803
+ /**
3804
+ * 0.1.5 commits Native auto-compact by reopening the Session with a seed
3805
+ * log; seed events never reach the `session/event` firehose, so scan the
3806
+ * snapshot for summary manifests the live listener could not observe.
3807
+ */
3808
+ scanForSeededNativeSummary(session) {
3809
+ for (const event of sessionEvents(session)) if (event.type === "compaction/summary") this.emitNativeSummaryAudit(session, event.seq, event.data);
3810
+ }
3766
3811
  auditFailure(session, stage, operation, error) {
3767
3812
  emitCompressionAudit(this.ctx.logger, {
3768
3813
  schemaVersion: 1,
@@ -9,7 +9,16 @@ import { createUserMessage } from "@deepseek-ai/dsh-llm";
9
9
  */
10
10
  function sessionEvents(session) {
11
11
  const snapshot = session.snapshotEvents;
12
- return typeof snapshot === "function" ? snapshot.call(session) : session.events;
12
+ if (typeof snapshot === "function") return snapshot.call(session);
13
+ return session.events ?? [];
14
+ }
15
+ /**
16
+ * Resolve one surface-node seq to its event. 0.1.5 surface nodes are
17
+ * positional and may replace earlier ranges, so a node's seq is not
18
+ * guaranteed to equal the snapshot array index: always resolve by seq.
19
+ */
20
+ function eventBySeq(events, seq) {
21
+ return events[Number(seq)];
13
22
  }
14
23
  //#endregion
15
24
  //#region src/runtime/tail-trim.ts
@@ -60,9 +69,9 @@ function tailTrimMessage(stub) {
60
69
  function validatePublishedTailTrim(session, manifestSeq) {
61
70
  const events = sessionEvents(session);
62
71
  const manifest = events[manifestSeq];
63
- if (manifest?.type !== "compaction/prune" || manifest.data.shadowedSeqs.length < 2 || manifest.data.shadowedSeqs.length > MAX_ROOTS || manifest.data.shadowedSeqs[0] !== manifest.data.shadowedRange.start || manifest.data.shadowedSeqs.at(-1) !== manifest.data.shadowedRange.end || new Set(manifest.data.shadowedSeqs).size !== manifest.data.shadowedSeqs.length) return null;
72
+ if (manifest?.type !== "compaction/prune" || manifest.data.shadowedSeqs.length < 2 || manifest.data.shadowedSeqs.length > MAX_ROOTS || manifest.data.shadowedSeqs[0] !== Number(manifest.data.shadowedRange.start) || manifest.data.shadowedSeqs.at(-1) !== Number(manifest.data.shadowedRange.end) || new Set(manifest.data.shadowedSeqs).size !== manifest.data.shadowedSeqs.length) return null;
64
73
  const replacement = events[manifestSeq + 1];
65
- if (replacement?.type !== "user/message" || replacement.seq !== manifest.seq + 1 || replacement.data.source.kind !== "plugin" || replacement.data.source.plugin !== "dsh-context-compression-improved-runtime" || replacement.surfaceOp === void 0 || replacement.surfaceOp === "append" || replacement.surfaceOp.start !== manifest.data.shadowedRange.start || replacement.surfaceOp.end !== manifest.data.shadowedRange.end || !sameNumbers(replacement.sourceEventSeqs, [manifest.seq, ...manifest.data.shadowedSeqs]) || replacement.data.content.length !== 1 || replacement.data.content[0]?.type !== "text") return null;
74
+ if (replacement?.type !== "user/message" || replacement.seq !== manifest.seq + 1 || replacement.data.source.kind !== "plugin" || replacement.data.source.plugin !== "dsh-context-compression-improved-runtime" || replacement.surfaceOp === void 0 || replacement.surfaceOp === "append" || replacement.surfaceOp.startSeq !== manifest.data.shadowedRange.start || replacement.surfaceOp.endSeq !== manifest.data.shadowedRange.end || !sameNumbers(replacement.sourceEventSeqs, [manifest.seq, ...manifest.data.shadowedSeqs]) || replacement.data.content.length !== 1 || replacement.data.content[0]?.type !== "text") return null;
66
75
  const tracedRoots = manifest.data.shadowedSeqs.map((seq) => uniqueAppendRoot(session, seq, manifestSeq));
67
76
  if (tracedRoots.some((root) => root === null)) return null;
68
77
  const sourceEventSeqs = tracedRoots;
@@ -133,4 +142,4 @@ function sameNumbers(left, right) {
133
142
  return left !== void 0 && left.length === right.length && left.every((value, index) => value === right[index]);
134
143
  }
135
144
  //#endregion
136
- export { validatePublishedTailTrim as a, tailTrimStub as i, tailTrimMessage as n, sessionEvents as o, tailTrimRef as r, parseTailTrimRef as t };
145
+ export { validatePublishedTailTrim as a, tailTrimStub as i, tailTrimMessage as n, eventBySeq as o, tailTrimRef as r, sessionEvents as s, parseTailTrimRef as t };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-compression-improved",
3
- "version": "0.1.1",
3
+ "version": "0.1.0",
4
4
  "description": "One-install context-compression selector bundle for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -40,9 +40,8 @@
40
40
  "client": {
41
41
  "inject": [
42
42
  "@deepseek-ai/dsh-client-locale",
43
- "@deepseek-ai/dsh-client-runtime",
44
- "@deepseek-ai/dsh-client-ui-settings",
45
- "@deepseek-ai/dsh-client-ui-workspace"
43
+ "@deepseek-ai/dsh-client-ui-slots",
44
+ "@deepseek-ai/dsh-client-ui-settings"
46
45
  ],
47
46
  "platform": "web"
48
47
  },
@@ -69,7 +68,7 @@
69
68
  "author": "WilliamShi666",
70
69
  "engines": {
71
70
  "node": "^22.19.0 || >=24.0.0",
72
- "dsh": ">=0.1.1-rc.2 <0.2.0-0"
71
+ "dsh": ">=0.1.5-alpha.1 <0.2.0-0"
73
72
  },
74
73
  "keywords": [
75
74
  "deepseek",
@@ -91,24 +90,25 @@
91
90
  "@deepseek-ai/cordis": ">=4.0.1 <5",
92
91
  "@deepseek-ai/cordis-plugin-include": ">=1.0.6 <2",
93
92
  "@deepseek-ai/cordis-plugin-loader": ">=1.0.2 <2",
94
- "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2 <0.2.0",
95
- "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2 <0.2.0",
96
- "@deepseek-ai/dsh-command-compact": ">=0.1.1-rc.2 <0.2.0",
97
- "@deepseek-ai/dsh-compaction": ">=0.1.1-rc.2 <0.2.0",
98
- "@deepseek-ai/dsh-compaction-basic": ">=0.1.1-rc.2 <0.2.0",
99
- "@deepseek-ai/dsh-client-locale": ">=0.1.1-rc.2 <0.2.0",
100
- "@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2 <0.2.0",
101
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.1-rc.2 <0.2.0",
102
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.1-rc.2 <0.2.0",
103
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.1-rc.2 <0.2.0",
104
- "@deepseek-ai/dsh-client-ui-workspace": ">=0.1.1-rc.2 <0.2.0",
105
- "@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2 <0.2.0",
106
- "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2 <0.2.0",
107
- "@deepseek-ai/dsh-session": ">=0.1.1-rc.2 <0.2.0",
108
- "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2 <0.2.0",
109
- "@deepseek-ai/dsh-system-prompt": ">=0.1.1-rc.2 <0.2.0",
110
- "@deepseek-ai/dsh-token-meter": ">=0.1.1-rc.2 <0.2.0",
111
- "@deepseek-ai/dsh-tools": ">=0.1.1-rc.2 <0.2.0",
93
+ "@deepseek-ai/dsh-agent": ">=0.1.5-rc.2 <0.2.0-0",
94
+ "@deepseek-ai/dsh-agent-presets": ">=0.1.5-rc.2 <0.2.0-0",
95
+ "@deepseek-ai/dsh-command-compact": ">=0.1.5-rc.2 <0.2.0-0",
96
+ "@deepseek-ai/dsh-compaction": ">=0.1.5-rc.2 <0.2.0-0",
97
+ "@deepseek-ai/dsh-compaction-basic": ">=0.1.5-rc.2 <0.2.0-0",
98
+ "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.2 <0.2.0-0",
99
+ "@deepseek-ai/dsh-client-store": ">=0.1.5-rc.2 <0.2.0-0",
100
+ "@deepseek-ai/dsh-client-ui-session": ">=0.1.5-rc.2 <0.2.0",
101
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-rc.2 <0.2.0-0",
102
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.2 <0.2.0-0",
103
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.2 <0.2.0-0",
104
+ "@deepseek-ai/dsh-client-ui-workspace": ">=0.1.5-rc.2 <0.2.0-0",
105
+ "@deepseek-ai/dsh-invariants": ">=0.1.5-rc.2 <0.2.0-0",
106
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.2 <0.2.0-0",
107
+ "@deepseek-ai/dsh-session": ">=0.1.5-rc.2 <0.2.0-0",
108
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.2 <0.2.0-0",
109
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.5-rc.2 <0.2.0-0",
110
+ "@deepseek-ai/dsh-token-meter": ">=0.1.5-rc.2 <0.2.0-0",
111
+ "@deepseek-ai/dsh-tools": ">=0.1.5-rc.2 <0.2.0-0",
112
112
  "@deepseek-ai/schemastery": ">=3.18.1 <4",
113
113
  "react": ">=18 <19"
114
114
  }
@@ -1,6 +1,3 @@
1
1
  {
2
- "screenshots": [
3
- "assets/screenshots/context-compression-selector-settings.png",
4
- "assets/screenshots/context-compression-selector-profiles.jpg"
5
- ]
2
+ "screenshots": []
6
3
  }
@@ -22,16 +22,13 @@ import type { CompressionProfileSelectorProps } from './CompressionProfileSelect
22
22
  import { CustomPolicyEditor, editableCustom } from './CustomPolicyEditor.tsx'
23
23
 
24
24
  export function CompressionProfileControls({
25
- useCompression, useSessions, select, saveCustom, resetCustom, t, showCustomEditor,
25
+ useCompression, select, saveCustom, resetCustom, t, showCustomEditor,
26
26
  }: CompressionProfileSelectorProps & { showCustomEditor: boolean }) {
27
27
  const state = useCompression(snapshot => snapshot)
28
- const currentPreset = useSessions((sessions) => {
29
- const current = sessions.current
30
- return current === undefined ? undefined : sessions.byId[current]?.agentPreset
31
- })
32
- // A profile is Host-global. A cold Session may not have reported a live
33
- // capability yet, so only Minimal blocks initial configuration.
34
- const contextCompressionAvailable = currentPreset !== 'minimal'
28
+ // Harness 0.1.5 dropped browser-side agentPreset from session summaries; see
29
+ // the section component note. Only Minimal used to block initial
30
+ // configuration, and that signal is no longer available client-side.
31
+ const contextCompressionAvailable = true
35
32
  const [open, setOpen] = useState(false)
36
33
  const [saving, setSaving] = useState(false)
37
34
  const [saveError, setSaveError] = useState<string | null>(null)
@@ -8,8 +8,8 @@
8
8
  * @module dsh-context-compression-improved/client/CompressionProfileSelector
9
9
  */
10
10
 
11
- import type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'
12
- import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
11
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
12
+ import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
13
13
  import {
14
14
  COMPRESSION_PROFILES,
15
15
  isCustomCompressionPolicy,
@@ -33,8 +33,14 @@ export interface CompressionSelectorInjected {
33
33
  savePresetOptions: (options: import('./preset-options.ts').PresetOptionsPatch) => Promise<void>
34
34
  }
35
35
 
36
+ /**
37
+ * Owner-agnostic panel props: the same component registers under the 0.1.5
38
+ * `settings.plugins.tab` card (no owner props) and the legacy
39
+ * `settings.section` page (whose shell supplies `close`), so the section
40
+ * owner share stays optional.
41
+ */
36
42
  export type CompressionProfileSelectorProps =
37
- PropsRuntime<'settings.section'>
43
+ { close?: () => void }
38
44
  & PropsLocale<'context-compression'>
39
45
  & InjectFace<CompressionSelectorInjected>
40
46
 
@@ -52,8 +58,6 @@ export {
52
58
 
53
59
  export {
54
60
  EstimatorControls,
55
- EstimatorInactiveNotice,
56
- ESTIMATOR_CATALOG_ROUTES,
57
61
  } from './EstimatorControls.tsx'
58
62
 
59
63
  export {
@@ -12,6 +12,14 @@ import type { PresetOptionsPatch } from './preset-options.ts'
12
12
  import type { PresetOptionsSettings } from '../profiles.ts'
13
13
  import css from './CompressionProfileSelector.module.css'
14
14
 
15
+ interface EstimatorControlsProps {
16
+ options: PresetOptionsSettings
17
+ disabled: boolean
18
+ save: (options: PresetOptionsPatch) => Promise<void>
19
+ settle: (operation: () => Promise<void>) => void
20
+ t: (key: ContextCompressionLocaleKey) => string
21
+ }
22
+
15
23
  interface EstimatorInactiveNoticeProps {
16
24
  /** Already-localized label of the profile that currently owns the selector. */
17
25
  profile: string
@@ -25,6 +33,9 @@ interface EstimatorInactiveNoticeProps {
25
33
  * exactly that — so keep the heading and its anchor id in place and spend them
26
34
  * on the reason plus the profile that unlocks the card. The gate itself is
27
35
  * unchanged: no estimator control exists outside tokenpilot-inspired.
36
+ *
37
+ * Exported from the estimator card's own module so the gated card and its
38
+ * explanation cannot drift apart.
28
39
  */
29
40
  export function EstimatorInactiveNotice({ profile, t }: EstimatorInactiveNoticeProps) {
30
41
  return (
@@ -35,14 +46,6 @@ export function EstimatorInactiveNotice({ profile, t }: EstimatorInactiveNoticeP
35
46
  )
36
47
  }
37
48
 
38
- interface EstimatorControlsProps {
39
- options: PresetOptionsSettings
40
- disabled: boolean
41
- save: (options: PresetOptionsPatch) => Promise<void>
42
- settle: (operation: () => Promise<void>) => void
43
- t: (key: ContextCompressionLocaleKey) => string
44
- }
45
-
46
49
  interface CatalogModelEntry { readonly id: string, readonly name: string }
47
50
  interface CatalogProviderEntry { readonly id: string, readonly name: string, readonly models: readonly CatalogModelEntry[], readonly error?: string }
48
51
  interface EstimatorCatalogBody {
@@ -51,23 +54,18 @@ interface EstimatorCatalogBody {
51
54
  readonly selection?: { readonly provider: string, readonly model: string }
52
55
  }
53
56
 
54
- // The host channel is `/api`, and only `/api`: the pre-0.1.2 `/endpoint` prefix
55
- // does not exist on this tier's hosts, so probing it first only bought a
56
- // guaranteed 404 round-trip ahead of every successful load.
57
- export const ESTIMATOR_CATALOG_ROUTES = [
58
- '/api/dsh-context-compression-improved/estimator-catalog',
59
- ]
57
+ const ESTIMATOR_CATALOG_ROUTE = '/api/dsh-context-compression-improved/estimator-catalog'
60
58
 
61
59
  /**
62
60
  * TokenPilot-inspired estimator channel card. Shown only while the
63
61
  * tokenpilot-inspired profile is selected, and split by channel:
64
62
  *
65
- * - `host` reuses the providers and credentials already configured in DSH
66
- * through the Harness `llm` service, so this card names a provider and a
63
+ * - `host` reuses the providers and credentials the user already configured in
64
+ * DSH through the Harness `llm` service, so this card names a provider and a
67
65
  * model and accepts NO API key — the key field belongs to the direct channel
68
66
  * alone.
69
- * - `direct` talks to a native OpenAI-compatible endpoint, the only channel
70
- * carrying its own base URL and write-only key.
67
+ * - `direct` talks to a native OpenAI-compatible endpoint, which is the only
68
+ * channel that carries its own base URL and write-only key.
71
69
  *
72
70
  * The whole card is advisory: an unconfigured or failing endpoint keeps every
73
71
  * consumer on its rule-only fallback.
@@ -79,9 +77,11 @@ export function EstimatorControls({ options, disabled, save, settle, t }: Estima
79
77
  const [provider, setProvider] = useState(options.estimatorProvider ?? '')
80
78
  const mode = options.estimatorMode ?? ''
81
79
  // Host-mode dropdown source: the live provider/model-group catalog served by
82
- // the runtime's estimator-catalog route (dsh-perm-gate receiver pattern).
83
- // Empty catalog (route missing / llm service absent) falls back to the
84
- // manual text inputs so nothing breaks on hosts without the webServer.
80
+ // the Bundle entry's estimator-catalog route (dsh-perm-gate receiver
81
+ // pattern). The provider/model fields are always comboboxes: an empty
82
+ // catalog (route missing / llm service absent) simply offers no suggestions
83
+ // while custom typing keeps working, so nothing breaks on hosts without the
84
+ // webServer.
85
85
  const [catalog, setCatalog] = useState<EstimatorCatalogBody | undefined>()
86
86
  useEffect(() => {
87
87
  if (mode !== 'host') return
@@ -89,20 +89,17 @@ export function EstimatorControls({ options, disabled, save, settle, t }: Estima
89
89
  // The catalog route answers once the host webServer/llm services are up;
90
90
  // poll briefly so the dropdowns fill without reopening the panel.
91
91
  let attempts = 0
92
- const load = async (routes: readonly string[]): Promise<EstimatorCatalogBody | undefined> => {
93
- for (const route of routes) {
94
- try {
95
- const response = await fetch(route, { headers: { 'cache-control': 'no-cache' } })
96
- if (response.ok) return (await response.json()) as EstimatorCatalogBody
97
- } catch {
98
- // try the next prefix
99
- }
92
+ const load = async (): Promise<EstimatorCatalogBody | undefined> => {
93
+ try {
94
+ const response = await fetch(ESTIMATOR_CATALOG_ROUTE, { headers: { 'cache-control': 'no-cache' } })
95
+ return response.ok ? (await response.json()) as EstimatorCatalogBody : undefined
96
+ } catch {
97
+ return undefined
100
98
  }
101
- return undefined
102
99
  }
103
100
  const tick = (): void => {
104
101
  attempts += 1
105
- void load(ESTIMATOR_CATALOG_ROUTES).then((body) => {
102
+ void load().then((body) => {
106
103
  if (!alive) return
107
104
  if (body !== undefined && (body.providers?.length ?? 0) > 0) {
108
105
  setCatalog(body)
@@ -236,7 +233,6 @@ export function EstimatorControls({ options, disabled, save, settle, t }: Estima
236
233
  type="text"
237
234
  value={model}
238
235
  disabled={disabled}
239
- placeholder={t('estimator.model.placeholder')}
240
236
  onChange={(event) => { setModel(event.currentTarget.value) }}
241
237
  onBlur={() => { if (model !== (options.estimatorModel ?? '')) commit({ estimatorModel: model }) }}
242
238
  />