dsh-hypatia-auto-memory 0.1.0
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/LICENSE +21 -0
- package/README.md +454 -0
- package/cordis.patch.yml +20 -0
- package/lib/client.js +1081 -0
- package/lib/client.js.map +1 -0
- package/package.json +90 -0
- package/skills/hypatia/SKILL.md +585 -0
- package/skills/hypatia-dream/SKILL.md +246 -0
- package/skills/hypatia-dream/evals/evals.json +92 -0
- package/skills/hypatia-memory/SKILL.md +175 -0
- package/src/auto-approve.js +133 -0
- package/src/cascade.js +190 -0
- package/src/client/SettingsCard.module.css +420 -0
- package/src/client/SettingsCard.tsx +700 -0
- package/src/client/card-form.ts +450 -0
- package/src/client/consolidation-models.ts +69 -0
- package/src/client/css-modules.d.ts +4 -0
- package/src/client/index.tsx +106 -0
- package/src/client/locales.ts +111 -0
- package/src/client/shelves.ts +88 -0
- package/src/client/slot-contract.ts +17 -0
- package/src/collector.js +596 -0
- package/src/config.js +225 -0
- package/src/consolidator.js +592 -0
- package/src/content-policy.js +301 -0
- package/src/housekeeping.js +197 -0
- package/src/hypatia-cli.js +294 -0
- package/src/hypatia-client.js +72 -0
- package/src/hypatia-mcp.js +477 -0
- package/src/index.js +418 -0
- package/src/persisted-session.js +99 -0
- package/src/progress.js +56 -0
- package/src/queue.js +466 -0
- package/src/recall.js +148 -0
- package/src/shelf.js +180 -0
- package/src/skills.js +166 -0
- package/src/state.js +87 -0
- package/src/status.js +53 -0
- package/src/writer.js +320 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hypatia-dream
|
|
3
|
+
description: "Run an incremental Hypatia graph-consolidation pass at a work-period boundary, like sleep-time knowledge organization. Trigger only for an explicit batch closeout or dream-time review, not ordinary knowledge CRUD, a one-off triple, or a lookup. Without an explicit --mode, these mean apply: '下班了', '收工了', '今天先到这', '本阶段结束', '阶段性收尾', '做个好梦', '睡前整理一下', 'done for the day', 'wrapping up', 'call it a night'. These mean report-only: '先整理看看', '做个梦看看', '睡前审阅一下', '看看知识之间的关系', '只出报告', '先别修改', 'just show me first', 'do not change anything yet'. Statement triples are the primary evidence; Knowledge bodies only disambiguate."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
allowed-tools: Bash, Read, Grep, Glob
|
|
6
|
+
argument-hint: "[shelf] [--mode report|apply]"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Hypatia Dream
|
|
10
|
+
|
|
11
|
+
Perform an incremental, relationship-focused consolidation pass over one Hypatia shelf. The purpose is to make the graph more coherent after new knowledge arrives, not to summarize, rewrite, delete, or otherwise curate the knowledge nodes themselves.
|
|
12
|
+
|
|
13
|
+
Treat the graph as a personal knowledge base: inspect its whole available relationship topology rather than imposing a small relationship window. Keep the analysis disciplined by treating Statement triples as the primary evidence. Knowledge bodies may clarify entity identity or resolve an apparent contradiction, but they are not a license to invent weak associations.
|
|
14
|
+
|
|
15
|
+
## Trigger And Mode Resolution
|
|
16
|
+
|
|
17
|
+
Trigger this skill only for a deliberate batch consolidation at a work-period boundary or an explicitly requested dream-time review. Do not trigger it for ordinary knowledge CRUD, a direct one-off triple request, an entity lookup, a relationship query, or automatic conversation-memory extraction; those belong to `hypatia` or `hypatia-memory`.
|
|
18
|
+
|
|
19
|
+
Interpret the optional shelf name from the request; use `default` when it is omitted.
|
|
20
|
+
|
|
21
|
+
Resolve the mode in this order:
|
|
22
|
+
|
|
23
|
+
1. An explicit `--mode report` or `--mode apply` always wins.
|
|
24
|
+
2. Without an explicit mode, treat these closeout signals and clear equivalents as `apply`: `下班了`, `收工了`, `今天先到这`, `本阶段结束`, `阶段性收尾`, `做个好梦`, `睡前整理一下`, `done for the day`, `wrapping up`, and `call it a night`.
|
|
25
|
+
3. Without an explicit mode, treat these preview signals and clear equivalents as `report`: `先整理看看`, `做个梦看看`, `睡前审阅一下`, `看看知识之间的关系`, `只出报告`, `先别修改`, `just show me first`, and `do not change anything yet`.
|
|
26
|
+
4. If both signal classes appear in one request, ask the user to choose a mode. Do not assume `apply`.
|
|
27
|
+
5. A direct but otherwise unspecified invocation defaults to `report`.
|
|
28
|
+
|
|
29
|
+
The modes have these effects:
|
|
30
|
+
|
|
31
|
+
- `report` reads the shelf and returns a structured report. It creates no state marker and changes no knowledge or statement.
|
|
32
|
+
- `apply` may add a missing statement or replace an explicitly contradicted statement. It never creates, edits, or deletes a regular Knowledge entry.
|
|
33
|
+
|
|
34
|
+
Before every Hypatia CLI command, briefly state the goal, the exact command, why that query or mutation is appropriate, and the expected result. Use one-shot commands only; do not use `hypatia repl`.
|
|
35
|
+
|
|
36
|
+
## Hypatia Conventions To Preserve
|
|
37
|
+
|
|
38
|
+
Use the actual Statement fields and CLI terminology: `head`, `relation`, and `tail`. Do not describe them as fields named `subject`, `predicate`, or `object` in commands or reports.
|
|
39
|
+
|
|
40
|
+
Prefer the relationship vocabulary already established by Hypatia's semantic-memory design:
|
|
41
|
+
|
|
42
|
+
- `is_a` for a clear category relationship.
|
|
43
|
+
- `refines` for a more precise form of an existing concept.
|
|
44
|
+
- `extends` for an addition that builds on an existing concept.
|
|
45
|
+
- `supersedes` for an explicit replacement of an older concept or decision.
|
|
46
|
+
- `derivedFrom` for a clear provenance relationship.
|
|
47
|
+
|
|
48
|
+
Preserve existing predicate spellings. `belongTo` and `summary` are operational relationships maintained by the memory system; do not reinterpret, delete, replace, or add semantic links to their message/session/summary endpoints. Likewise, do not reorganize archive metadata relationships such as `is_a archive`.
|
|
49
|
+
|
|
50
|
+
Do not use a vague catch-all relationship such as `related_to` merely because two entries share words or a topic. Omit uncertain candidates.
|
|
51
|
+
|
|
52
|
+
## Resolve The CLI And Snapshot Boundary
|
|
53
|
+
|
|
54
|
+
1. Resolve the binary in this order: `hypatia` on `PATH`, `./target/debug/hypatia`, then `./target/release/hypatia`. Stop and report a concrete blocker if none exists.
|
|
55
|
+
2. Set `SHELF` to the requested shelf or `default`, then confirm that shelf is connected before doing anything else:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
hypatia list
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Every Hypatia command fails when the target shelf is not connected, and the whole CLI fails at startup when any registered shelf uses the legacy DuckDB layout. Both are blockers to report, not conditions to work around.
|
|
62
|
+
|
|
63
|
+
3. Record a UTC run-start timestamp before reading graph data:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
date -u '+%Y-%m-%d %H:%M:%S'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
SQLite's `'now'` is UTC, so this is the same clock Hypatia stamps with. Hypatia writes `created_at` as `%Y-%m-%d %H:%M:%f`, so stored values carry milliseconds (`2026-09-02 08:50:08.790`) and compare as TEXT. A second-resolution boundary is therefore slightly conservative, deliberately so: `created_at <= "2026-09-02 08:50:08"` excludes `2026-09-02 08:50:08.790`, and because the watermark is written at the same resolution, the next run's `created_at > "2026-09-02 08:50:08"` picks that row back up. Do not pad the boundary with `.999` and do not truncate stored values to close the apparent gap — either one reintroduces a real gap.
|
|
70
|
+
|
|
71
|
+
4. Query the latest successful apply marker in the same shelf:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
hypatia query '{"$knowledge":[["$contains","tags","hypatia-dream-run"]],"limit":1}' -s "<SHELF>"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Query results are ordered by `created_at DESC`, so `limit:1` returns the most recent marker. A marker is a system-tagged Knowledge entry created by this skill after a successful `apply`. Its content contains a `processed_through` timestamp. Read that value, not the marker entry's `created_at`: a run can create statements after its snapshot begins, and using the marker creation time could skip concurrent new information.
|
|
78
|
+
|
|
79
|
+
5. Define the collection interval as follows:
|
|
80
|
+
|
|
81
|
+
- If a valid marker exists, use `processed_through < created_at <= run_started_at`.
|
|
82
|
+
- If there is no valid marker, this is the first consolidation pass. Collect all pre-snapshot triples and knowledge entries.
|
|
83
|
+
- A malformed marker is not a reason to guess. Report it and treat the run as a first pass unless the user supplies an explicit baseline.
|
|
84
|
+
|
|
85
|
+
`report` mode must not advance this watermark. This lets a later `apply` reconsider the same proposed changes. Only a completed `apply` writes a marker.
|
|
86
|
+
|
|
87
|
+
## Gather The Working Set
|
|
88
|
+
|
|
89
|
+
Retrieve the complete pre-snapshot relationship graph. Use a large, explicit limit because the shelf is personal and the user requested no relationship-range cap:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
hypatia query '{"$statement":[["$lte","created_at","<RUN_STARTED_AT>"]],"limit":10000}' -s "<SHELF>"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Retrieve the newly created triples with the collection interval. For a first pass, omit the lower-bound condition:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
hypatia query '{"$statement":[["$and",["$gt","created_at","<BASELINE>"],["$lte","created_at","<RUN_STARTED_AT>"]]],"limit":10000}' -s "<SHELF>"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Also retrieve new Knowledge entries from the same interval. Exclude internal log, session, summary, and dream-marker entries by tag. For a first pass, omit the lower-bound condition:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
hypatia query '{"$knowledge":[["$and",["$gt","created_at","<BASELINE>"],["$lte","created_at","<RUN_STARTED_AT>"],["$not",["$or",["$contains","tags","message"],["$contains","tags","session"],["$contains","tags","summary"],["$contains","tags","hypatia-dream-run"]]]]],"limit":10000}' -s "<SHELF>"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`limit` is a hard cap, not a promise. Compare the returned row count with the limit for each of the three queries above. If they are equal, the result was truncated by `ORDER BY created_at DESC` and the graph in hand is incomplete. Stop, report the truncation as a blocker, and apply nothing: a relation can only be judged contradicted, duplicate, or missing against the whole graph, and a truncated read would let this skill "correct" a relationship whose supporting evidence was simply cut off.
|
|
108
|
+
|
|
109
|
+
The new Knowledge set can include an unlinked node. It is eligible for one well-supported relationship to an older graph node, but only after its body and the prospective target's body have been read for disambiguation. Do not manufacture a relation solely to make an isolated node connected.
|
|
110
|
+
|
|
111
|
+
For each potentially affected node, fetch its Knowledge entry by exact name and inspect its one-hop incident triples from the full graph. Read only the bodies needed to explain an entity match, source lineage, category, refinement, extension, supersession, or direct contradiction. Never use conversation text, session summaries, or system markers as semantic evidence.
|
|
112
|
+
|
|
113
|
+
## Reason About Relationships
|
|
114
|
+
|
|
115
|
+
Work from the newly added triples and eligible new Knowledge nodes toward the full existing graph. For every candidate, identify:
|
|
116
|
+
|
|
117
|
+
- The exact new evidence: triple or Knowledge entry name and the relevant wording.
|
|
118
|
+
- The supporting old graph context: exact triples and, when needed, the old node body.
|
|
119
|
+
- The proposed triple in `(head, relation, tail)` form.
|
|
120
|
+
- A confidence level and a short explanation of why that relation is more precise than its alternatives.
|
|
121
|
+
|
|
122
|
+
Classify each outcome as exactly one of these:
|
|
123
|
+
|
|
124
|
+
1. **Add**: a missing, non-duplicate triple is directly supported by the new evidence and the existing graph vocabulary.
|
|
125
|
+
2. **Replace**: a specific old triple is directly contradicted by newer, unambiguous evidence. Name both triples. Prefer adding `supersedes` when the old idea remains historically useful; delete and replace only when the old relation itself is false.
|
|
126
|
+
3. **No change**: evidence is duplicate, weak, ambiguous, historical rather than contradictory, or depends on a protected/system relation.
|
|
127
|
+
|
|
128
|
+
A candidate must be high confidence before it may be applied. Similarity, shared tags, or lexical overlap alone are never high confidence.
|
|
129
|
+
|
|
130
|
+
Before a replacement, inspect the old statement's `content`, `tr_start`, and `tr_end`. A query result omits NULL columns entirely, so an absent `tr_start` or `tr_end` key means the statement has no temporal bound — not that the information is unavailable.
|
|
131
|
+
|
|
132
|
+
`statement-create` accepts only `--data`, `--synonyms`, and `--scopes`. It cannot set `tags`, cannot set `format`, cannot set `figures`, and cannot recreate a temporal bound. Report instead of replacing whenever the source statement has any of these:
|
|
133
|
+
|
|
134
|
+
- a populated `tr_start` or `tr_end`;
|
|
135
|
+
- a non-empty `content.tags`;
|
|
136
|
+
- a `content.format` other than `markdown`;
|
|
137
|
+
- a non-empty `content.figures`.
|
|
138
|
+
|
|
139
|
+
Otherwise the statement is eligible, and the replacement must carry over `content.data`, positional `content.synonyms`, and `content.scopes` from the source.
|
|
140
|
+
|
|
141
|
+
## Apply Changes
|
|
142
|
+
|
|
143
|
+
In `report` mode, stop after producing the report. Do not write a marker.
|
|
144
|
+
|
|
145
|
+
In `apply` mode, execute this sequence only after the analysis identifies high-confidence changes:
|
|
146
|
+
|
|
147
|
+
1. Re-query every proposed old and new triple immediately before mutating. Use the exact-key form, which matches on the `triple` primary key:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
hypatia query '{"$statement":[["$triple","<HEAD>","<RELATION>","<TAIL>"]],"limit":1}' -s "<SHELF>"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Skip a stale plan, an addition that already exists, and a replacement whose source triple no longer matches the reviewed evidence.
|
|
154
|
+
|
|
155
|
+
A replacement whose new triple already exists needs one more check, because `statement-create` never overwrites an existing triple. Compare that triple's `data`, `synonyms`, and `scopes` with the source triple's. Treat an absent field and an empty list or object as equal, and compare `scopes` as a set.
|
|
156
|
+
|
|
157
|
+
- If they are equal, an earlier run created it and stopped before the delete. Mark the replacement **delete-only** and count it as replaced. Do not delete anything yet: step 4 runs the delete after step 2's backup.
|
|
158
|
+
- If they differ, skip the replacement. List it under No Change with the reason `target already exists with different metadata; a person must decide`, do not delete the source, and leave both triples out of step 5's verification.
|
|
159
|
+
|
|
160
|
+
2. If the run plans any replacement, export the shelf to a timestamped backup directory before the first replacement step and report the destination. `export` creates the destination directory itself, so do not pre-create it, and pass the shelf name as the first argument:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
hypatia export "<SHELF>" "<BACKUP_DIR>"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
3. For an addition, create only the exact reviewed triple. Do not create supporting nodes or generic fallback links.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
hypatia statement-create "<HEAD>" "<RELATION>" "<TAIL>" -s "<SHELF>"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`statement-create` is idempotent. Creating a triple that already exists exits 0 and prints `Statement already exists: (<HEAD>, <RELATION>, <TAIL>)` instead of `Created statement: ...`, and leaves the stored triple and its metadata unchanged. That means the relationship is already recorded: count it as skipped and continue. It is not a run failure.
|
|
173
|
+
|
|
174
|
+
4. For a replacement, **create the new triple first, verify it, and only then delete the source triple.** A replacement changes at least one of `head`, `relation`, and `tail`, so the two triples have different primary keys and can coexist for the moment in between. This ordering has no window in which the old relationship is already gone and the new one does not yet exist. Never delete first. For a delete-only replacement from step 1, skip the create and run only the `statement-delete`.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
hypatia statement-create "<NEW_HEAD>" "<NEW_RELATION>" "<NEW_TAIL>" \
|
|
178
|
+
-d "<SOURCE_DATA>" --synonyms '<SOURCE_SYNONYMS_JSON>' --scopes "<SOURCE_SCOPES>" -s "<SHELF>"
|
|
179
|
+
hypatia statement-delete "<OLD_HEAD>" "<OLD_RELATION>" "<OLD_TAIL>" -s "<SHELF>"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Carry the source metadata across exactly, and omit any flag whose source value is empty — `--synonyms ""` is a hard parse error, not an empty value. `--synonyms` takes the positional form `{"head":[...],"relation":[...],"tail":[...]}`. `--scopes` is comma-separated and a trailing comma means global scope, so source scopes `["project-a",""]` are reproduced as `--scopes "project-a,"` and `[""]` as `--scopes ","`.
|
|
183
|
+
|
|
184
|
+
If the create fails, nothing has been lost: report it and leave the source triple untouched. If the create prints `Statement already exists`, the new triple appeared after step 1's re-query and may not carry the source metadata. Do not delete the source yet: apply step 1's already-exists check to it now. If the create succeeds but the delete fails, stop and report both triples as present — the graph is consistent but now carries a duplicate relationship, and a person must decide.
|
|
185
|
+
|
|
186
|
+
5. Re-query each created triple and every intended removal with the same `$triple` form. For a delete-only replacement, also re-query its target: it must still come back. A created triple must come back; a removed triple must return `No results found.` Note that `statement-delete` exits non-zero with `not found` when the triple is already gone; that is an already-applied removal rather than a failure, but confirm it by query before continuing. If any verification fails, stop, report the partial outcome and the backup location, and do not create a watermark.
|
|
187
|
+
|
|
188
|
+
After all intended changes verify, write one run marker in the same shelf:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
hypatia knowledge-create "hypatia-dream-run-<UTC_COMPACT_TIMESTAMP>" \
|
|
192
|
+
-d "schema: 1
|
|
193
|
+
mode: apply
|
|
194
|
+
processed_through: <RUN_STARTED_AT>
|
|
195
|
+
added: <COUNT>
|
|
196
|
+
replaced: <COUNT>
|
|
197
|
+
report: Hypatia Dream completed successfully" \
|
|
198
|
+
-t "system,hypatia-dream-run" \
|
|
199
|
+
-s "<SHELF>"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The marker is operational metadata, not semantic knowledge. Never attach a Statement triple to it or use it as relationship evidence.
|
|
203
|
+
|
|
204
|
+
A marker is still an ordinary Knowledge entry: it is full-text indexed and vector-embedded like any other, so it will surface in the user's own `hypatia search` and `hypatia similar` results. Keep the body to the fields above for that reason — never store report prose, analysis, or candidate lists in it. This skill never deletes an old marker; only the most recent one is ever read.
|
|
205
|
+
|
|
206
|
+
If no high-confidence graph changes exist, `apply` may still write the marker after reporting a verified no-op. This records that the interval has been reviewed without falsely claiming a graph mutation.
|
|
207
|
+
|
|
208
|
+
## Required Report
|
|
209
|
+
|
|
210
|
+
Return the report in this structure, in the user's language:
|
|
211
|
+
|
|
212
|
+
```markdown
|
|
213
|
+
# Hypatia Dream Report
|
|
214
|
+
|
|
215
|
+
## Scope
|
|
216
|
+
- Shelf: `<name>`
|
|
217
|
+
- Mode: `report` or `apply`
|
|
218
|
+
- Baseline: first pass, or `<processed_through>`
|
|
219
|
+
- Snapshot boundary: `<run_started_at>`
|
|
220
|
+
- Reviewed: `<new knowledge count>` Knowledge entries and `<new statement count>` new triples against `<total statement count>` existing triples
|
|
221
|
+
- Graph read: `<returned row count>` of `<limit>` rows, complete or truncated
|
|
222
|
+
|
|
223
|
+
## Relationship Findings
|
|
224
|
+
### Add
|
|
225
|
+
- `(head, relation, tail)` [high confidence]
|
|
226
|
+
Evidence: ...
|
|
227
|
+
Existing context: ...
|
|
228
|
+
|
|
229
|
+
### Replace
|
|
230
|
+
- `(old head, old relation, old tail)` -> `(new head, new relation, new tail)` [high confidence]
|
|
231
|
+
Evidence: ...
|
|
232
|
+
Rationale: ...
|
|
233
|
+
|
|
234
|
+
### No Change
|
|
235
|
+
- Item: ...
|
|
236
|
+
Reason: duplicate, insufficient evidence, protected system relation, metadata/temporal safety restriction, or target already exists with different metadata.
|
|
237
|
+
|
|
238
|
+
## Outcome
|
|
239
|
+
- Planned or applied additions: `<count>`
|
|
240
|
+
- Planned or applied replacements: `<count>`
|
|
241
|
+
- Skipped candidates: `<count>`
|
|
242
|
+
- Backup: `<path>` or `not needed`
|
|
243
|
+
- Watermark: `<processed_through>` written only after a verified `apply`, otherwise `unchanged`
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Be concise but include every command that changed state and every exact triple it affected. Do not claim a relationship was corrected unless its post-mutation query verified the result.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "hypatia-dream",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "在名为 research 的 Hypatia shelf 上做一次知识图谱睡眠整理。不要改动任何数据,只给我报告。这个 shelf 没有 hypatia-dream 水位;其中新增节点 'JSE Query Planner' 明确说明它细化了 'JSE Query Engine',但目前缺少两者之间的关系。还包含一条 msg-123-4 belongTo session-123。",
|
|
7
|
+
"expected_output": "Resolve report mode, treat the run as a first pass, inspect the full statement graph and relevant Knowledge bodies, exclude the message/session operational relationship, and report a high-confidence candidate `(JSE Query Planner, refines, JSE Query Engine)` without executing any create, delete, export, or marker command.",
|
|
8
|
+
"files": [],
|
|
9
|
+
"expectations": [
|
|
10
|
+
"Defaults to report mode and schedules no state-changing command or watermark.",
|
|
11
|
+
"Treats the missing marker as a first pass with a fixed snapshot boundary.",
|
|
12
|
+
"Proposes (JSE Query Planner, refines, JSE Query Engine) with explicit evidence.",
|
|
13
|
+
"Excludes the belongTo message/session relationship from semantic consolidation."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": 2,
|
|
18
|
+
"prompt": "对默认 shelf 运行 hypatia-dream --mode apply。上次成功水位为 2026-03-01 09:00:00。新知识明确规定 'SQLite Storage' 已取代 'DuckDB Storage' 作为唯一结构化存储;图中仍有 `(Hypatia, uses, DuckDB Storage)`,该旧三元组没有时间范围,且 scopes 和 data 为空。请修正关系。",
|
|
19
|
+
"expected_output": "Use the marker's processed_through value and a fixed snapshot boundary, identify an explicit contradiction rather than a merely related concept, re-query the reviewed triple with the $triple exact-key form before mutation, export a backup before the replacement, create `(Hypatia, uses, SQLite Storage)` first and verify it, only then delete `(Hypatia, uses, DuckDB Storage)`, verify the removal, create a successful apply marker, and return the required report with exact changes and backup path.",
|
|
20
|
+
"files": [],
|
|
21
|
+
"expectations": [
|
|
22
|
+
"Uses the marker's processed_through value rather than its creation time.",
|
|
23
|
+
"Re-queries the exact old triple and exports a backup before the replacement.",
|
|
24
|
+
"Creates (Hypatia, uses, SQLite Storage) BEFORE deleting (Hypatia, uses, DuckDB Storage), never the reverse order.",
|
|
25
|
+
"Verifies both the creation and the removal, and writes a successful apply marker only after verification."
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": 3,
|
|
30
|
+
"prompt": "帮我做梦整理默认知识库,模式 report。新条目 'Database Notes' 和旧条目 'Distributed Systems' 都提到了 consistency,但没有明确的来源、分类、细化、扩展或替代关系;图中还存在一个时间范围不为空的 `(Storage, uses, DuckDB)` 三元组。",
|
|
31
|
+
"expected_output": "Report no semantic change for the lexical overlap, avoid adding a vague related_to triple, and explain that a time-bounded statement is not eligible for replacement through the current CLI because statement-create cannot recreate temporal bounds. Note that the CLI itself never sets tr_start/tr_end, so this guard exists for statements written by other Hypatia clients.",
|
|
32
|
+
"files": [],
|
|
33
|
+
"expectations": [
|
|
34
|
+
"Does not add a related_to or other semantic triple from lexical overlap alone.",
|
|
35
|
+
"Does not replace the time-bounded Storage uses DuckDB triple.",
|
|
36
|
+
"Keeps report mode read-only and leaves the watermark unchanged."
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": 4,
|
|
41
|
+
"prompt": "下班了。请对默认 Hypatia shelf 做个好梦。",
|
|
42
|
+
"expected_output": "Treat the two closeout signals as an explicit request for apply mode even though --mode is omitted; begin the safe apply workflow rather than returning a report-only plan.",
|
|
43
|
+
"files": [],
|
|
44
|
+
"expectations": [
|
|
45
|
+
"Selects apply mode without asking for confirmation.",
|
|
46
|
+
"Explains that the closeout wording is the mode signal, then follows the normal preflight, backup, verification, and watermark rules."
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": 5,
|
|
51
|
+
"prompt": "先整理看看默认知识库,看看知识之间的关系;先别修改。",
|
|
52
|
+
"expected_output": "Treat the preview wording as report mode and make no graph or watermark mutation.",
|
|
53
|
+
"files": [],
|
|
54
|
+
"expectations": [
|
|
55
|
+
"Selects report mode from the preview signals.",
|
|
56
|
+
"Does not schedule statement mutations or watermark creation."
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": 6,
|
|
61
|
+
"prompt": "本阶段结束了,但先别修改,帮我做个好梦。",
|
|
62
|
+
"expected_output": "Detect the conflicting apply and report signals, ask the user to choose a mode, and make no state change.",
|
|
63
|
+
"files": [],
|
|
64
|
+
"expectations": [
|
|
65
|
+
"Does not prioritize the closeout signal over the preview signal.",
|
|
66
|
+
"Requests a mode choice and performs no graph mutation or watermark update."
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": 7,
|
|
71
|
+
"prompt": "下班了,对默认 shelf 做个好梦。这个 shelf 很大:完整关系图查询返回的行数正好等于 10000 条上限。",
|
|
72
|
+
"expected_output": "Detect that the returned row count equals the query limit, treat the graph read as truncated rather than complete, and stop with a blocker report instead of applying any addition or replacement against a partial graph.",
|
|
73
|
+
"files": [],
|
|
74
|
+
"expectations": [
|
|
75
|
+
"Compares the returned row count against the limit and identifies the read as truncated.",
|
|
76
|
+
"Applies no statement mutation and writes no watermark.",
|
|
77
|
+
"Reports the truncation as a concrete blocker rather than proceeding on the rows it did receive."
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": 8,
|
|
82
|
+
"prompt": "对默认 shelf 运行 hypatia-dream --mode apply。新知识明确说明 'Parquet Export' 已取代 'CSV Export';旧三元组 `(Pipeline, emits, CSV Export)` 没有时间范围,但它的 content.tags 是 [\"pipeline\",\"legacy\"]。",
|
|
83
|
+
"expected_output": "Recognize the contradiction but refuse to apply the replacement, because statement-create has no tags flag and the source statement's tags would be silently dropped. Report the candidate under No Change with the metadata-preservation reason and leave the graph unchanged.",
|
|
84
|
+
"files": [],
|
|
85
|
+
"expectations": [
|
|
86
|
+
"Does not delete or replace (Pipeline, emits, CSV Export).",
|
|
87
|
+
"Names the non-empty content.tags as the reason the statement is ineligible for replacement.",
|
|
88
|
+
"Reports the candidate rather than silently dropping it or silently losing the tags."
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hypatia-memory
|
|
3
|
+
description: Long-term memory for DSH via hypatia. Conversation logging and background consolidation run automatically; retrieval is yours to drive. Use when the task touches past decisions, project conventions, prior bugs, or anything the user expects you to already know — and whenever the user says "read hypatia", "查一下 hypatia", "记住", "忘记".
|
|
4
|
+
user-invocable: false
|
|
5
|
+
allowed-tools: Bash, Read, Grep, Glob
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Hypatia Memory (DSH auto-memory variant)
|
|
9
|
+
|
|
10
|
+
The `dsh-hypatia-auto-memory` plugin is active. It handles the **write** side of
|
|
11
|
+
memory in the background. The **read** side is yours.
|
|
12
|
+
|
|
13
|
+
## What runs without you
|
|
14
|
+
|
|
15
|
+
Do not do these by hand — you would only create duplicates:
|
|
16
|
+
|
|
17
|
+
- **Conversation logging.** Every human message and every assistant message
|
|
18
|
+
becomes a `msg-<sessionId>-<N>` entry (`N` is a dense per-session message
|
|
19
|
+
ordinal), redacted, with relative dates rewritten to absolute ones and a tool
|
|
20
|
+
ledger attached. Spans are cut at turn boundaries.
|
|
21
|
+
- **Span summaries.** `sum-*` entries tagged `summary`, linked to the messages
|
|
22
|
+
they cover with `summary` statements.
|
|
23
|
+
- **Work units.** `wu-*` entries tagged `memory,work-unit` for lessons worth
|
|
24
|
+
keeping, with `is_a` / `derivedFrom` statements.
|
|
25
|
+
- **Session seed.** Project and global entries tagged `rule` or `taboo` are
|
|
26
|
+
injected into your context at session start. Nothing else is pushed to you.
|
|
27
|
+
|
|
28
|
+
## Which shelf
|
|
29
|
+
|
|
30
|
+
The plugin writes to one hypatia shelf, chosen in its settings. When that is
|
|
31
|
+
not `default`, the session seed says so: *"Memory lives on the hypatia shelf
|
|
32
|
+
`<SHELF>`"*. Then add `--shelf <SHELF>` to **every** command below — reads and
|
|
33
|
+
writes alike — or you will search a shelf the plugin never wrote to and store
|
|
34
|
+
rules the seed will never load. When the seed names no shelf, leave the flag off.
|
|
35
|
+
|
|
36
|
+
## What you own: retrieval
|
|
37
|
+
|
|
38
|
+
Rules and taboos are pushed because you cannot know to ask for them. Everything
|
|
39
|
+
else you must **pull**, when you judge you need it — including part-way through
|
|
40
|
+
a task, once you discover what the task is actually about.
|
|
41
|
+
|
|
42
|
+
Search when the task touches prior decisions, project conventions, a bug that
|
|
43
|
+
smells familiar, an unfamiliar internal name, or when the user implies you
|
|
44
|
+
should already know something. Searching costs one subprocess; not searching
|
|
45
|
+
costs a wrong answer that contradicts a decision already made.
|
|
46
|
+
|
|
47
|
+
### Semantic search — the default
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
hypatia similar "<what you actually want to know>" -t knowledge --limit 5
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Write the query as the *idea* you are looking for, not the user's words verbatim.
|
|
54
|
+
Rows come back as `{"name", "content" (object), "distance"}`; lower `distance` is
|
|
55
|
+
closer. If it exits non-zero with `model unavailable` / `no embedding provider`,
|
|
56
|
+
this shelf has no embedding model — fall back to `search` and carry on.
|
|
57
|
+
|
|
58
|
+
### Keyword search — for exact identifiers
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
hypatia search "<exact symbol, error code, file name>" -c knowledge --limit 5
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Rows come back as `{"id", "catalog", "key", "content" (**JSON-encoded string**),
|
|
65
|
+
"rank"}`. Note the shape differs from `similar`: the name is `key`, not `name`,
|
|
66
|
+
and `content` must be parsed before use.
|
|
67
|
+
|
|
68
|
+
### Structured queries
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# This project's rules (and global ones)
|
|
72
|
+
hypatia query '["$knowledge", ["$contains","tags","rule"],
|
|
73
|
+
["$or", ["$contains","scopes","<PROJECT>"], ["$contains","scopes",""]]]'
|
|
74
|
+
|
|
75
|
+
# Everything derived from one work unit, two hops out
|
|
76
|
+
hypatia query '["$statement", ["$k-hop", "<entry-name>", "$*", 2]]'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`<PROJECT>` is the basename of the workspace's git root (of the workspace
|
|
80
|
+
itself outside git), written as the plugin writes it: commas become `_`,
|
|
81
|
+
surrounding whitespace is dropped, and the filesystem root is `/`. Written raw,
|
|
82
|
+
a name with a comma would be split into two scopes.
|
|
83
|
+
|
|
84
|
+
### Always exclude the operational layer
|
|
85
|
+
|
|
86
|
+
Raw logs and bookkeeping entries are indexed for full-text and vector search,
|
|
87
|
+
so they *will* come back in results. They are not knowledge — skip rows whose
|
|
88
|
+
name starts with `msg-`, `sum`, `session-`, or `hypatia-dream-run-`, and rows
|
|
89
|
+
tagged `message`, `session`, `summary`, or `hypatia-dream-run`. Read `wu-*`,
|
|
90
|
+
`rule`, `taboo`, and ordinary named entries.
|
|
91
|
+
|
|
92
|
+
Reach into `msg-*` only when the user asks what was literally said, and prefer
|
|
93
|
+
reaching it by walking `summary` statements down from a `sum-*` entry.
|
|
94
|
+
|
|
95
|
+
## Explicit writes
|
|
96
|
+
|
|
97
|
+
### Remember
|
|
98
|
+
|
|
99
|
+
When the user states a durable rule, taboo, or correction — "以后都记住:……",
|
|
100
|
+
"always use tabs", "never commit to main" — write it immediately. The background
|
|
101
|
+
extractor only produces work units; it never invents rules or taboos, so an
|
|
102
|
+
unwritten rule is a lost rule.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
hypatia knowledge-create "<short-kebab-name>" \
|
|
106
|
+
--data="<the fact, as an imperative>" \
|
|
107
|
+
--tags "rule" \
|
|
108
|
+
--scopes "<PROJECT>"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- `--tags rule` for conventions to follow, `taboo` for never-do items,
|
|
112
|
+
`memory` for durable facts.
|
|
113
|
+
- **Scope syntax matters.** `--scopes "<PROJECT>"` is project-only.
|
|
114
|
+
`--scopes "<PROJECT>,"` — with a *trailing comma* — also marks it global.
|
|
115
|
+
`--scopes ""` writes no scope at all, which is not the same as global and will
|
|
116
|
+
not be found by the session seed.
|
|
117
|
+
- Check with `knowledge-get <name>` first. A new name gets `knowledge-create`.
|
|
118
|
+
An existing entry the user wants reworded, retagged or rescoped gets
|
|
119
|
+
`knowledge-update`, **never** delete + create:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
hypatia knowledge-update "<exact-name>" --data="<the corrected fact>"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Only the fields you pass change. The entry keeps its `created_at`, and its
|
|
126
|
+
vector is regenerated on the next flush. An update that changes nothing
|
|
127
|
+
prints `Knowledge unchanged: <name>` and writes nothing. Updating a missing
|
|
128
|
+
entry exits 1.
|
|
129
|
+
- Pass only the fields you mean to change. `--scopes` **replaces** the stored
|
|
130
|
+
scopes, with the syntax above: leave off the trailing comma and a global rule
|
|
131
|
+
drops out of the session seed. `--tags ""` clears every tag, and an entry
|
|
132
|
+
that is no longer tagged `rule` or `taboo` is not seeded either.
|
|
133
|
+
- An update is not how you record a contradiction; that is `supersedes`, below.
|
|
134
|
+
- A hypatia older than #20 has no `knowledge-update` and exits 2 with
|
|
135
|
+
`unrecognized subcommand`. Then leave the entry alone unless the user asks:
|
|
136
|
+
delete + create loses its creation time.
|
|
137
|
+
- Creating a name that already exists exits 1 with `UNIQUE constraint failed`.
|
|
138
|
+
That is a collision, not a crash.
|
|
139
|
+
|
|
140
|
+
Relate it to what is already there when the relationship is clear:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
hypatia statement-create "<new>" "supersedes" "<old>" --scopes "<PROJECT>"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Use `refines`, `extends`, `supersedes`, `derivedFrom`, `is_a`. When new
|
|
147
|
+
knowledge **contradicts** old, record `supersedes` and leave the old entry in
|
|
148
|
+
place — the memory system must never quietly forget what it once believed. Do
|
|
149
|
+
not invent a vague `related_to` edge just because two entries share a topic.
|
|
150
|
+
|
|
151
|
+
### Forget
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
hypatia search "<keywords>" -c knowledge --limit 10
|
|
155
|
+
hypatia knowledge-delete "<exact-name>"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Deleting a missing entry exits 1. Statements referencing the entry are not
|
|
159
|
+
cascade-deleted; remove them with `hypatia statement-delete <head> <relation>
|
|
160
|
+
<tail>` when they would otherwise dangle. Deleted memories stay deleted — the
|
|
161
|
+
collector's watermark has already moved past them.
|
|
162
|
+
|
|
163
|
+
## Known gaps in the automatic layer
|
|
164
|
+
|
|
165
|
+
Say so plainly if a user's expectation depends on one of these:
|
|
166
|
+
|
|
167
|
+
- **No topic-shift detection.** Spans are cut on turn boundaries and token
|
|
168
|
+
thresholds, not on the conversation changing subject. A session covering three
|
|
169
|
+
unrelated topics summarises them together.
|
|
170
|
+
- **No `session-<id>` node** unless the host produced a session title or a
|
|
171
|
+
compaction summary to build it from.
|
|
172
|
+
- **Summary names are mechanical** (`sum-<session>-<from>-<to>`), not descriptive.
|
|
173
|
+
The descriptive title is the first heading inside the entry.
|
|
174
|
+
- **Work-unit dedup is best-effort** and degrades to "no relationship" whenever
|
|
175
|
+
the shelf has no embedding model.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-approval for the agent's own `hypatia` calls.
|
|
3
|
+
*
|
|
4
|
+
* This plugin's own writes never need it: they go through the subprocess
|
|
5
|
+
* service as argv arrays, with no shell and no approval in the path. It exists
|
|
6
|
+
* for the other half of the protocol — the agent running `hypatia …` through
|
|
7
|
+
* bash to retrieve, remember or forget — which otherwise stops on an approval
|
|
8
|
+
* prompt every single time and, in practice, stops the agent from retrieving at
|
|
9
|
+
* all.
|
|
10
|
+
*
|
|
11
|
+
* The matcher is narrow on purpose: a bash call whose executable word is a
|
|
12
|
+
* trusted basename AND that contains no shell composition outside quotes. A
|
|
13
|
+
* command that pipes, redirects, chains or substitutes is a command that can do
|
|
14
|
+
* something other than talk to hypatia, so it goes to the human.
|
|
15
|
+
*
|
|
16
|
+
* Ported from `dsh-hypatia`'s auto-approve (same repository, MIT), which this
|
|
17
|
+
* plugin replaces — see README.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-hypatia-auto-memory/auto-approve
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True when the command's executable word (after env assignments) is trusted.
|
|
24
|
+
* @param {string} command
|
|
25
|
+
* @param {string[]} binaries - trusted basenames, e.g. `['hypatia']`.
|
|
26
|
+
*/
|
|
27
|
+
export function invokesTrustedBinary(command, binaries) {
|
|
28
|
+
let rest = command.trimStart()
|
|
29
|
+
// Skip leading KEY=VALUE environment assignments (e.g. HYPATIA_BIN=...).
|
|
30
|
+
for (;;) {
|
|
31
|
+
const assignment = /^[A-Za-z_][A-Za-z0-9_]*=\S+\s+/.exec(rest)
|
|
32
|
+
if (assignment === null) break
|
|
33
|
+
rest = rest.slice(assignment[0].length).trimStart()
|
|
34
|
+
}
|
|
35
|
+
const word = /^([^\s"']+)/.exec(rest)?.[1]
|
|
36
|
+
if (word === undefined) return false
|
|
37
|
+
return binaries.some((binary) => word === binary || word.endsWith(`/${binary}`))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* True when the command contains shell composition OUTSIDE quotes.
|
|
42
|
+
*
|
|
43
|
+
* Quote tracking matters: a JSE query argument routinely contains `|` or `>`
|
|
44
|
+
* inside a quoted string (`'["$knowledge",[["$gt","x",1]]]'`), and treating
|
|
45
|
+
* those as composition would send every structured query to the human.
|
|
46
|
+
* @param {string} command
|
|
47
|
+
*/
|
|
48
|
+
export function hasShellComposition(command) {
|
|
49
|
+
let single = false
|
|
50
|
+
let double = false
|
|
51
|
+
let escaped = false
|
|
52
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
53
|
+
const char = command[index]
|
|
54
|
+
if (escaped) {
|
|
55
|
+
escaped = false
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
if (single === false && char === '\\') {
|
|
59
|
+
escaped = true
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
if (double === false && char === "'") {
|
|
63
|
+
single = single === false
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
if (single === false && char === '"') {
|
|
67
|
+
double = double === false
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (single || double) continue
|
|
71
|
+
if (char === '&' || char === ';' || char === '|' || char === '<' || char === '>' || char === '`' || char === '\n') {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
if (char === '$' && command[index + 1] === '(') return true
|
|
75
|
+
}
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* True when this tool execution is a plain call to a trusted binary.
|
|
81
|
+
* @param {{name?: string, arguments?: {command?: unknown}}} exec
|
|
82
|
+
* @param {string[]} binaries
|
|
83
|
+
*/
|
|
84
|
+
export function isPureTrustedCall(exec, binaries) {
|
|
85
|
+
if (exec?.name !== 'bash') return false
|
|
86
|
+
const command = exec.arguments?.command
|
|
87
|
+
if (typeof command !== 'string') return false
|
|
88
|
+
return invokesTrustedBinary(command, binaries) && hasShellComposition(command) === false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Answer approval requests for the agent's pure hypatia calls.
|
|
93
|
+
*
|
|
94
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx - context injecting `approval` and `tools`.
|
|
95
|
+
* @param {{getBinaries: () => string[], status: import('./status.js').StatusLog}} deps
|
|
96
|
+
*/
|
|
97
|
+
export function createAutoApprove(ctx, { getBinaries, status }) {
|
|
98
|
+
/** callIds of in-flight bash calls that qualify for automatic approval. */
|
|
99
|
+
const pending = new Set()
|
|
100
|
+
|
|
101
|
+
const binaries = () => {
|
|
102
|
+
const configured = getBinaries()
|
|
103
|
+
return Array.isArray(configured) && configured.length > 0 ? configured : ['hypatia']
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ctx.on('tools/pre-execute', (exec, next) => {
|
|
107
|
+
try {
|
|
108
|
+
if (isPureTrustedCall(exec, binaries())) pending.add(exec.callId)
|
|
109
|
+
} catch {
|
|
110
|
+
// A matcher bug must never block the tool pipeline; fall through to ask.
|
|
111
|
+
}
|
|
112
|
+
return next()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Whether the call asked for approval or not, drop the marker once it settles.
|
|
116
|
+
ctx.on('tools/result', (exec) => {
|
|
117
|
+
pending.delete(exec?.callId)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// PREPEND: the waterfall runs outermost-first and a handler that skips next()
|
|
121
|
+
// vetoes the rest — the host's api-proxy publishes the approval to the UI and
|
|
122
|
+
// blocks on the human without calling next(), so a plain registration would
|
|
123
|
+
// sit behind it and never run. Non-hypatia calls fall through unchanged.
|
|
124
|
+
ctx.on('approval/request', (request, next) => {
|
|
125
|
+
if (request?.callId !== undefined && pending.delete(request.callId)) {
|
|
126
|
+
status.count('autoApproved')
|
|
127
|
+
return 'allowed-once'
|
|
128
|
+
}
|
|
129
|
+
return next()
|
|
130
|
+
}, { prepend: true })
|
|
131
|
+
|
|
132
|
+
return { pendingSize: () => pending.size }
|
|
133
|
+
}
|