dsh-hypatia-auto-memory 2.4.2 → 2.4.3
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.md +7 -4
- package/README.zh-CN.md +5 -3
- package/package.json +1 -1
- package/src/auto-approve.js +43 -8
package/README.md
CHANGED
|
@@ -400,9 +400,12 @@ agent/created ──▶ rules/taboos inject()
|
|
|
400
400
|
- **Auto-approve** answers the approval request for the agent's own bash
|
|
401
401
|
`hypatia` calls, and only those: the executable word must be a trusted
|
|
402
402
|
basename (after `KEY=value` prefixes) and the command must carry no pipe,
|
|
403
|
-
redirect, chain or command substitution *outside quotes* — a JSE
|
|
404
|
-
of `|` and `>` inside quotes still qualifies.
|
|
405
|
-
|
|
403
|
+
redirect, chain or command substitution *outside single quotes* — a JSE
|
|
404
|
+
argument full of `|` and `>` inside quotes still qualifies. Double quotes do
|
|
405
|
+
**not** make a payload inert: the shell expands `$(…)` and backticks inside
|
|
406
|
+
them, so `hypatia query "$(id)"` goes to the human like any other composed
|
|
407
|
+
command. Everything else goes to the human. The plugin's own writes never
|
|
408
|
+
take this path.
|
|
406
409
|
|
|
407
410
|
Scope, measured rather than assumed: in DSH a bash call raises an approval
|
|
408
411
|
request **only** when the sandbox denies it and the model retries with
|
|
@@ -668,7 +671,7 @@ now only projects plugin Config forms.)
|
|
|
668
671
|
| Every write fails right after changing `shelf` | The shelf is not registered or not connected — startup logs `shelf "<name>" is not registered`. Connect it (`hypatia connect <dir> --name <name>`) and save the setting again (or reload the profile), or pick another |
|
|
669
672
|
| The agent searches `default` while the plugin writes elsewhere | A disk copy of `hypatia-memory` (see below) replaced the bundled skill, or recall is disabled, so nothing told the agent which shelf to use |
|
|
670
673
|
| Duplicate `msg-*` after weird manual edits | Delete the entry in hypatia and lower `lastLoggedSeq` for that session in the state domain — backfill recreates it once |
|
|
671
|
-
| The agent's own `hypatia` write still asks for approval | `autoApprove: false`, the command pipes/redirects/chains outside quotes, or its first word is not one of `binaries` — only plain calls are answered, by design. Reads never reach approval at all, so nothing to fix there |
|
|
674
|
+
| The agent's own `hypatia` write still asks for approval | `autoApprove: false`, the command pipes/redirects/chains outside single quotes (or substitutes `$(…)`/backticks even inside double ones), or its first word is not one of `binaries` — only plain calls are answered, by design. Reads never reach approval at all, so nothing to fix there |
|
|
672
675
|
| The agent got a memory protocol that tells it to log messages by hand | Another plugin registered `hypatia-memory` first (`dsh-hypatia` still in the profile), or a `hypatia-memory` exists on disk — typically `~/.agents/skills/hypatia-memory`, written by `hypatia skill install --agent codex`. Agent presets load disk skills in a layer nearer than plugins, so it wins in sessions even though the skill center may list this plugin. Remove the copy the startup warning names |
|
|
673
676
|
| Can't tell which model actually ran | The list is a priority order, so a task's first attempt names the head of `consolidation.models` and a retry names the route it degraded to. Each attempt lands in `~/.dsh/storages/hypatia_auto_memory_diag.json` (`model_calls`) with purpose, provider/model, the `effort` actually sent, outcome, duration and the route's own token split — reading one task's rows top-down is how a fallback is confirmed, and `effort` is how a configured thinking level is confirmed to have been honoured. The usage ledger cannot answer this — it folds agent turns (`assistant/message`) and never sees a plugin's direct `llm.stream`. A `pending` row means the call is still in flight, or the process died mid-call: the row is written before the call and settled after it |
|
|
674
677
|
| A configured `reasoningEffort` seems to do nothing | The route does not declare that level, so the call drops it rather than risk the core's `UNSUPPORTED_REASONING_EFFORT`. One warning names the route, the value and what the route does declare; `model_calls.effort` then reads empty for those attempts. The card offers exactly the levels the model advertises, so this normally only happens after editing the profile by hand or pointing the route at a different model |
|
package/README.zh-CN.md
CHANGED
|
@@ -307,8 +307,10 @@ agent/created ──▶ rules/taboos inject()
|
|
|
307
307
|
|
|
308
308
|
- **Auto-approve** 只回答 Agent 自己的 bash `hypatia` 调用的审批请求,而且仅限这些:
|
|
309
309
|
可执行词必须是一个可信的 basename(去掉 `KEY=value` 前缀之后),并且命令在
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
**单引号之外**不能有管道、重定向、串联或命令替换——一个引号内满是 `|` 和 `>` 的 JSE
|
|
311
|
+
参数仍然合格。双引号**不**能当作"已成字面量":shell 会在双引号内展开 `$(…)` 和
|
|
312
|
+
反引号,所以 `hypatia query "$(id)"` 和任何带组合的命令一样交给人类。其余都交给
|
|
313
|
+
人类。插件自己的写入从不走这条路。
|
|
312
314
|
|
|
313
315
|
Scope 是实测而非想当然:在 DSH 里,一个 bash 调用**只有**在沙箱拒绝、且模型带
|
|
314
316
|
`sandbox_permissions` 重试时才会发起审批请求。检索(`query`、`search`、`list`、
|
|
@@ -522,7 +524,7 @@ shelf 清单本来就不是配置,而设置域现在只投影插件的 Config
|
|
|
522
524
|
| 改了 `shelf` 之后每次写入都失败 | shelf 没注册或没连接——启动时记录 `shelf "<name>" is not registered`。连接它(`hypatia connect <dir> --name <name>`)后重新保存设置(或重载 profile),或换一个 |
|
|
523
525
|
| Agent 在 `default` 里搜,而插件写在别处 | 一份磁盘上的 `hypatia-memory`(见下)替换了随包技能,或 recall 被禁用,所以没有东西告诉 Agent 用哪个 shelf |
|
|
524
526
|
| 手动乱改后出现重复 `msg-*` | 在 hypatia 里删掉该条目,并在 state domain 里把该会话的 `lastLoggedSeq` 调低——回填会重新创建它一次 |
|
|
525
|
-
| Agent 自己的 `hypatia` 写入仍要审批 | `autoApprove: false
|
|
527
|
+
| Agent 自己的 `hypatia` 写入仍要审批 | `autoApprove: false`、命令在单引号外有管道/重定向/串联(双引号内的 `$(…)`/反引号也算)、或首词不是 `binaries` 之一——按设计只回答纯调用。读取根本不会走到审批,所以那里没有要修的 |
|
|
526
528
|
| Agent 拿到了一份教它手动记录消息的记忆协议 | 另一个插件先注册了 `hypatia-memory`(profile 里还有 `dsh-hypatia`),或磁盘上有 `hypatia-memory`——通常是 `hypatia skill install --agent codex` 写的 `~/.agents/skills/hypatia-memory`。Agent 预设会在比插件更近的一层加载磁盘技能,所以它在会话里胜出,尽管技能中心可能列出本插件。删掉启动告警指出的那份拷贝 |
|
|
527
529
|
| 不知道实际用了哪个模型 | 列表是优先级顺序:一个任务的第一次尝试就是 `consolidation.models` 的队首,重试则是它降级到的那条。每次尝试都落在 `~/.dsh/storages/hypatia_auto_memory_diag.json`(`model_calls`),带 purpose、provider/model、**实际发出的 `effort`**、outcome、耗时以及路由自己上报的 token 拆分——把同一个任务的几行按序读下来,就能确认备选确实被用上了,而 `effort` 是确认配置的思考档位真的生效了的依据。usage ledger 答不了这个问题——它只折算 Agent 的 turn(`assistant/message`),看不见插件直连的 `llm.stream`。`pending` 行表示调用仍在进行中,或进程在调用中途死了:记录在调用前先落盘、调用结束后回填 |
|
|
528
530
|
| 配了 `reasoningEffort` 却像没生效 | 该路由没有声明这个档位,于是调用把它丢掉了,以免撞上核心的 `UNSUPPORTED_REASONING_EFFORT`。会有一条警告点名路由、值和该路由实际声明的档位,此后这些尝试的 `model_calls.effort` 是空的。卡片只提供模型声明的档位,所以这通常只发生在手改 profile、或把这条路由换成了别的模型之后 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hypatia-auto-memory",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"description": "Event-driven Hypatia memory for DSH: automatic conversation logging via session events, queued background consolidation (span summaries + work-unit extraction) with a dedicated model route, and recall injection — zero per-call model work in the main session.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|
package/src/auto-approve.js
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
* prompt every single time and, in practice, stops the agent from retrieving at
|
|
9
9
|
* all.
|
|
10
10
|
*
|
|
11
|
-
* The matcher is narrow on purpose: a
|
|
12
|
-
* trusted basename AND that contains no shell composition outside
|
|
13
|
-
* command that pipes, redirects, chains or substitutes is a command
|
|
14
|
-
* something other than talk to hypatia, so it goes to the human.
|
|
11
|
+
* The matcher is narrow on purpose: a shell call whose executable word is a
|
|
12
|
+
* trusted basename AND that contains no shell composition outside single
|
|
13
|
+
* quotes. A command that pipes, redirects, chains or substitutes is a command
|
|
14
|
+
* that can do something other than talk to hypatia, so it goes to the human.
|
|
15
|
+
* Only single quotes are trusted to make a payload literal: `$(…)` and
|
|
16
|
+
* backticks are expanded inside double quotes, so a double-quoted payload is
|
|
17
|
+
* still a payload that runs.
|
|
15
18
|
*
|
|
16
19
|
* Ported from `dsh-hypatia`'s auto-approve (same repository, MIT), which this
|
|
17
20
|
* plugin replaces — see README.
|
|
@@ -21,6 +24,11 @@
|
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* True when the command's executable word (after env assignments) is trusted.
|
|
27
|
+
*
|
|
28
|
+
* Windows shapes are accepted too — an optional `.exe` suffix and backslash
|
|
29
|
+
* separators, as the pwsh tool produces them on a Windows host
|
|
30
|
+
* (`C:\Users\me\bin\hypatia.exe`, `hypatia.exe`).
|
|
31
|
+
*
|
|
24
32
|
* @param {string} command
|
|
25
33
|
* @param {string[]} binaries - trusted basenames, e.g. `['hypatia']`.
|
|
26
34
|
*/
|
|
@@ -34,15 +42,30 @@ export function invokesTrustedBinary(command, binaries) {
|
|
|
34
42
|
}
|
|
35
43
|
const word = /^([^\s"']+)/.exec(rest)?.[1]
|
|
36
44
|
if (word === undefined) return false
|
|
37
|
-
|
|
45
|
+
// The suffix is tried on both separators and on the bare word, so `hypatia.exe`
|
|
46
|
+
// and `C:/bin/hypatia.exe` match without a second regex.
|
|
47
|
+
return binaries.some((binary) =>
|
|
48
|
+
word === binary
|
|
49
|
+
|| word === `${binary}.exe`
|
|
50
|
+
|| word.endsWith(`/${binary}`)
|
|
51
|
+
|| word.endsWith(`/${binary}.exe`)
|
|
52
|
+
|| word.endsWith(`\\${binary}`)
|
|
53
|
+
|| word.endsWith(`\\${binary}.exe`))
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
/**
|
|
41
|
-
* True when the command contains shell composition
|
|
57
|
+
* True when the command contains shell composition outside SINGLE quotes.
|
|
42
58
|
*
|
|
43
59
|
* Quote tracking matters: a JSE query argument routinely contains `|` or `>`
|
|
44
60
|
* inside a quoted string (`'["$knowledge",[["$gt","x",1]]]'`), and treating
|
|
45
61
|
* those as composition would send every structured query to the human.
|
|
62
|
+
*
|
|
63
|
+
* Double quotes are different, and that difference is the whole point: a shell
|
|
64
|
+
* expands `$(…)` and backticks inside them, so `hypatia query "$(id)"` still
|
|
65
|
+
* runs something else and must reach the human. Only single quotes make a
|
|
66
|
+
* payload literal. Everything else (`|`, `>`, `&&`, `;`) stays non-composition
|
|
67
|
+
* inside double quotes, which is what keeps `--data="a | b"` prompt-free.
|
|
68
|
+
*
|
|
46
69
|
* @param {string} command
|
|
47
70
|
*/
|
|
48
71
|
export function hasShellComposition(command) {
|
|
@@ -67,7 +90,13 @@ export function hasShellComposition(command) {
|
|
|
67
90
|
double = double === false
|
|
68
91
|
continue
|
|
69
92
|
}
|
|
70
|
-
if (single
|
|
93
|
+
if (single) continue
|
|
94
|
+
if (double) {
|
|
95
|
+
// Substitution survives double quoting; nothing else does.
|
|
96
|
+
if (char === '`') return true
|
|
97
|
+
if (char === '$' && command[index + 1] === '(') return true
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
71
100
|
if (char === '&' || char === ';' || char === '|' || char === '<' || char === '>' || char === '`' || char === '\n') {
|
|
72
101
|
return true
|
|
73
102
|
}
|
|
@@ -78,11 +107,17 @@ export function hasShellComposition(command) {
|
|
|
78
107
|
|
|
79
108
|
/**
|
|
80
109
|
* True when this tool execution is a plain call to a trusted binary.
|
|
110
|
+
*
|
|
111
|
+
* `pwsh` and `powershell` qualify because that is how a Windows host runs its
|
|
112
|
+
* shell tool; the command string gets the same conservative scan, so widening
|
|
113
|
+
* this gate changes only which tool name may ask, not what may pass.
|
|
114
|
+
*
|
|
81
115
|
* @param {{name?: string, arguments?: {command?: unknown}}} exec
|
|
82
116
|
* @param {string[]} binaries
|
|
83
117
|
*/
|
|
84
118
|
export function isPureTrustedCall(exec, binaries) {
|
|
85
|
-
|
|
119
|
+
const name = exec?.name
|
|
120
|
+
if (name !== 'bash' && name !== 'pwsh' && name !== 'powershell') return false
|
|
86
121
|
const command = exec.arguments?.command
|
|
87
122
|
if (typeof command !== 'string') return false
|
|
88
123
|
return invokesTrustedBinary(command, binaries) && hasShellComposition(command) === false
|