dsh-plugin-sessiongraph-tool 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/lib/command.d.ts +179 -0
- package/lib/command.js +398 -0
- package/lib/index.d.ts +102 -0
- package/lib/index.js +582 -0
- package/lib/invariant.d.ts +16 -0
- package/lib/invariant.js +22 -0
- package/lib/refresh.d.ts +103 -0
- package/lib/refresh.js +171 -0
- package/lib/render.d.ts +85 -0
- package/lib/render.js +247 -0
- package/lib/schema.d.ts +539 -0
- package/lib/schema.js +267 -0
- package/lib/status-hint.d.ts +42 -0
- package/lib/status-hint.js +100 -0
- package/package.json +69 -3
- package/README.md +0 -3
package/lib/schema.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `sessiongraph` tool family's model-facing schemas: the parameters the model fills in, the
|
|
3
|
+
* per-operation defaults the seam refuses to guess, and the canonical value each operation returns.
|
|
4
|
+
*
|
|
5
|
+
* The query vocabulary itself is owned by the seam (`SESSIONGRAPH_OPERATIONS`,
|
|
6
|
+
* `SESSIONGRAPH_ENTRY_KINDS`) and re-exported here rather than copied, so adding an entry kind in the
|
|
7
|
+
* service package cannot leave the model's `kinds` filter behind. The output is a closed `oneOf` keyed
|
|
8
|
+
* on `operation`, not one loose object with every field optional, so a Code Mode program that switches
|
|
9
|
+
* on `operation` gets exactly the fields that operation produces and nothing it must test for.
|
|
10
|
+
* @module dsh-plugin-sessiongraph-tool/schema
|
|
11
|
+
*/
|
|
12
|
+
import { SESSIONGRAPH_ENTRY_KINDS, SESSIONGRAPH_OPERATIONS } from 'dsh-plugin-sessiongraph-service';
|
|
13
|
+
export { SESSIONGRAPH_ENTRY_KINDS, SESSIONGRAPH_OPERATIONS };
|
|
14
|
+
/**
|
|
15
|
+
* Every operation the tool family exposes: the seam's five query operations plus `index`, which lives
|
|
16
|
+
* on the dedicated `sessiongraph_index` tool because a full index run needs its own, much larger
|
|
17
|
+
* timeout budget than a query.
|
|
18
|
+
*/
|
|
19
|
+
export const SESSIONGRAPH_TOOL_OPERATIONS = [...SESSIONGRAPH_OPERATIONS, 'index'];
|
|
20
|
+
/**
|
|
21
|
+
* Results returned per operation when the model names no `limit`.
|
|
22
|
+
*
|
|
23
|
+
* The seam requires a `limit` on every list request and has no opinion on its value, so the consumer
|
|
24
|
+
* owns the defaults: a search answers with a short, scannable list, a session with enough entries to
|
|
25
|
+
* be worth opening, and a workspace census with the whole machine in one page.
|
|
26
|
+
*/
|
|
27
|
+
export const SESSIONGRAPH_DEFAULT_LIMITS = {
|
|
28
|
+
search: 10,
|
|
29
|
+
session: 200,
|
|
30
|
+
timeline: 20,
|
|
31
|
+
workspaces: 50,
|
|
32
|
+
};
|
|
33
|
+
/** One session's metadata, as the timeline and session operations report it. */
|
|
34
|
+
const SESSION_SUMMARY = {
|
|
35
|
+
type: 'object',
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
properties: {
|
|
38
|
+
session_id: { type: 'string', required: true },
|
|
39
|
+
title: { type: 'string' },
|
|
40
|
+
workspace: { type: 'string', required: true },
|
|
41
|
+
created_at: { type: 'integer', required: true },
|
|
42
|
+
last_at: { type: 'integer', required: true },
|
|
43
|
+
turns: { type: 'integer', required: true },
|
|
44
|
+
entries: { type: 'integer', required: true },
|
|
45
|
+
parent_session: { type: 'string' },
|
|
46
|
+
origin: { type: 'string' },
|
|
47
|
+
agent_preset: { type: 'string' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
/** The session object a `session` answer carries, marked required by its holder. */
|
|
51
|
+
const REQUIRED_SESSION_SUMMARY = { ...SESSION_SUMMARY, required: true };
|
|
52
|
+
/** One matched entry, with enough context to recognize it without a second query. */
|
|
53
|
+
const HIT = {
|
|
54
|
+
type: 'object',
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
properties: {
|
|
57
|
+
session_id: { type: 'string', required: true },
|
|
58
|
+
title: { type: 'string' },
|
|
59
|
+
workspace: { type: 'string', required: true },
|
|
60
|
+
time: { type: 'integer', required: true },
|
|
61
|
+
kind: { type: 'string', required: true },
|
|
62
|
+
seq: { type: 'integer', required: true },
|
|
63
|
+
turn: { type: 'integer' },
|
|
64
|
+
snippet: { type: 'string', required: true },
|
|
65
|
+
length: { type: 'integer', required: true },
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/** One indexed entry inside a session. */
|
|
69
|
+
const ENTRY = {
|
|
70
|
+
type: 'object',
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
properties: {
|
|
73
|
+
seq: { type: 'integer', required: true },
|
|
74
|
+
time: { type: 'integer', required: true },
|
|
75
|
+
kind: { type: 'string', required: true },
|
|
76
|
+
turn: { type: 'integer' },
|
|
77
|
+
text: { type: 'string', required: true },
|
|
78
|
+
clipped: { type: 'boolean', required: true },
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
/** One workspace's coverage. */
|
|
82
|
+
const WORKSPACE_STAT = {
|
|
83
|
+
type: 'object',
|
|
84
|
+
additionalProperties: false,
|
|
85
|
+
properties: {
|
|
86
|
+
workspace: { type: 'string', required: true },
|
|
87
|
+
sessions: { type: 'integer', required: true },
|
|
88
|
+
entries: { type: 'integer', required: true },
|
|
89
|
+
first_at: { type: 'integer', required: true },
|
|
90
|
+
last_at: { type: 'integer', required: true },
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
/** One source session an index run could not parse. */
|
|
94
|
+
const FAILED_SESSION = {
|
|
95
|
+
type: 'object',
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
properties: {
|
|
98
|
+
path: { type: 'string', required: true },
|
|
99
|
+
reason: { type: 'string', required: true },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
/** Bounds every truncatable member reports, so a capped answer is never read as a complete one. */
|
|
103
|
+
const COUNTS = {
|
|
104
|
+
total: { type: 'integer', required: true },
|
|
105
|
+
truncated: { type: 'boolean', required: true },
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Optional "this answer may predate the conversation" marker, set only when the query path tried an
|
|
109
|
+
* incremental refresh and could not finish one (it failed, or was handed to a background job). An
|
|
110
|
+
* answer from an index that is merely a little old keeps it absent: a boolean on every result that is
|
|
111
|
+
* almost always false is noise a model learns to ignore.
|
|
112
|
+
*/
|
|
113
|
+
const STALE = {
|
|
114
|
+
stale: { type: 'boolean' },
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Build one `oneOf` member for a fixed operation name.
|
|
118
|
+
*
|
|
119
|
+
* Both type parameters are `const` so the member keeps its literal types: without them the helper
|
|
120
|
+
* would widen `operation` to the whole union and erase each member's own fields, and `InferValue`
|
|
121
|
+
* would produce one shapeless object instead of a discriminated union.
|
|
122
|
+
* @param operation - the literal `operation` value discriminating this member.
|
|
123
|
+
* @param properties - the fields this operation returns beyond the discriminant.
|
|
124
|
+
* @returns the member's value schema.
|
|
125
|
+
*/
|
|
126
|
+
function member(operation, properties) {
|
|
127
|
+
return {
|
|
128
|
+
type: 'object',
|
|
129
|
+
additionalProperties: false,
|
|
130
|
+
properties: {
|
|
131
|
+
operation: { type: 'string', required: true, const: operation },
|
|
132
|
+
...properties,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** The canonical value every `sessiongraph` or `sessiongraph_index` call returns, keyed on `operation`. */
|
|
137
|
+
export const SESSIONGRAPH_OUTPUT_SCHEMA = {
|
|
138
|
+
oneOf: [
|
|
139
|
+
member('search', {
|
|
140
|
+
hits: { type: 'array', required: true, items: HIT },
|
|
141
|
+
...COUNTS,
|
|
142
|
+
...STALE,
|
|
143
|
+
}),
|
|
144
|
+
member('session', {
|
|
145
|
+
session: REQUIRED_SESSION_SUMMARY,
|
|
146
|
+
entries: { type: 'array', required: true, items: ENTRY },
|
|
147
|
+
children: { type: 'array', required: true, items: { type: 'string' } },
|
|
148
|
+
truncated: { type: 'boolean', required: true },
|
|
149
|
+
...STALE,
|
|
150
|
+
}),
|
|
151
|
+
member('timeline', {
|
|
152
|
+
sessions: { type: 'array', required: true, items: SESSION_SUMMARY },
|
|
153
|
+
truncated: { type: 'boolean', required: true },
|
|
154
|
+
...STALE,
|
|
155
|
+
}),
|
|
156
|
+
member('workspaces', {
|
|
157
|
+
workspaces: { type: 'array', required: true, items: WORKSPACE_STAT },
|
|
158
|
+
total: { type: 'integer', required: true },
|
|
159
|
+
...STALE,
|
|
160
|
+
}),
|
|
161
|
+
member('status', {
|
|
162
|
+
indexed: { type: 'boolean', required: true },
|
|
163
|
+
index_path: { type: 'string', required: true },
|
|
164
|
+
sessions: { type: 'integer', required: true },
|
|
165
|
+
entries: { type: 'integer', required: true },
|
|
166
|
+
workspaces: { type: 'integer', required: true },
|
|
167
|
+
// Always present: a store that cannot distinguish live sessions omits the seam field, and the
|
|
168
|
+
// tool answers `0` for it, which is what "nothing is running that this index covers" means.
|
|
169
|
+
live_sessions: { type: 'integer', required: true },
|
|
170
|
+
indexed_at: { type: 'integer' },
|
|
171
|
+
bytes: { type: 'integer', required: true },
|
|
172
|
+
}),
|
|
173
|
+
// An index run on a store large enough to outlive the call is handed to the host's background
|
|
174
|
+
// job registry, and the call answers before it has a report to give. `index` is therefore two
|
|
175
|
+
// members rather than one object with optional counts: the started member carries only what the
|
|
176
|
+
// call honestly knows — a job id — while the report member keeps every count required, so a
|
|
177
|
+
// renderer never invents a zero for a run that has not finished. "0 added" and "not run yet" are
|
|
178
|
+
// different answers and must stay distinguishable.
|
|
179
|
+
member('index', {
|
|
180
|
+
status: { type: 'string', required: true, const: 'started' },
|
|
181
|
+
job_id: { type: 'string', required: true },
|
|
182
|
+
}),
|
|
183
|
+
member('index', {
|
|
184
|
+
index_path: { type: 'string', required: true },
|
|
185
|
+
added: { type: 'integer', required: true },
|
|
186
|
+
updated: { type: 'integer', required: true },
|
|
187
|
+
skipped: { type: 'integer', required: true },
|
|
188
|
+
removed: { type: 'integer', required: true },
|
|
189
|
+
discovered: { type: 'integer', required: true },
|
|
190
|
+
failed: { type: 'array', required: true, items: FAILED_SESSION },
|
|
191
|
+
// Always present and normalized to 0 by the tool; the renderer omits the line at 0 so a routine
|
|
192
|
+
// incremental run does not print "replayed 0 live sessions" every time.
|
|
193
|
+
live_sessions: { type: 'integer', required: true },
|
|
194
|
+
entries: { type: 'integer', required: true },
|
|
195
|
+
sessions: { type: 'integer', required: true },
|
|
196
|
+
workspaces: { type: 'integer', required: true },
|
|
197
|
+
bytes: { type: 'integer', required: true },
|
|
198
|
+
elapsed_ms: { type: 'integer', required: true },
|
|
199
|
+
}),
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* The parameters the model fills in for the query tool. Which ones apply depends on `operation`; the
|
|
204
|
+
* conditional requirements (`query` for `search`, `session_id` for `session`) are enforced when the
|
|
205
|
+
* request is built, because the parameter DSL has no per-operation conditionals.
|
|
206
|
+
*/
|
|
207
|
+
export const SESSIONGRAPH_PARAMETERS = {
|
|
208
|
+
operation: {
|
|
209
|
+
type: 'string',
|
|
210
|
+
required: true,
|
|
211
|
+
enum: [...SESSIONGRAPH_OPERATIONS],
|
|
212
|
+
description: 'search(跨会话全文检索)、session(按 id 或唯一前缀读一个会话)、timeline(按时间列会话,新→旧)、workspaces(工作区清单与统计)、status(索引自身状态)。status 是唯一在还没有索引时也能回答的操作;要建或刷新索引请调用 sessiongraph_index 工具。',
|
|
213
|
+
},
|
|
214
|
+
query: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
description: 'search 必填:检索词。中文按双字切分,两字词即可命中(例如「广告」)。',
|
|
217
|
+
},
|
|
218
|
+
session_id: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
description: 'session 必填:完整会话 id,或长到只指向一个会话的前缀。',
|
|
221
|
+
},
|
|
222
|
+
workspace: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: '只看某个工作区:精确的 cwd,或不带结尾斜杠的路径前缀。',
|
|
225
|
+
},
|
|
226
|
+
after: {
|
|
227
|
+
type: 'number',
|
|
228
|
+
description: '只要这个 epoch 毫秒时间点之后(含)的条目/会话。',
|
|
229
|
+
},
|
|
230
|
+
before: {
|
|
231
|
+
type: 'number',
|
|
232
|
+
description: '只要这个 epoch 毫秒时间点之前(含)的条目/会话。',
|
|
233
|
+
},
|
|
234
|
+
kinds: {
|
|
235
|
+
type: 'array',
|
|
236
|
+
items: { type: 'string', enum: [...SESSIONGRAPH_ENTRY_KINDS] },
|
|
237
|
+
description: '只看这些条目类型:user(人真正打的字)、assistant(模型可见回复)、tool(工具调用的名字与参数)、title(会话标题)。省略即全部。',
|
|
238
|
+
},
|
|
239
|
+
limit: {
|
|
240
|
+
type: 'number',
|
|
241
|
+
description: '最多返回多少条;超出配置上限会被截断。省略按操作给默认值:search 10、timeline 20、workspaces 50、session 200 条 entries。',
|
|
242
|
+
},
|
|
243
|
+
offset: {
|
|
244
|
+
type: 'number',
|
|
245
|
+
description: '跳过前多少条,用于翻页。',
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* The parameters the dedicated `sessiongraph_index` tool takes: what to (re)build. It needs no
|
|
250
|
+
* `operation` — building the index is the only thing this tool does — and none of the query-side
|
|
251
|
+
* fields (`query`, `limit`, …), which an index run has no use for.
|
|
252
|
+
*/
|
|
253
|
+
export const SESSIONGRAPH_INDEX_PARAMETERS = {
|
|
254
|
+
force: {
|
|
255
|
+
type: 'boolean',
|
|
256
|
+
description: '全量重建:不跳过源文件自上次索引后没有变化的会话。',
|
|
257
|
+
},
|
|
258
|
+
workspace: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
description: '只索引 cwd 匹配这个工作区(精确,或不带结尾斜杠的路径前缀)的会话。',
|
|
261
|
+
},
|
|
262
|
+
session_id: {
|
|
263
|
+
type: 'string',
|
|
264
|
+
description: '只索引一个会话,按完整 id 或唯一前缀。',
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one line of index state this plugin contributes to the system prompt on every request.
|
|
3
|
+
*
|
|
4
|
+
* A model that has to call `status` before every query spends a tool round-trip learning something the
|
|
5
|
+
* host could have told it at assembly time. `ctx.systemPrompt.section` takes a function for exactly
|
|
6
|
+
* this case (`packages/tool/src/index.ts` passes one), and that function runs synchronously on every
|
|
7
|
+
* assembly — so this module must never reach the store: opening SQLite once per model request would
|
|
8
|
+
* put a database on the prompt-assembly path. It does one `stat` of the index file, plus (when an
|
|
9
|
+
* indexer left one) one small `status.json` read beside it.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here is authoritative: the file system says an index exists, the index says what is in it.
|
|
12
|
+
* Three outcomes are deliberately distinguished — a summary, an index without a summary (built by a
|
|
13
|
+
* run that recorded no sidecar), and no index at all. Collapsing the middle case into "no index" would
|
|
14
|
+
* invite rebuilding an index that is already there.
|
|
15
|
+
* @module dsh-plugin-sessiongraph-tool/status-hint
|
|
16
|
+
*/
|
|
17
|
+
/** The facts a `status.json` sidecar carries, as this module reads them. */
|
|
18
|
+
export interface IndexStatusFile {
|
|
19
|
+
/** Epoch milliseconds of the run that wrote the index. */
|
|
20
|
+
indexedAt: number;
|
|
21
|
+
/** Sessions in the index after that run. */
|
|
22
|
+
sessions: number;
|
|
23
|
+
/** Searchable entries after that run. */
|
|
24
|
+
entries: number;
|
|
25
|
+
/** Distinct workspaces after that run. */
|
|
26
|
+
workspaces: number;
|
|
27
|
+
/** Bytes the index file occupies. */
|
|
28
|
+
bytes: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read an indexing run's persisted summary.
|
|
32
|
+
* @param indexRoot - the directory the index lives in.
|
|
33
|
+
* @returns the summary, or `undefined` when the file is absent, unreadable, or not shaped like one.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readIndexStatus(indexRoot: string): IndexStatusFile | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The state of the session index, phrased for a system prompt.
|
|
38
|
+
* @param indexRoot - the absolute directory holding the index (`~` already expanded by the caller).
|
|
39
|
+
* @returns one sentence naming the state, or what the model should do about it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function indexStatusHint(indexRoot: string): string;
|
|
42
|
+
//# sourceMappingURL=status-hint.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one line of index state this plugin contributes to the system prompt on every request.
|
|
3
|
+
*
|
|
4
|
+
* A model that has to call `status` before every query spends a tool round-trip learning something the
|
|
5
|
+
* host could have told it at assembly time. `ctx.systemPrompt.section` takes a function for exactly
|
|
6
|
+
* this case (`packages/tool/src/index.ts` passes one), and that function runs synchronously on every
|
|
7
|
+
* assembly — so this module must never reach the store: opening SQLite once per model request would
|
|
8
|
+
* put a database on the prompt-assembly path. It does one `stat` of the index file, plus (when an
|
|
9
|
+
* indexer left one) one small `status.json` read beside it.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here is authoritative: the file system says an index exists, the index says what is in it.
|
|
12
|
+
* Three outcomes are deliberately distinguished — a summary, an index without a summary (built by a
|
|
13
|
+
* run that recorded no sidecar), and no index at all. Collapsing the middle case into "no index" would
|
|
14
|
+
* invite rebuilding an index that is already there.
|
|
15
|
+
* @module dsh-plugin-sessiongraph-tool/status-hint
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { INDEX_RELATIVE_PATH } from 'dsh-plugin-sessiongraph-service';
|
|
20
|
+
import { formatBytes, formatTime } from "./render.js";
|
|
21
|
+
/**
|
|
22
|
+
* Optional summary an indexer may leave beside `index.db` after a successful run.
|
|
23
|
+
*
|
|
24
|
+
* The design contract only requires the `meta` table inside the index, which cannot be read without
|
|
25
|
+
* opening the database — exactly what prompt assembly must not do. A writer that wants the richer
|
|
26
|
+
* prompt line therefore persists the same facts as JSON here; a writer that does not leaves it absent,
|
|
27
|
+
* and the hint degrades to the honest "index exists, no status record" wording instead of guessing.
|
|
28
|
+
*/
|
|
29
|
+
const STATUS_RELATIVE_PATH = 'status.json';
|
|
30
|
+
/**
|
|
31
|
+
* Read an indexing run's persisted summary.
|
|
32
|
+
* @param indexRoot - the directory the index lives in.
|
|
33
|
+
* @returns the summary, or `undefined` when the file is absent, unreadable, or not shaped like one.
|
|
34
|
+
*/
|
|
35
|
+
export function readIndexStatus(indexRoot) {
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(readFileSync(join(indexRoot, STATUS_RELATIVE_PATH), 'utf8'));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Absent, unreadable, or not JSON: all three mean "no summary to trust", which the caller renders
|
|
42
|
+
// as the weaker statement rather than as an error. A prompt line must never fail an assembly.
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
46
|
+
return undefined;
|
|
47
|
+
const record = parsed;
|
|
48
|
+
const indexedAt = numberField(record, 'indexedAt');
|
|
49
|
+
const sessions = numberField(record, 'sessions');
|
|
50
|
+
const entries = numberField(record, 'entries');
|
|
51
|
+
const workspaces = numberField(record, 'workspaces');
|
|
52
|
+
const bytes = numberField(record, 'bytes');
|
|
53
|
+
if (indexedAt === undefined || sessions === undefined || entries === undefined
|
|
54
|
+
|| workspaces === undefined || bytes === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
return { indexedAt, sessions, entries, workspaces, bytes };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One numeric field of a parsed object, or `undefined` when it is absent or not a finite number.
|
|
60
|
+
* @param record - the parsed object.
|
|
61
|
+
* @param field - the field name to read.
|
|
62
|
+
* @returns the number, or `undefined`.
|
|
63
|
+
*/
|
|
64
|
+
function numberField(record, field) {
|
|
65
|
+
const value = record[field];
|
|
66
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
67
|
+
}
|
|
68
|
+
/** The size and modification time of one file, or `undefined` when it does not exist. */
|
|
69
|
+
function statFile(path) {
|
|
70
|
+
try {
|
|
71
|
+
const stats = statSync(path);
|
|
72
|
+
return { size: stats.size, mtimeMs: stats.mtimeMs };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Missing, or a directory in the way, or no permission: all mean "cannot claim an index here".
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The state of the session index, phrased for a system prompt.
|
|
81
|
+
* @param indexRoot - the absolute directory holding the index (`~` already expanded by the caller).
|
|
82
|
+
* @returns one sentence naming the state, or what the model should do about it.
|
|
83
|
+
*/
|
|
84
|
+
export function indexStatusHint(indexRoot) {
|
|
85
|
+
const indexPath = join(indexRoot, INDEX_RELATIVE_PATH);
|
|
86
|
+
const index = statFile(indexPath);
|
|
87
|
+
if (index === undefined) {
|
|
88
|
+
return `这台机器还没有 sessiongraph 索引(${indexPath})。要检索过去的对话就先调用 sessiongraph_index 建一次:`
|
|
89
|
+
+ '它会扫描 ~/.dsh/sessions 下的全部会话,首次可能要几分钟,走它自己的 300 秒预算。';
|
|
90
|
+
}
|
|
91
|
+
const status = readIndexStatus(indexRoot);
|
|
92
|
+
if (status !== undefined) {
|
|
93
|
+
return `索引就绪(${indexPath}):${status.sessions} 个会话、${status.entries} 条可检索条目、`
|
|
94
|
+
+ `${status.workspaces} 个工作区、${formatBytes(status.bytes)};`
|
|
95
|
+
+ `最近一次建索引 ${formatTime(status.indexedAt)}。`;
|
|
96
|
+
}
|
|
97
|
+
return `索引文件已存在(${indexPath},${formatBytes(index.size)},${formatTime(index.mtimeMs)} 更新),`
|
|
98
|
+
+ '但没有 status 记录——规模与新鲜度用 sessiongraph status 查;怀疑过期时用 sessiongraph_index 刷新。';
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=status-hint.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-sessiongraph-tool",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Model-facing sessiongraph tools for DeepSeek Harness — a read-only sessiongraph tool that searches the whole local conversation history, plus a sessiongraph_index tool that builds or refreshes the index on its own, much larger timeout budget",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"cordis",
|
|
10
|
+
"cordis-plugin",
|
|
11
|
+
"session",
|
|
12
|
+
"session-history",
|
|
13
|
+
"conversation",
|
|
14
|
+
"search",
|
|
15
|
+
"fts",
|
|
16
|
+
"ai-agent",
|
|
17
|
+
"llm-tools",
|
|
18
|
+
"agent-tools"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "CC ZHAO",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/CC19990113/dsh-plugin-sessiongraph.git",
|
|
25
|
+
"directory": "packages/tool"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/CC19990113/dsh-plugin-sessiongraph#readme",
|
|
28
|
+
"bugs": "https://github.com/CC19990113/dsh-plugin-sessiongraph/issues",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "lib/index.js",
|
|
31
|
+
"types": "lib/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./lib/index.d.ts",
|
|
35
|
+
"default": "./lib/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./invariant": {
|
|
38
|
+
"types": "./lib/invariant.d.ts",
|
|
39
|
+
"default": "./lib/invariant.js"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"lib/**/*.js",
|
|
45
|
+
"lib/**/*.d.ts"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": false
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
53
|
+
"dsh-plugin-sessiongraph-service": "^0.1.1",
|
|
54
|
+
"dsh-plugin-sessiongraph-sessions": "^0.1.1"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@deepseek-ai/cordis": ">=4.0.1",
|
|
58
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.0-rc.6",
|
|
59
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6",
|
|
60
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.6",
|
|
61
|
+
"@deepseek-ai/dsh-timeout": ">=0.1.0-rc.6",
|
|
62
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.6"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
66
|
+
"@deepseek-ai/dsh-invariants": "0.1.0-rc.6",
|
|
67
|
+
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
68
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.6",
|
|
69
|
+
"@deepseek-ai/dsh-timeout": "0.1.0-rc.6",
|
|
70
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.6"
|
|
71
|
+
}
|
|
6
72
|
}
|
package/README.md
DELETED