@try-works/dsh-memory 0.1.21 → 0.1.22

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.
Files changed (3) hide show
  1. package/STATUS.md +6 -5
  2. package/package.json +1 -1
  3. package/src/host.js +27 -6
package/STATUS.md CHANGED
@@ -147,11 +147,12 @@ the flag on did not change what the user sees.
147
147
 
148
148
  ### Blocked on a restart
149
149
 
150
- - [ ] **Publish 0.1.21, install, restart.** The guard against unmarked `memory/map` events is
151
- written and tested (403 pass) but the publish needs npm credentials that were not available
152
- in this session (`npm publish` → ENEEDAUTH). Installed 0.1.20 already appends WITH the
153
- marker, so nothing is at risk while this waits; the guard only protects against a harness
154
- older than the `append(type, data, { ignorable: true })` option.
150
+ - [ ] **Restart to load 0.1.21.** Published and installed: 0.1.21 is on npm (tarball byte-identical to
151
+ source) and installed in the profile (host.js byte-identical, guard at host.js:727/753). The
152
+ profile's minimumReleaseAge gate needed its dsh-memory exclusion extended to 0.1.21 in
153
+ pnpm-workspace.yaml, the same convention as every earlier version. The host half loads at
154
+ boot, so the guard only takes effect after a restart; installed 0.1.20 already appends WITH
155
+ the marker, so nothing is at risk while this waits.
155
156
  - [x] **Restart, then check the tasks layer — DONE 2026-10-09.** The restart confirmed it. See the
156
157
  tasks-layer section above for the two log entries and the counts. It writes to ANOTHER plugin's storage through
157
158
  that plugin's service, so it deserves a real observation rather than a passing suite. After
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@try-works/dsh-memory",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Git-backed, workspace-scoped memory for the DeepSeek Harness (global layer + per-workspace layers)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/host.js CHANGED
@@ -29,15 +29,28 @@ const TASKS_BACKUP_MS = 30 * 60 * 1000
29
29
  const IDLE_TICK_MS = 60_000
30
30
 
31
31
  function makeGitRunner(ctx) {
32
- const subprocess = ctx.get('subprocess')
33
- if (!subprocess || typeof subprocess.resolveExecutable !== 'function' || typeof subprocess.spawn !== 'function') return null
32
+ // The subprocess service is NOT resolvable during apply. cordis ctx.get() only returns an
33
+ // implementation whose PROVIDING FIBER IS ACTIVE (vendor/cordis reflect.ts _getImpl), and no
34
+ // fiber is active while the composition is still mounting. The first version probed here,
35
+ // once, at wire time - it cached undefined, returned null, and every git path was dead
36
+ // forever: zero sync events ever recorded, and the history hook threw "Cannot read
37
+ // properties of null (reading 'run')" every hour. The service exists by first USE (an agent
38
+ // pre-step, minutes after boot), so resolve lazily and keep re-probing until it appears.
39
+ let subprocess
40
+ const probe = () => {
41
+ const svc = ctx.get('subprocess')
42
+ if (svc && typeof svc.resolveExecutable === 'function' && typeof svc.spawn === 'function') subprocess = svc
43
+ return subprocess
44
+ }
34
45
  let executablePromise
35
46
  const run = async (argv, opts = {}) => {
36
- if (!executablePromise) executablePromise = subprocess.resolveExecutable('git', undefined, undefined).catch(() => null)
47
+ const svc = probe()
48
+ if (!svc) return { exitCode: 127, stdout: '', stderr: 'subprocess service unavailable' }
49
+ if (!executablePromise) executablePromise = svc.resolveExecutable('git', undefined, undefined).catch(() => null)
37
50
  const executable = await executablePromise
38
51
  if (!executable) return { exitCode: 127, stdout: '', stderr: 'git unavailable' }
39
52
  try {
40
- const handle = subprocess.spawn({
53
+ const handle = svc.spawn({
41
54
  argv: [executable, ...argv],
42
55
  cwd: opts.cwd,
43
56
  stdio: { stdin: 'ignore', stdout: { maxBytes: 4 * 1024 * 1024 }, stderr: { maxBytes: 16 * 1024 } },
@@ -52,7 +65,9 @@ function makeGitRunner(ctx) {
52
65
  return { exitCode: 1, stdout: '', stderr: String(error) }
53
66
  }
54
67
  }
55
- return new GitRunner(run)
68
+ const runner = new GitRunner(run)
69
+ runner.available = () => Boolean(probe())
70
+ return runner
56
71
  }
57
72
 
58
73
  function resolveTarget(agent) {
@@ -235,6 +250,9 @@ export function wireHost(ctx, store, cfg) {
235
250
  if (!workspace) return { ok: false, reason: 'no workspace for this session', added: 0 }
236
251
  const path = readAssociations(store.root)[workspace]?.path
237
252
  if (!path) return { ok: false, reason: 'no local folder associated with ' + workspace, added: 0 }
253
+ // Report the honest reason instead of throwing: a hook that throws hourly because a service
254
+ // was probed too early must never look like a repository with no commits.
255
+ if (!git.available()) return { ok: false, reason: 'subprocess service unavailable', added: 0 }
238
256
  return ingestHistory(store.root, workspace, path, (argv, opts) => git.run(argv, opts), { limit })
239
257
  }
240
258
 
@@ -570,6 +588,9 @@ export function wireHost(ctx, store, cfg) {
570
588
  }
571
589
 
572
590
  async function runSync(force = false) {
591
+ // Not a git failure: no CONFLICT marker, no status record - the capability simply is not
592
+ // there yet. available() re-probes, so this is true only while the host is still mounting.
593
+ if (!git.available()) return { initialized: false, committed: false, synced: false, error: 'subprocess service unavailable' }
573
594
  const branch = cfg.gitBranch || 'main'
574
595
  const hasGit = existsSync(join(store.root, '.git'))
575
596
  if (!hasGit) {
@@ -764,7 +785,7 @@ export function wireHost(ctx, store, cfg) {
764
785
  }
765
786
 
766
787
  function maybeSync() {
767
- if (syncInFlight || !git) return
788
+ if (syncInFlight || !git.available()) return
768
789
  if (!scheduler.shouldCommit() && !scheduler.shouldSync()) return
769
790
  syncInFlight = true
770
791
  // Sync wrote its outcome only to state/sync.json, so the log could not answer whether the