dsh-plugin-sessiongraph-sessions 0.1.18 → 0.1.20

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/lib/index.d.ts CHANGED
@@ -16,9 +16,11 @@
16
16
  *
17
17
  * Every session creation also does indexing work in the background (`agent/created`, `autoIndex`):
18
18
  * an existing index is refreshed incrementally, and a missing one is built from scratch (`autoBuild`,
19
- * default true) so a fresh install becomes searchable without the user running anything. Both runs
20
- * go through the host's job registry and never block session creation. `autoBuild: false` restores
21
- * the stricter behaviour where nothing is indexed until the caller asks for it.
19
+ * default true) so a fresh install becomes searchable without the user running anything. Those runs
20
+ * are plain background promises, not host jobs: a registered job reports its settlement back to the
21
+ * owning agent, which would put an unasked-for maintenance notice into the transcript and (under the
22
+ * host's default `wakeup` delivery) open a turn of its own. `autoBuild: false` restores the stricter
23
+ * behaviour where nothing is indexed until the caller asks for it.
22
24
  *
23
25
  * Sessions the host still holds in memory (`ctx.sessionQuery`) are indexed as well, in a second pass
24
26
  * after the file-path transaction commits: a conversation that is still running has no log on disk
package/lib/index.js CHANGED
@@ -16,9 +16,11 @@
16
16
  *
17
17
  * Every session creation also does indexing work in the background (`agent/created`, `autoIndex`):
18
18
  * an existing index is refreshed incrementally, and a missing one is built from scratch (`autoBuild`,
19
- * default true) so a fresh install becomes searchable without the user running anything. Both runs
20
- * go through the host's job registry and never block session creation. `autoBuild: false` restores
21
- * the stricter behaviour where nothing is indexed until the caller asks for it.
19
+ * default true) so a fresh install becomes searchable without the user running anything. Those runs
20
+ * are plain background promises, not host jobs: a registered job reports its settlement back to the
21
+ * owning agent, which would put an unasked-for maintenance notice into the transcript and (under the
22
+ * host's default `wakeup` delivery) open a turn of its own. `autoBuild: false` restores the stricter
23
+ * behaviour where nothing is indexed until the caller asks for it.
22
24
  *
23
25
  * Sessions the host still holds in memory (`ctx.sessionQuery`) are indexed as well, in a second pass
24
26
  * after the file-path transaction commits: a conversation that is still running has no log on disk
@@ -118,13 +120,17 @@ export function apply(ctx, config) {
118
120
  // said is findable, and "once until restart" stopped being enough the moment the desktop stayed up
119
121
  // for days. The window still absorbs a burst of session creations into one run.
120
122
  const autoIndexRuns = new Map();
123
+ // Roots whose automatic run is still executing. Kept apart from `autoIndexRuns`, which is a time
124
+ // window: a first build can outlive the window, and two runs on one root would only fight over the
125
+ // write lock.
126
+ const autoIndexInFlight = new Set();
121
127
  // `agent/created` is typed by the host's agent service; this package reads only the two fields it
122
128
  // needs through a structural face rather than depending on that package.
123
129
  const agentEvents = ctx;
124
130
  ctx.effect(function* () {
125
131
  if (resolved.autoIndex) {
126
132
  yield agentEvents.on('agent/created', payload => {
127
- scheduleAutoIndex(ctx, resolved, autoIndexRuns, payload);
133
+ scheduleAutoIndex(resolved, autoIndexRuns, autoIndexInFlight, payload);
128
134
  });
129
135
  }
130
136
  yield ctx.sessiongraph.registerIndexer(indexer);
@@ -172,13 +178,22 @@ export function isBackgroundIndexReport(report) {
172
178
  *
173
179
  * Runs synchronously inside `agent/created` dispatch, which happens during agent registration, so it
174
180
  * must never block or throw: a synchronous index here would hold up the session's own creation. It
175
- * only decides whether a refresh is warranted and hands one to the host's job registry.
176
- * @param ctx - the plugin context, consulted for the optional job registry.
181
+ * only decides whether a refresh is warranted and starts one in the background.
182
+ *
183
+ * The run deliberately does **not** go through the host's job registry. A registered job announces
184
+ * its settlement to the owning agent — the host injects a completion notice into the next step and,
185
+ * under the default `wakeup` delivery, opens a turn on an idle one. A maintenance refresh nobody
186
+ * asked for must not appear in the transcript or start a reply of its own, so this runs as a plain
187
+ * background promise: the index still keeps itself fresh, and the conversation stays quiet. The
188
+ * explicit `sessiongraph_index` path is the one that still registers a job, because there a caller
189
+ * is waiting for the id.
177
190
  * @param config - the resolved plugin configuration.
178
191
  * @param autoIndexRuns - when each root last had an automatic refresh, in epoch milliseconds.
192
+ * @param autoIndexInFlight - roots with a background run still executing, so a slow first build cannot
193
+ * be started twice by sessions that open past the time window.
179
194
  * @param payload - the `agent/created` dispatch payload.
180
195
  */
181
- function scheduleAutoIndex(ctx, config, autoIndexRuns, payload) {
196
+ function scheduleAutoIndex(config, autoIndexRuns, autoIndexInFlight, payload) {
182
197
  try {
183
198
  const agent = payload.agent;
184
199
  const workspace = agent?.session?.header?.cwd;
@@ -193,29 +208,20 @@ function scheduleAutoIndex(ctx, config, autoIndexRuns, payload) {
193
208
  const previous = autoIndexRuns.get(indexRoot);
194
209
  if (previous !== undefined && now - previous < config.autoIndexMinIntervalMs)
195
210
  return;
211
+ // A run already executing for this root is the one the window would have deduplicated if it had
212
+ // been slow enough to straddle it; starting a second would only collide on the index write lock.
213
+ if (autoIndexInFlight.has(indexRoot))
214
+ return;
196
215
  // A first build reads every log on the machine and can take a while, so it stays a background
197
- // job like the incremental refresh. `autoBuild: false` restores the stricter behaviour where
216
+ // run like the incremental refresh. `autoBuild: false` restores the stricter behaviour where
198
217
  // nothing becomes searchable until someone asks for it.
199
218
  const existing = hasExistingIndex(indexRoot);
200
219
  if (!existing && !config.autoBuild)
201
220
  return;
202
- // `ctx.get` rather than an `inject` entry: background jobs are a host composition choice, and
203
- // requiring the service would hide this whole indexer from a profile that runs none.
204
- const jobs = ctx.get('jobs');
205
- if (jobs === undefined)
206
- return;
207
- jobs.start({
208
- // The registry keys a job by its owning session id, not the agent object.
209
- owner: agent.id,
210
- kind: AUTO_INDEX_JOB_KIND,
211
- label: existing
212
- ? `sessiongraph refresh for ${workspace}`
213
- : `sessiongraph first build for ${workspace}`,
214
- run: handle => runIndexJob(indexRoot, {}, config, handle),
215
- });
216
- // Marked only after the registry accepted the job, so a refusal leaves the root eligible for
217
- // the next session that opens.
218
221
  autoIndexRuns.set(indexRoot, now);
222
+ autoIndexInFlight.add(indexRoot);
223
+ // Never rejects: a lost refresh is a degradation, not an error the session can act on.
224
+ void runAutomaticIndex(indexRoot, config).finally(() => autoIndexInFlight.delete(indexRoot));
219
225
  }
220
226
  catch (error) {
221
227
  // A throwing listener would veto publication of the agent. Losing a refresh is a degradation,
@@ -223,6 +229,22 @@ function scheduleAutoIndex(ctx, config, autoIndexRuns, payload) {
223
229
  console.error(`sessiongraph-sessions: automatic index after session creation failed: ${describeError(error)}`);
224
230
  }
225
231
  }
232
+ /**
233
+ * Run one automatic refresh to completion, reporting a failure instead of propagating it.
234
+ *
235
+ * The run gets no abort signal of its own: nothing in the transcript can cancel it, and the write
236
+ * lock makes an abandoned or duplicated run harmless. Session creation never waits on this promise.
237
+ * @param indexRoot - the index to refresh.
238
+ * @param config - the resolved plugin configuration.
239
+ */
240
+ async function runAutomaticIndex(indexRoot, config) {
241
+ try {
242
+ await runIndex(indexRoot, {}, undefined, config);
243
+ }
244
+ catch (error) {
245
+ console.error(`sessiongraph-sessions: automatic index for ${indexRoot} failed: ${describeError(error)}`);
246
+ }
247
+ }
226
248
  /**
227
249
  * Hand one explicit index run to the host's background-job registry, when the run is large enough to
228
250
  * outlive a tool call.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-sessiongraph-sessions",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Session-log indexer for DeepSeek Harness — scans ~/.dsh/sessions, decompresses the multi-frame zstd JSONL logs and writes the sessiongraph SQLite index the store reads",
5
5
  "keywords": [
6
6
  "dsh",
@@ -49,8 +49,8 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@deepseek-ai/schemastery": "^3.18.1",
52
- "dsh-plugin-sessiongraph-native": "^0.1.18",
53
- "dsh-plugin-sessiongraph-service": "^0.1.18"
52
+ "dsh-plugin-sessiongraph-service": "^0.1.20",
53
+ "dsh-plugin-sessiongraph-native": "^0.1.20"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@deepseek-ai/cordis": ">=4.0.1",