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.
- package/cordis.patch.yml +7 -18
- package/lib/client.capability-badge.js +2 -0
- package/lib/client.capability-manager.js +46 -179
- package/lib/client.capability-model.js +428 -3
- package/lib/client.file-lease-badge.js +307 -0
- package/lib/client.file-lease-tool-model.js +189 -0
- package/lib/client.file-lease-tool-view.js +434 -0
- package/lib/client.js +124 -36
- package/lib/client.mcp-manager.js +563 -0
- package/lib/client.settings-page.js +10 -34
- package/package.json +2 -2
- package/skills/mcp-server-setup/SKILL.md +124 -0
- package/src/capabilities/apply-engine.js +18 -4
- package/src/capabilities/capability-remote.js +296 -45
- package/src/capabilities/initial-selection.js +3 -2
- package/src/capabilities/lifecycle-preload.js +69 -11
- package/src/capabilities/lifecycle-snapshot.js +127 -39
- package/src/capabilities/mcp-admission.js +2 -2
- package/src/capabilities/mcp-config.js +344 -0
- package/src/capabilities/mcp-credentials.js +180 -0
- package/src/capabilities/mcp-diagnostics.js +169 -0
- package/src/capabilities/mcp-drain.js +18 -4
- package/src/capabilities/mcp-facade.js +3 -3
- package/src/capabilities/mcp-gate.js +51 -52
- package/src/capabilities/mcp-install-tool.js +276 -0
- package/src/capabilities/mcp-manager.js +368 -142
- package/src/capabilities/mcp-mount.js +219 -79
- package/src/capabilities/mcp-operations.js +359 -0
- package/src/capabilities/mcp-process.js +43 -0
- package/src/capabilities/mcp-registry.js +74 -60
- package/src/capabilities/mcp-runtime.js +103 -0
- package/src/capabilities/read-payloads.js +8 -3
- package/src/capabilities/skill-applicability.js +185 -0
- package/src/capabilities/skill-builtin-migration.js +2 -1
- package/src/capabilities/skill-selection-plugin.js +161 -56
- package/src/capabilities/store/checked-contract.d.ts +2 -2
- package/src/capabilities/store/fs-adapter.js +2 -3
- package/src/context-guard/index.js +2 -2
- package/src/delegate/supervision-mount.js +5 -5
- package/src/delegate/tool.js +2 -10
- package/src/file-lease/client.js +1299 -0
- package/src/file-lease/index.js +34 -0
- package/src/file-lease/lineage.js +207 -0
- package/src/file-lease/node-contracts.d.ts +42 -0
- package/src/file-lease/plugin.js +1013 -0
- package/src/file-lease/singleton.js +155 -0
- package/src/file-lease/spawn.js +62 -0
- package/src/file-lease/tools.js +847 -0
- package/src/file-lease/view.js +144 -0
- package/src/file-lease/watch.js +473 -0
- package/src/hashline-edit/index.js +76 -42
- package/src/hashline-edit/write-tool.js +90 -0
- package/src/intent-gate/index.js +29 -12
- package/src/intent-gate/matcher.js +36 -2
- package/src/lease-broker/entry.js +98 -0
- package/src/lease-broker/node-contracts.d.ts +63 -0
- package/src/lease-broker/protocol.js +237 -0
- package/src/lease-broker/server.js +402 -0
- package/src/lease-broker/state.js +482 -0
- package/src/lsp/tools.js +137 -27
- package/src/process/index.js +4 -2
- package/src/settings/index.js +2 -44
- package/src/settings/sections.js +2 -24
- package/src/shared/audit.js +9 -1
- package/src/shared/child-scope.js +2 -0
- package/src/todo-driver/index.js +1 -6
- package/src/worktree/cards.js +6 -21
- package/src/worktree/lane-core.js +1 -19
- package/src/worktree/lane-workflows.js +11 -32
- package/src/worktree/rules.js +1 -1
- package/lib/client.edit-lock-maintenance.js +0 -592
- package/lib/client.edit-lock-panel.js +0 -405
- package/src/edit-lock/adapter.js +0 -57
- package/src/edit-lock/admin-ledger.js +0 -149
- package/src/edit-lock/admin-recovery.js +0 -151
- package/src/edit-lock/admission.js +0 -82
- package/src/edit-lock/binding-attestation.js +0 -29
- package/src/edit-lock/binding-guidance.js +0 -72
- package/src/edit-lock/binding-machine.js +0 -312
- package/src/edit-lock/call-context.js +0 -65
- package/src/edit-lock/derived-facts.js +0 -67
- package/src/edit-lock/domain.js +0 -165
- package/src/edit-lock/domains.js +0 -170
- package/src/edit-lock/host.js +0 -149
- package/src/edit-lock/incarnations.js +0 -150
- package/src/edit-lock/index.js +0 -1303
- package/src/edit-lock/inspect.js +0 -219
- package/src/edit-lock/lifecycle.js +0 -579
- package/src/edit-lock/limits.js +0 -61
- package/src/edit-lock/maintenance.js +0 -310
- package/src/edit-lock/manager.js +0 -875
- package/src/edit-lock/negotiation.js +0 -132
- package/src/edit-lock/operation-history.js +0 -214
- package/src/edit-lock/peer-client.js +0 -86
- package/src/edit-lock/peer-transport.js +0 -76
- package/src/edit-lock/peer.js +0 -87
- package/src/edit-lock/publisher.js +0 -191
- package/src/edit-lock/read-authority.js +0 -101
- package/src/edit-lock/recovery.js +0 -139
- package/src/edit-lock/remote.js +0 -362
- package/src/edit-lock/request-binding.js +0 -42
- package/src/edit-lock/request-data.js +0 -40
- package/src/edit-lock/reservation.js +0 -301
- package/src/edit-lock/reserved-runtime.js +0 -45
- package/src/edit-lock/resource-identity.js +0 -130
- package/src/edit-lock/runtime.js +0 -145
- package/src/edit-lock/settle.js +0 -144
- package/src/edit-lock/snapshot.js +0 -175
- package/src/edit-lock/stale-sweep.js +0 -120
- package/src/edit-lock/state.js +0 -462
- package/src/edit-lock/store.js +0 -327
- package/src/edit-lock/tool-scope.js +0 -29
- package/src/edit-lock/view.js +0 -170
- package/src/edit-lock/write-scopes.js +0 -48
- package/src/edit-lock/write-tool.js +0 -45
- package/src/shared/delegate-edit-lifecycle.js +0 -36
- 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
|
+
}
|