@deepseek-ai/dsh-tmux-context 0.1.2-rc.1 → 0.1.5-alpha.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.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/context/tmux-context/README.md
5
- README.md: df94e309839a8ecdae902def8531856071269d20
6
- README.zh.md: da69791cb96cd4ef13b5fa31ddaf4e6470e08d60
5
+ README.md: 2f00aa4e8f42670bbadba4e2a1777d5b596090b9
6
+ README.zh.md: 06743d57d6332401fcb65bf00e6c23bf0ec7eec2
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-tmux-context` tells the model where its agent process runs: on each turn whose tmux state changed, it appends a durable, source-attributed reading naming the tmux session, window, and pane plus the window's pane-tree layout. It is sampled once per turn during request preparation and only when the process genuinely lives inside the named pane — a terminal that merely inherited `$TMUX`/`$TMUX_PANE` from a tmux ancestor reads as not in tmux and adds nothing. An unchanged location adds nothing, and a failed query is a no-op, never a turn failure. The plugin is opt-in and not part of the shipped Web/headless composition.
12
+ `dsh-tmux-context` lets the model identify the tmux session, window, pane, and pane-tree layout containing its agent process. It adds a durable, source-attributed reading on the first step of a turn only when that location changed. Terminals that merely inherit tmux environment variables without running in the named pane add nothing; failed queries also add nothing and do not fail the turn. This package is opt-in and is not included in the shipped Web or headless profiles.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -85,7 +85,7 @@ At the first step of a turn, the listener checks whether an injection is due, qu
85
85
 
86
86
  Read these pages when the package-level contract is not enough. They move from the design decision to the executor the query runs through and the exhaustive configuration.
87
87
 
88
- - [Tmux location context decision record](../../../.agents/notes/implemented/feature/2026-07-27-tmux-location-context.md) — design rationale for the tty-based detection and reading shape.
88
+ - [Tmux location context decision record](../../../.agents/notes/archived/feature/2026-07-27-tmux-location-context.md) — design rationale for the tty-based detection and reading shape.
89
89
  - [Shell subsystem](../../../docs/subsystems/shell.md) — the executor service the read-only query runs through.
90
90
  - [Context group map](../README.md) — sibling request-context packages.
91
91
  - [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tmux-context) — every accepted config field and its source declaration.
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-tmux-context` 告诉模型它的 agent(智能体)进程运行在哪里:在 tmux 状态发生变化的每一轮,它追加一条持久、带来源的读数,命名 tmux session、window 与 pane,以及该 window 的 pane 树布局。它在准备模型请求时每轮采样一次,且仅当进程确实位于所指名的 pane 内时——仅从 tmux 祖先进程继承了 `$TMUX`/`$TMUX_PANE` 的终端会被视为不在 tmux 中,不添加任何内容。位置未变化时不添加任何内容;查询失败是空操作,绝不导致轮次失败。本插件需主动启用,且不属于随附 Web/无头组合。
12
+ `dsh-tmux-context` 让模型识别其 agent(智能体)进程所在的 tmux session、window、pane 和 pane 树布局。它仅在位置发生变化时,于每轮的第一个步骤追加一条持久、带来源的读数。若终端只继承了 tmux 环境变量,却并未在所指名的 pane 中运行,则不添加任何内容;查询失败同样不添加内容,也不会使该轮失败。本包需主动启用,且不包含在随附的 Web 或无头 profile 中。
13
13
 
14
14
  ## 目录
15
15
 
@@ -85,7 +85,7 @@ kind: "package-reference"
85
85
 
86
86
  包级约定不够用时阅读以下页面。它们从设计决策进入查询所经由的执行器与穷尽式配置。
87
87
 
88
- - [tmux 位置上下文决策记录](../../../.agents/notes/implemented/feature/2026-07-27-tmux-location-context.zh.md)——基于 tty 的检测与读数形状的设计理由。
88
+ - [tmux 位置上下文决策记录](../../../.agents/notes/archived/feature/2026-07-27-tmux-location-context.md)——基于 tty 的检测与读数形状的设计理由。
89
89
  - [shell 子系统](../../../docs/subsystems/shell.zh.md)——只读查询所经由的执行器服务。
90
90
  - [context 组地图](../README.zh.md)——相邻的请求上下文包。
91
91
  - [生成的配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tmux-context)——每个受支持配置字段及其源声明。
package/lib/index.js CHANGED
@@ -496,6 +496,9 @@ function harnessErrorCode(error) {
496
496
  }
497
497
  //#endregion
498
498
  //#region ../../llm/llm/src/content.ts
