weir-harness 1.2.3 → 2.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.
Files changed (117) hide show
  1. package/cordis.patch.yml +7 -18
  2. package/lib/client.capability-badge.js +2 -0
  3. package/lib/client.capability-manager.js +46 -179
  4. package/lib/client.capability-model.js +428 -3
  5. package/lib/client.file-lease-badge.js +307 -0
  6. package/lib/client.file-lease-tool-model.js +189 -0
  7. package/lib/client.file-lease-tool-view.js +434 -0
  8. package/lib/client.js +124 -36
  9. package/lib/client.mcp-manager.js +563 -0
  10. package/lib/client.settings-page.js +10 -34
  11. package/package.json +2 -2
  12. package/skills/mcp-server-setup/SKILL.md +124 -0
  13. package/src/capabilities/apply-engine.js +18 -4
  14. package/src/capabilities/capability-remote.js +296 -45
  15. package/src/capabilities/initial-selection.js +3 -2
  16. package/src/capabilities/lifecycle-preload.js +69 -11
  17. package/src/capabilities/lifecycle-snapshot.js +127 -39
  18. package/src/capabilities/mcp-admission.js +2 -2
  19. package/src/capabilities/mcp-config.js +344 -0
  20. package/src/capabilities/mcp-credentials.js +180 -0
  21. package/src/capabilities/mcp-diagnostics.js +169 -0
  22. package/src/capabilities/mcp-drain.js +18 -4
  23. package/src/capabilities/mcp-facade.js +3 -3
  24. package/src/capabilities/mcp-gate.js +51 -52
  25. package/src/capabilities/mcp-install-tool.js +276 -0
  26. package/src/capabilities/mcp-manager.js +368 -142
  27. package/src/capabilities/mcp-mount.js +219 -79
  28. package/src/capabilities/mcp-operations.js +359 -0
  29. package/src/capabilities/mcp-process.js +43 -0
  30. package/src/capabilities/mcp-registry.js +74 -60
  31. package/src/capabilities/mcp-runtime.js +103 -0
  32. package/src/capabilities/read-payloads.js +8 -3
  33. package/src/capabilities/skill-applicability.js +185 -0
  34. package/src/capabilities/skill-builtin-migration.js +2 -1
  35. package/src/capabilities/skill-selection-plugin.js +161 -56
  36. package/src/capabilities/store/checked-contract.d.ts +2 -2
  37. package/src/capabilities/store/fs-adapter.js +2 -3
  38. package/src/context-guard/index.js +2 -2
  39. package/src/delegate/supervision-mount.js +5 -5
  40. package/src/delegate/tool.js +2 -10
  41. package/src/file-lease/client.js +1299 -0
  42. package/src/file-lease/index.js +34 -0
  43. package/src/file-lease/lineage.js +207 -0
  44. package/src/file-lease/node-contracts.d.ts +42 -0
  45. package/src/file-lease/plugin.js +1013 -0
  46. package/src/file-lease/singleton.js +155 -0
  47. package/src/file-lease/spawn.js +62 -0
  48. package/src/file-lease/tools.js +847 -0
  49. package/src/file-lease/view.js +144 -0
  50. package/src/file-lease/watch.js +473 -0
  51. package/src/hashline-edit/index.js +76 -42
  52. package/src/hashline-edit/write-tool.js +90 -0
  53. package/src/intent-gate/index.js +29 -12
  54. package/src/intent-gate/matcher.js +36 -2
  55. package/src/lease-broker/entry.js +98 -0
  56. package/src/lease-broker/node-contracts.d.ts +63 -0
  57. package/src/lease-broker/protocol.js +237 -0
  58. package/src/lease-broker/server.js +402 -0
  59. package/src/lease-broker/state.js +482 -0
  60. package/src/lsp/tools.js +137 -27
  61. package/src/process/index.js +4 -2
  62. package/src/settings/index.js +2 -44
  63. package/src/settings/sections.js +2 -24
  64. package/src/shared/audit.js +9 -1
  65. package/src/shared/child-scope.js +2 -0
  66. package/src/todo-driver/index.js +1 -6
  67. package/src/worktree/cards.js +6 -21
  68. package/src/worktree/lane-core.js +1 -19
  69. package/src/worktree/lane-workflows.js +11 -32
  70. package/src/worktree/rules.js +1 -1
  71. package/lib/client.edit-lock-maintenance.js +0 -592
  72. package/lib/client.edit-lock-panel.js +0 -405
  73. package/src/edit-lock/adapter.js +0 -57
  74. package/src/edit-lock/admin-ledger.js +0 -149
  75. package/src/edit-lock/admin-recovery.js +0 -151
  76. package/src/edit-lock/admission.js +0 -82
  77. package/src/edit-lock/binding-attestation.js +0 -29
  78. package/src/edit-lock/binding-guidance.js +0 -72
  79. package/src/edit-lock/binding-machine.js +0 -312
  80. package/src/edit-lock/call-context.js +0 -65
  81. package/src/edit-lock/derived-facts.js +0 -67
  82. package/src/edit-lock/domain.js +0 -165
  83. package/src/edit-lock/domains.js +0 -170
  84. package/src/edit-lock/host.js +0 -149
  85. package/src/edit-lock/incarnations.js +0 -150
  86. package/src/edit-lock/index.js +0 -1303
  87. package/src/edit-lock/inspect.js +0 -219
  88. package/src/edit-lock/lifecycle.js +0 -579
  89. package/src/edit-lock/limits.js +0 -61
  90. package/src/edit-lock/maintenance.js +0 -310
  91. package/src/edit-lock/manager.js +0 -875
  92. package/src/edit-lock/negotiation.js +0 -132
  93. package/src/edit-lock/operation-history.js +0 -214
  94. package/src/edit-lock/peer-client.js +0 -86
  95. package/src/edit-lock/peer-transport.js +0 -76
  96. package/src/edit-lock/peer.js +0 -87
  97. package/src/edit-lock/publisher.js +0 -191
  98. package/src/edit-lock/read-authority.js +0 -101
  99. package/src/edit-lock/recovery.js +0 -139
  100. package/src/edit-lock/remote.js +0 -362
  101. package/src/edit-lock/request-binding.js +0 -42
  102. package/src/edit-lock/request-data.js +0 -40
  103. package/src/edit-lock/reservation.js +0 -301
  104. package/src/edit-lock/reserved-runtime.js +0 -45
  105. package/src/edit-lock/resource-identity.js +0 -130
  106. package/src/edit-lock/runtime.js +0 -145
  107. package/src/edit-lock/settle.js +0 -144
  108. package/src/edit-lock/snapshot.js +0 -175
  109. package/src/edit-lock/stale-sweep.js +0 -120
  110. package/src/edit-lock/state.js +0 -462
  111. package/src/edit-lock/store.js +0 -327
  112. package/src/edit-lock/tool-scope.js +0 -29
  113. package/src/edit-lock/view.js +0 -170
  114. package/src/edit-lock/write-scopes.js +0 -48
  115. package/src/edit-lock/write-tool.js +0 -45
  116. package/src/shared/delegate-edit-lifecycle.js +0 -36
  117. package/src/worktree/authority.js +0 -56
