pi-ssh-remote 0.1.8 → 0.1.10

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/CHANGELOG.md CHANGED
@@ -18,6 +18,34 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Rel
18
18
 
19
19
  - None.
20
20
 
21
+ ## [0.1.10] - 2026-08-16
22
+
23
+ ### Added
24
+
25
+ - None.
26
+
27
+ ### Changed
28
+
29
+ - None.
30
+
31
+ ### Fixed
32
+
33
+ - Prevented the agent-facing `memory` action from clearing or replacing server memory accidentally: omission now inspects the saved memory, non-empty text appends idempotently, empty text is rejected, and clearing requires an explicit `--clear` value.
34
+
35
+ ## [0.1.9] - 2026-08-12
36
+
37
+ ### Added
38
+
39
+ - None.
40
+
41
+ ### Changed
42
+
43
+ - Removed the redundant `SSH remote` prefix from footer status labels.
44
+
45
+ ### Fixed
46
+
47
+ - Kept injected server memory as a single stable context prefix so tool continuations do not repeatedly move it to the end of the conversation or invalidate cached provider context.
48
+
21
49
  ## [0.1.8] - 2026-08-10
22
50
 
23
51
  ### Added
@@ -161,7 +189,9 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Rel
161
189
 
162
190
  - None.
163
191
 
164
- [Unreleased]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.8...HEAD
192
+ [Unreleased]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.10...HEAD
193
+ [0.1.10]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.9...v0.1.10
194
+ [0.1.9]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.8...v0.1.9
165
195
  [0.1.8]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.7...v0.1.8
166
196
  [0.1.7]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.6...v0.1.7
167
197
  [0.1.6]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.5...v0.1.6
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -73,7 +73,7 @@ Requires Node.js 20+. The remote server must provide Bash, SFTP, and GNU `timeou
73
73
  From this point, normal Pi operations target `/srv/project` on the remote server. The footer makes that routing explicit:
74
74
 
75
75
  ```text
76
- SSH remote H100 training server (root@gpu-box.example.com:2202):/srv/project
76
+ H100 training server (root@gpu-box.example.com:2202):/srv/project
77
77
  ```
78
78
 
79
79
  Return to local tools with:
@@ -164,7 +164,7 @@ Forward the remote service on port 8000 to localhost:8000, but keep my coding
164
164
  tools on the local repository.
165
165
  ```
166
166
 
167
- These requests are handled through the agent-facing `remote` tool; slash commands are optional.
167
+ These requests are handled through the agent-facing `remote` tool; slash commands are optional. For safety, calling its `memory` action without a `memory` argument only reads the current server memory. Non-empty text is appended to the existing memory instead of replacing it; retrying the same final entry is a no-op. Clearing still requires the explicit `--clear` value.
168
168
 
169
169
  ## Command reference
170
170
 
@@ -215,13 +215,13 @@ The extension currently supports direct SSH commands with `-p`, `-l`, and `-i`.
215
215
 
216
216
  ## Releases
217
217
 
218
- Latest release: [v0.1.8](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.8)
218
+ Latest release: [v0.1.10](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.10)
219
219
 
220
220
  | Version | Date | Highlights |
221
221
  |---|---|---|