499
+ function quoted(value) {
500
+ return JSON.stringify(value);
501
+ }
499
502
  /**
500
503
  * Stable text shown to a model that cannot accept one durable image reference.
501
504
  * @param ref - durable normalized attachment omitted from the request.
@@ -515,6 +518,76 @@ function textOnlyImageText(ref) {
515
518
  function contentHasImage(content) {
516
519
  return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
517
520
  }
521
+ /**
522
+ * True when typed model content contains a file block, walking nested
523
+ * tool-result content on the same recursion every file policy shares.
524
+ * Reads current content on every call without retaining scan results.
525
+ * @param content - typed model content blocks.
526
+ * @returns whether any nested block is a file.
527
+ */
528
+ function contentHasFile(content) {
529
+ for (const block of content) if (block.type === "file" || block.type === "tool-result" && contentHasFile(block.content)) return true;
530
+ return false;
531
+ }
532
+ /**
533
+ * Stable model-facing handle for one durable file reference: the address of
534
+ * the verbatim stored copy and the instruction to read it on demand. This is
535
+ * the only representation a provider ever receives for a file.
536
+ * @param ref - durable verbatim file reference.
537
+ * @param readonlyPath - execution-world path of the stored copy, when resolvable.
538
+ * @returns deterministic handle text naming the file, its size, and its address.
539
+ */
540
+ function fileHandleText(ref, readonlyPath) {
541
+ const digest = String(ref.attachmentId).slice(7, 15);
542
+ const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
543
+ if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
544
+ return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
545
+ }
546
+ /** Replace every file occurrence, including nested tool results, with handle text. */
547
+ function replaceFilesWithHandles(blocks, resolvePath) {
548
+ let next;
549
+ for (const [index, block] of blocks.entries()) {
550
+ if (block.type === "file") {
551
+ next ??= blocks.slice(0, index);
552
+ next.push({
553
+ type: "text",
554
+ text: fileHandleText(block.attachment, resolvePath(block.attachment))
555
+ });
556
+ continue;
557
+ }
558
+ if (block.type === "tool-result") {
559
+ const content = replaceFilesWithHandles(block.content, resolvePath);
560
+ if (content !== block.content) {
561
+ next ??= blocks.slice(0, index);
562
+ next.push({
563
+ ...block,
564
+ content
565
+ });
566
+ continue;
567
+ }
568
+ }
569
+ next?.push(block);
570
+ }
571
+ return next ?? blocks;
572
+ }
573
+ /**
574
+ * Project durable file history into deterministic handle text for every model
575
+ * route. Unlike images, no provider receives file blocks natively, so this
576
+ * projection is unconditional in request assembly.
577
+ * @param messages - complete request history.
578
+ * @param resolvePath - resolve one reference's current execution-world read path.
579
+ * @returns the original list without files, otherwise shallow message copies with handle text.
580
+ */
581
+ function projectFilesToText(messages, resolvePath) {
582
+ if (!messages.some((message) => contentHasFile(message.content))) return messages;
583
+ return messages.map((message) => {
584
+ const content = replaceFilesWithHandles(message.content, resolvePath);
585
+ return content === message.content ? message : {
586
+ ...message,
587
+ content
588
+ };
589
+ });
590
+ }
518
591
  /** Replace every image occurrence, including nested tool results, for a text-only model. */