@@ -0,0 +1,155 @@
1
+ // The process-wide lease client and the wiring the managed editors share
2
+ // (design D8, D9, D11).
3
+ //
4
+ // ONE client per endpoint for the whole process, created lazily on first use and
5
+ // NEVER closed: two preset mounts of this bundle share one connection and one
6
+ // lease ledger, so unmounting a mount must not rip the connection out from under
7
+ // its sibling. There is deliberately no reference counting and no dispose hook —
8
+ // the client's lifetime is the process's, and its own idle window plus the
9
+ // daemon's disconnect rule are what end leases.
10
+ //
11
+ // `createEditorLease` is the second half of the same wiring: which client, who
12
+ // the caller is, and which opaque scope string it reports. It is here (rather
13
+ // than repeated in `hashline-edit` and `lsp`) because design D9 makes owner
14
+ // identity a single rule — a delegated child inherits its spawner's lineage, so
15
+ // it never conflicts with the session that spawned it. Two copies of that rule
16
+ // would drift, and the drift would only show up as a stuck delegation chain.
17
+ //
18
+ // Nothing here connects, spawns or audits on import: creating a client allocates
19
+ // state, and its first request is what dials.
20
+
21
+ import process from 'node:process'
22
+ import { createLeaseClient } from './client.js'
23
+ import { createLineageResolver } from './lineage.js'
24
+ import { defaultSocketPath } from '../lease-broker/server.js'
25
+ import { PROTOCOL } from '../lease-broker/protocol.js'
26
+
27
+ /**
28
+ * Opaque scope used when neither the session nor the call resolved a root. It is
29
+ * a `status` filter label and never an authority (design D8): reporting `'/'`
30
+ * for a composition with no cwd beats refusing to acquire a lease.
31
+ */
32
+ const FALLBACK_SCOPE = '/'
33
+
34
+ /**
35
+ * Clients this process has created, keyed by endpoint. Never emptied — see the
36
+ * module comment. The map is the whole "singleton": keyed by socket path, so two
37
+ * protocol majors (or a test's private endpoint) never share a client.
38
+ * @type {Map<string, any>}
39
+ */
40
+ const clients = new Map()
41
+
42
+ /** @param {unknown} value @returns {string | undefined} */
43
+ function text(value) {
44
+ return typeof value === 'string' && value.length > 0 ? value : undefined
45
+ }
46
+
47
+ /**
48
+ * The endpoint this bundle's protocol major arbitrates on.
49
+ *
50
+ * `WEIR_EDITLOCKD_SOCKET` wins when set. The daemon entry reads the same
51
+ * variable, so a composer that owns a private root (the integration harness) can
52
+ * move BOTH halves to one private socket with a single knob; leaving it unset
53
+ * keeps the machine-level per-user default. Nothing else consults the variable.
54
+ * @returns {string}
55
+ */
56
+ export function defaultLeaseEndpoint() {
57
+ const override = text(process.env.WEIR_EDITLOCKD_SOCKET)
58
+ if (override !== undefined) return override
59
+ return defaultSocketPath({ protocol: PROTOCOL })
60
+ }
61
+
62
+ /**
63
+ * The process-wide client for one endpoint (design D11).
64
+ *
65
+ * The FIRST call creates it; every later call for the same endpoint returns that
66
+ * same instance, whatever overrides it passes — the connection and the lease
67
+ * ledger are process state, not per-mount state. Creation itself touches
68
+ * nothing: the client dials (and spawns) on its first request.
69
+ *
70
+ * @param {any} [overrides] `createLeaseClient` options; `socketPath` selects the endpoint
71
+ * @returns {any} the shared client
72
+ */
73
+ export function getSharedLeaseClient(overrides = {}) {
74
+ const socketPath = text(overrides?.socketPath) ?? defaultLeaseEndpoint()
75
+ const existing = clients.get(socketPath)
76
+ if (existing !== undefined) return existing
77
+ const created = createLeaseClient({ ...overrides, socketPath })
78
+ clients.set(socketPath, created)
79
+ return created
80
+ }
81
+
82
+ /**
83
+ * @typedef {object} LeaseOwner
84
+ * @property {string} id the lineage (opaque to the arbiter)
85
+ * @property {string} session the session that last touched the lease
86
+ * @property {string} label presentation only
87
+ */
88
+
89
+ /**
90
+ * @typedef {object} EditorLease
91
+ * @property {() => any} client the client to speak through (injected, else the process-wide one)
92
+ * @property {(agent: any) => LeaseOwner} ownerOf who the calling agent writes as
93
+ * @property {(agent: any, fallback?: string) => string} scopeOf the opaque project root to report
94
+ */
95
+
96
+ /**
97
+ * The `sessions` registry, read defensively: a cold or partially mounted host has
98
+ * none, and the resolver degrades to "this session is its own lineage".
99
+ * @param {any} ctx
100
+ * @returns {{ get?: (id: string) => any } | undefined}
101
+ */
102
+ function sessionsOf(ctx) {
103
+ try {
104
+ return ctx?.get?.('sessions') ?? undefined
105
+ } catch {
106
+ return undefined
107
+ }
108
+ }
109
+
110
+ /**
111
+ * The session this agent acts for: the durable header id when there is one, else
112
+ * the live session id, else the agent id. `undefined` only for an agent object
113
+ * with no identity at all — the caller then reports the lineage as the session.
114
+ * @param {any} agent
115
+ * @returns {string | undefined}
116
+ */
117
+ function sessionIdOf(agent) {
118
+ return text(agent?.session?.header?.id) ?? text(agent?.session?.id) ?? text(agent?.id)
119
+ }
120
+
121
+ /**
122
+ * Create the lease wiring for one managed editor.
123
+ *
124
+ * `leaseClient` is the injection seam tests (and any future embedder) use; when
125
+ * it is absent the process-wide client is resolved lazily on the first managed
126
+ * write, so a composition that never writes never constructs one.
127
+ *
128
+ * @param {{ leaseClient?: any, ctx?: any }} [options]
129
+ * @returns {EditorLease}
130
+ */
131
+ export function createEditorLease({ leaseClient, ctx } = {}) {
132
+ /** @type {any} */
133
+ const injected = leaseClient ?? null
134
+ /** @type {any} */
135
+ let shared = null
136
+ const resolver = createLineageResolver({ sessions: sessionsOf(ctx) })
137
+ return Object.freeze({
138
+ client() {
139
+ if (injected !== null) return injected
140
+ if (shared === null) shared = getSharedLeaseClient()
141
+ return shared
142
+ },
143
+ ownerOf(agent) {
144
+ const lineage = resolver.lineageOf(agent)
145
+ return {
146
+ id: lineage,
147
+ session: sessionIdOf(agent) ?? lineage,
148
+ label: resolver.labelOf(agent),
149
+ }
150
+ },
151
+ scopeOf(agent, fallback) {
152
+ return text(agent?.session?.header?.cwd) ?? text(fallback) ?? FALLBACK_SCOPE
153
+ },
154
+ })
155
+ }
@@ -0,0 +1,62 @@
1
+ // Start `weir-editlockd` as a detached child of this process.
2
+ //
3
+ // This is the only place in the client half that touches the process table, and
4
+ // it is deliberately dumb: fire, forget, never wait, never throw. A `null`
5
+ // return is a normal outcome — the binary may be gone, the socket directory may
6
+ // be unwritable, or the platform may refuse the spawn — and the caller answers
7
+ // it by degrading the write instead of blocking it (design D7).
8
+ //
9
+ // Why `ELECTRON_RUN_AS_NODE=1` (do not remove):
10
+ //
11
+ // The host CLI starts DSH as `ELECTRON_RUN_AS_NODE=1 exec "DeepSeek Harness"
12
+ // cli.js`, so inside a live session `process.execPath` is the Electron binary,
13
+ // not `node`. Launching a script with it without that variable boots a second
14
+ // Electron app instead of running our daemon. Under a plain Node binary the
15
+ // variable is a harmless no-op, which is why the tests can use this same module
16
+ // unchanged.
17
+ //
18
+ // `detached` + `unref()` (rather than `stdio: inherit`) is what makes the
19
+ // daemon outlive the caller: the client is not this daemon's supervisor, and
20
+ // the daemon's own idle window is the only thing that ends it.
21
+
22
+ import { spawn } from 'node:child_process'
23
+ import process, { execPath } from 'node:process'
24
+ import { fileURLToPath } from 'node:url'
25
+
26
+ /** The daemon entry shipped next to this module's package. */
27
+ const DEFAULT_ENTRY_URL = new URL('../lease-broker/entry.js', import.meta.url)
28
+
29
+ /**
30
+ * @typedef {object} SpawnDaemonOptions
31
+ * @property {URL | string} [entryUrl] executable script; defaults to `../lease-broker/entry.js`
32
+ * @property {Record<string, string | undefined>} [env] extra environment (socket path, idle window)
33
+ */
34
+
35
+ /**
36
+ * Spawn the lease broker daemon and forget about it.
37
+ *
38
+ * Never throws and never resolves anything: the caller learns "a daemon may now
39
+ * be starting" (truthy) or "no daemon was started" (`null`) and re-tries the
40
+ * socket either way. Failures are intentionally silent — an unstartable daemon
41
+ * is an infrastructure degrade, not an error the user has to see or fix.
42
+ *
43
+ * @param {SpawnDaemonOptions} [options]
44
+ * @returns {import('node:child_process').ChildProcess | null} the detached child, or `null` when the spawn failed
45
+ */
46
+ export function spawnDaemon({ entryUrl = DEFAULT_ENTRY_URL, env } = {}) {
47
+ try {
48
+ const entry = fileURLToPath(entryUrl)
49
+ const child = spawn(execPath, [entry], {
50
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: '1', ...env },
51
+ detached: true,
52
+ stdio: 'ignore',
53
+ windowsHide: true,
54
+ })
55
+ // The client is not the daemon's supervisor: nothing here may keep this
56
+ // process alive, and nothing here waits for the daemon to become reachable.
57
+ child.unref()
58
+ return child
59
+ } catch {
60
+ return null
61
+ }
62
+ }