222
+ | [0.1.10](CHANGELOG.md#0110---2026-08-16) | 2026-08-16 | Safe inspect-by-default and append-only agent updates for server memory |
223
+ | [0.1.9](CHANGELOG.md#019---2026-08-12) | 2026-08-12 | Stable deduplicated server-memory context and shorter footer labels |
222
224
  | [0.1.8](CHANGELOG.md#018---2026-08-10) | 2026-08-10 | Explicit private-key login, cross-port server memory, and the shorter `remote` tool name |
223
- | [0.1.7](CHANGELOG.md#017---2026-08-05) | 2026-08-05 | Session-aware SSH workspace restoration for new, forked, and resumed sessions |
224
- | [0.1.6](CHANGELOG.md#016---2026-08-05) | 2026-08-05 | Bounded remote reads, streamed command output, and per-turn output budgets |
225
225
 
226
226
  See [CHANGELOG.md](CHANGELOG.md) for the complete release history, including additions, behavior changes, and bug fixes.
227
227
 
package/README.zh-CN.md CHANGED
@@ -39,7 +39,7 @@ ssh user@host "cd /path && ..."
39
39
  插件会把当前远程目录和服务器信息写入 Pi 的上下文,并显示在底部状态栏:
40
40
 
41
41
  ```text
42
- SSH remote H100 训练机 (root@gpu-box.example.com:2202):/srv/project
42
+ H100 训练机 (root@gpu-box.example.com:2202):/srv/project
43
43
  ```
44
44
 
45
45
  这样无论是用户还是 Agent,都能随时确认当前操作发生在本地还是远端,减少误操作。
@@ -218,7 +218,7 @@ pi install npm:pi-ssh-remote
218
218
  把远端 8000 端口转发到本地 8000,但代码工具继续留在本地。
219
219
  ```
220
220
 
221
- Pi 会通过插件提供的 `remote` 工具完成这些操作。
221
+ Pi 会通过插件提供的 `remote` 工具完成这些操作。为防止误删,调用 `memory` action 但不传 `memory` 参数时只会读取当前服务器记忆;传入非空内容时会追加到现有记忆,而不是覆盖,重复提交相同的末尾内容不会再次追加;清除时仍必须显式传入 `--clear`。
222
222
 
223
223
  ## 命令说明
224
224
 
@@ -294,13 +294,13 @@ Pi 会通过插件提供的 `remote` 工具完成这些操作。
294
294
 
295
295
  ## 版本发布
296
296
 
297
- 最新版本:[v0.1.8](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.8)
297
+ 最新版本:[v0.1.10](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.10)
298
298
 
299
299
  | 版本 | 日期 | 主要内容 |
300
300
  |---|---|---|
301
+ | [0.1.10](CHANGELOG.md#0110---2026-08-16) | 2026-08-16 | 默认安全读取且仅追加的服务器记忆 agent 更新逻辑 |
302
+ | [0.1.9](CHANGELOG.md#019---2026-08-12) | 2026-08-12 | 稳定且去重的服务器记忆上下文与更短的状态栏标签 |
301
303
  | [0.1.8](CHANGELOG.md#018---2026-08-10) | 2026-08-10 | 显式私钥登录、跨端口服务器记忆与更短的 `remote` 工具名 |
302
- | [0.1.7](CHANGELOG.md#017---2026-08-05) | 2026-08-05 | 新建、分叉和恢复 session 时自动恢复对应 SSH 工作区 |
303
- | [0.1.6](CHANGELOG.md#016---2026-08-05) | 2026-08-05 | 有界远端读取、流式命令输出与每轮输出预算 |
304
304
 
305
305
  完整的新增内容、行为变更和 Bug 修复记录请查看 [CHANGELOG.md](CHANGELOG.md)。
306
306
 
package/index.ts CHANGED
@@ -931,8 +931,8 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
931
931
  const status = (ctx: any) => {
932
932
  currentCtx = ctx;
933
933
  if (!remote) ctx.ui.setStatus("ssh-remote", undefined);
934
- else if (routeRemoteTools) ctx.ui.setStatus("ssh-remote", ctx.ui.theme.fg("accent", `SSH remote ${endpointDisplayLabel(remote)}:${remote.cwd}`));
935
- else ctx.ui.setStatus("ssh-remote", ctx.ui.theme.fg("accent", `SSH remote tunnel ${[...forwardServers.keys()].join(",") || endpointDisplayLabel(remote)}`));
934
+ else if (routeRemoteTools) ctx.ui.setStatus("ssh-remote", ctx.ui.theme.fg("accent", `${endpointDisplayLabel(remote)}:${remote.cwd}`));
935
+ else ctx.ui.setStatus("ssh-remote", ctx.ui.theme.fg("accent", `tunnel ${[...forwardServers.keys()].join(",") || endpointDisplayLabel(remote)}`));
936
936
  };
937
937
 
938
938
  const attachClient = (state: RemoteState) => {
@@ -940,7 +940,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
940
940
  client.on("close", () => {
941
941
  if (remote?.client !== client) return;
942
942
  if (currentCtx) {
943
- currentCtx.ui.setStatus("ssh-remote", currentCtx.ui.theme.fg("warning", `SSH remote reconnecting ${endpointDisplayLabel(state)}…`));
943
+ currentCtx.ui.setStatus("ssh-remote", currentCtx.ui.theme.fg("warning", `reconnecting ${endpointDisplayLabel(state)}…`));
944
944
  }
945
945
  void reconnectRemote().catch((error) => {
946
946
  if (currentCtx) currentCtx.ui.notify(`SSH remote automatic reconnection failed: ${(error as Error).message}`, "error");
@@ -1055,7 +1055,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1055
1055
  }
1056
1056
 
1057
1057
  let password = getCachedPassword(parsed);
1058
- ctx.ui.setStatus("ssh-remote", ctx.ui.theme.fg("warning", `SSH remote connecting ${endpointDisplayLabel(parsed)}…`));
1058
+ ctx.ui.setStatus("ssh-remote", ctx.ui.theme.fg("warning", `connecting ${endpointDisplayLabel(parsed)}…`));
1059
1059
  try {
1060
1060
  let authentication = parsed.identityFile
1061
1061
  ? await privateKeyAuthentication(parsed, ctx)
@@ -1299,6 +1299,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1299
1299
  promptGuidelines: [
1300
1300
  "Use remote when the user asks the agent to enter, reconnect, inspect, or leave a remote SSH environment.",
1301
1301
  "Use remote with action chdir when the user asks to change the remote working directory; do not emulate a persistent directory change with action exec and a one-command cwd.",
1302
+ "Use remote with action memory and no memory argument to inspect server memory. Non-empty memory text appends to the existing memory; clear it only by passing --clear explicitly.",
1302
1303
  `Always set timeout for remote exec commands; it defaults to ${DEFAULT_REMOTE_TIMEOUT_SECONDS} seconds when omitted.`,
1303
1304
  "Keep remote exec output narrow with tail, sed, rg limits, or similarly bounded commands; never cat large logs or emit broad file listings.",
1304
1305
  "Use remote with action disconnect after remote work when the user asks to return to the local environment.",
@@ -1307,7 +1308,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1307
1308
  action: StringEnum(["connect", "reconnect", "status", "disconnect", "forget", "forward", "unforward", "exec", "chdir", "note", "memory"] as const),
1308
1309
  command: Type.Optional(Type.String({ description: "SSH command for connect, such as ssh root@host -p 22 or ssh -i ~/.ssh/id_ed25519 root@host; optionally selects the endpoint for note or memory" })),
1309
1310
  note: Type.Optional(Type.String({ description: "Endpoint note for the note action; omit or use an empty string to clear it" })),
1310
- memory: Type.Optional(Type.String({ description: "Persistent context shared by the endpoint's user@host across SSH ports; omit or use an empty string to clear it" })),
1311
+ memory: Type.Optional(Type.String({ description: "Persistent context shared by the endpoint's user@host across SSH ports; omit to inspect, provide non-empty text to append without overwriting existing memory, or use --clear to clear explicitly" })),
1311
1312
  cwd: Type.Optional(Type.String({ description: "Remote working directory; required for chdir, and a one-command override for exec" })),
1312
1313
  forwards: Type.Optional(Type.String({ description: "Space-separated LOCAL_PORT:REMOTE_HOST:REMOTE_PORT mappings; defaults to ssh-remote-config.json" })),
1313
1314
  remoteCommand: Type.Optional(Type.String({ description: "Remote shell command for the exec action" })),
@@ -1366,12 +1367,37 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1366
1367
  details: { endpoint: label, note },
1367
1368
  };
1368
1369
  }
1369
- const memory = params.memory?.trim() || undefined;
1370
- const server = serverMemoryId(parseSshCommand(command));
1370
+ const parsed = parseSshCommand(command);
1371
+ const server = serverMemoryId(parsed);
1372
+ if (params.memory === undefined) {
1373
+ const memory = endpointMemory(parsed);
1374
+ return {
1375
+ content: [{ type: "text", text: memory ? `SSH remote server memory (${server}):\n${memory}` : `No SSH remote server memory is configured (${server}).` }],
1376
+ details: { endpoint: label, server, memory },
1377
+ };
1378
+ }
1379
+ const addition = params.memory.trim();
1380
+ if (!addition) throw new Error("memory must contain text; omit it to inspect the current memory or use --clear to clear it explicitly");
1381
+ if (addition.toLowerCase() === "--clear") {
1382
+ saveServerMemory(command, undefined);
1383
+ return {
1384
+ content: [{ type: "text", text: `SSH remote server memory cleared (${server}).` }],
1385
+ details: { endpoint: label, server, memory: undefined },
1386
+ };
1387
+ }
1388
+ const currentMemory = endpointMemory(parsed);
1389
+ const duplicate = currentMemory === addition || currentMemory?.endsWith(`\n\n${addition}`);
1390
+ if (duplicate) {
1391
+ return {
1392
+ content: [{ type: "text", text: `SSH remote server memory already ends with this content (${server}); nothing was changed.` }],
1393
+ details: { endpoint: label, server, memory: currentMemory, addedMemory: addition, changed: false },
1394
+ };
1395
+ }
1396
+ const memory = currentMemory ? `${currentMemory}\n\n${addition}` : addition;
1371
1397
  saveServerMemory(command, memory);
1372
1398
  return {
1373
- content: [{ type: "text", text: memory ? `SSH remote server memory updated (${server}). It applies to every port for this user and host.` : `SSH remote server memory cleared (${server}).` }],
1374
- details: { endpoint: label, server, memory },
1399
+ content: [{ type: "text", text: `SSH remote server memory appended (${server}). It applies to every port for this user and host.` }],
1400
+ details: { endpoint: label, server, memory, addedMemory: addition, changed: true },
1375
1401
  };
1376
1402
  }
1377
1403
  if (params.action === "exec") {
@@ -1679,14 +1705,17 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1679
1705
  if (!remote || !routeRemoteTools) return undefined;
1680
1706
  const content = serverMemoryContext(remote);
1681
1707
  if (!content) return undefined;
1708
+ const messages = event.messages.filter((message) =>
1709
+ message.role !== "custom" || message.customType !== "ssh-remote-server-memory"
1710
+ );
1682
1711
  return {
1683
- messages: [...event.messages, {
1712
+ messages: [{
1684
1713
  role: "custom",
1685
1714
  customType: "ssh-remote-server-memory",
1686
1715
  content,
1687
1716
  display: false,
1688
1717
  timestamp: Date.now(),
1689
- }],
1718
+ }, ...messages],
1690
1719
  };
1691
1720
  });
1692
1721
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ssh-remote",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Persistent remote SSH workspaces for Pi.",
5
5
  "type": "module",
6
6
  "author": "Yutong Bian",