519
592
  function replaceImagesForTextModel(blocks) {
520
593
  let next;
@@ -951,6 +1024,15 @@ var LlmError = class extends HarnessError {
951
1024
  imageRequestPricing(provider, model) {
952
1025
  return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
953
1026
  }
1027
+ /**
1028
+ * Resolve the exact text one durable file occurrence contributes to every
1029
+ * provider request in the current execution environment.
1030
+ * @param ref - durable verbatim file reference from model history.
1031
+ * @returns the same deterministic handle text used at adapter dispatch.
1032
+ */
1033
+ fileRequestText(ref) {
1034
+ return fileHandleText(ref, this.fileReadPath(ref));
1035
+ }
954
1036
  /** Detach typed adapter-owned modality metadata. */
955
1037
  detachedModalities(modalities) {
956
1038
  return modalities === void 0 ? void 0 : [...modalities];
@@ -1000,6 +1082,8 @@ var LlmError = class extends HarnessError {
1000
1082
  const context = resolved.context;
1001
1083
  if (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_CONTEXT");
1002
1084
  const inputModalities = this.detachedModalities(resolved.inputModalities);
1085
+ const systemPromptUpdate = resolved.systemPromptUpdate;
1086
+ if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
1003
1087
  const defaultMaxTokens = resolved.defaultMaxTokens;
1004
1088
  if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
1005
1089
  const info = {
@@ -1009,7 +1093,8 @@ var LlmError = class extends HarnessError {
1009
1093
  ...resolved.description === void 0 ? {} : { description: resolved.description },
1010
1094
  ...inputModalities === void 0 ? {} : { inputModalities },
1011
1095
  ...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
1012
- ...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens }
1096
+ ...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
1097
+ ...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
1013
1098
  };
1014
1099
  const reasoning = resolved.reasoning;
1015
1100
  if (reasoning === void 0) return info;
@@ -1103,6 +1188,7 @@ var LlmError = class extends HarnessError {
1103
1188
  adapterDefaults,
1104
1189
  ...context === void 0 ? {} : { context },
1105
1190
  ...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
1191
+ ...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
1106
1192
  stream: (options) => {
1107
1193
  if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
1108
1194
  if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
@@ -1144,6 +1230,20 @@ var LlmError = class extends HarnessError {
1144
1230
  return Object.isFrozen(options) ? deepFreeze(filtered) : filtered;
1145
1231
  }
1146
1232
  /**
1233
+ * Resolve the current execution-world read path of one durable file
1234
+ * reference through the mounted attachment and filesystem providers.
1235
+ */
1236
+ fileReadPath(ref) {
1237
+ let hostPath;
1238
+ try {
1239
+ hostPath = this.ctx.get("attachments")?.fileHostPath(ref);
1240
+ } catch {
1241
+ return;
1242
+ }
1243
+ if (hostPath === void 0) return void 0;
1244
+ return this.ctx.get("fs")?.processPathFromHostPath(hostPath);
1245
+ }
1246
+ /**
1147
1247
  * Final adapter boundary. Adapter selection, dispatch, iterator construction,
1148
1248
  * and iteration failures become one terminal failure chunk. Middleware and
1149
1249
  * downstream consumer failures remain thrown plugin or consumer errors.
@@ -1174,13 +1274,16 @@ var LlmError = class extends HarnessError {
1174
1274
  ...options,
1175
1275
  ...resolvedConfig
1176
1276
  };
1177
- const projectedOptions = modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && resolvedOptions.messages.some((message) => contentHasImage(message.content)) ? Object.isFrozen(resolvedOptions) ? deepFreeze({
1277
+ let projectedMessages = resolvedOptions.messages;
1278
+ if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
1279
+ if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
1280
+ const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
1178
1281
  ...resolvedOptions,
1179
- messages: projectImagesForTextModel(resolvedOptions.messages)
1282
+ messages: projectedMessages
1180
1283
  }) : {
1181
1284
  ...resolvedOptions,
1182
- messages: projectImagesForTextModel(resolvedOptions.messages)
1183
- } : resolvedOptions;
1285
+ messages: projectedMessages
1286
+ };
1184
1287
  iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
1185
1288
  } catch (error) {
1186
1289
  yield adapterFailureChunk(error, options.signal);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-tmux-context",
3
3
  "description": "Opt-in durable per-step context with this agent's tmux pane and window location",
4
- "version": "0.1.2-rc.1",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,19 +31,20 @@
31
31
  "@deepseek-ai/schemastery": "^3.18.2"
32
32
  },
33
33
  "peerDependencies": {
34
- "@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
35
- "@deepseek-ai/dsh-shell": "^0.1.2-rc.1",
36
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
34
+ "@deepseek-ai/dsh-agent": "^0.1.5-alpha.1",
35
+ "@deepseek-ai/dsh-shell": "^0.1.5-alpha.1",
36
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
37
37
  "@deepseek-ai/cordis": "^4.0.2",
38
- "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1"
38
+ "@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.1"
39
39
  },
40
40
  "devDependencies": {
41
- "@deepseek-ai/dsh-agent": "^0.1.2-rc.1",
42
- "@deepseek-ai/dsh-shell": "^0.1.2-rc.1",
43
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
44
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
45
- "@deepseek-ai/dsh-system-prompt": "^0.1.2-rc.1",
46
- "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1",
47
- "@deepseek-ai/cordis": "^4.0.2"
41
+ "@deepseek-ai/dsh-agent": "^0.1.5-alpha.1",
42
+ "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.5-alpha.1",
43
+ "@deepseek-ai/dsh-shell": "^0.1.5-alpha.1",
44
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
45
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
46
+ "@deepseek-ai/dsh-system-prompt": "^0.1.5-alpha.1",
47
+ "@deepseek-ai/cordis": "^4.0.2",
48
+ "@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.1"
48
49
  }
49
50
  }