weir-harness 1.2.2 → 2.0.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.file-lease-badge.js +307 -0
- package/lib/client.js +20 -30
- package/lib/client.settings-page.js +10 -34
- package/package.json +2 -2
- 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 +1066 -0
- package/src/file-lease/index.js +30 -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 +789 -0
- package/src/file-lease/singleton.js +155 -0
- package/src/file-lease/spawn.js +62 -0
- package/src/file-lease/tools.js +459 -0
- package/src/file-lease/view.js +144 -0
- package/src/hashline-edit/index.js +76 -42
- package/src/hashline-edit/write-tool.js +90 -0
- package/src/intent-gate/matcher.js +27 -0
- package/src/lease-broker/entry.js +98 -0
- package/src/lease-broker/node-contracts.d.ts +63 -0
- package/src/lease-broker/protocol.js +236 -0
- package/src/lease-broker/server.js +399 -0
- package/src/lease-broker/state.js +425 -0
- package/src/lsp/admin.js +24 -13
- package/src/lsp/index.js +5 -2
- package/src/lsp/registry.js +83 -17
- package/src/lsp/tools.js +137 -27
- package/src/notify/commands.js +15 -2
- package/src/notify/index.js +48 -10
- package/src/notify/web-channel.js +26 -3
- package/src/process/index.js +11 -0
- package/src/settings/index.js +2 -44
- package/src/settings/sections.js +29 -28
- package/src/shared/audit.js +6 -1
- 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/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,30 @@
|
|
|
1
|
+
// Public surface of the file-lease client subtree (edit-lock-rebuild 3.1–3.5).
|
|
2
|
+
//
|
|
3
|
+
// Barrel only: importing this module must have no side effects — no socket, no
|
|
4
|
+
// spawn, no audit — because the adapters that use it (`hashline-edit`, `lsp`,
|
|
5
|
+
// the tool surface) are loaded for every session, including the ones that never
|
|
6
|
+
// touch a managed write. `plugin.js` exports a row (`name`/`inject`/`apply`) and
|
|
7
|
+
// a factory; neither connects anything until `apply` runs, and the process-wide
|
|
8
|
+
// client that `singleton.js` owns is created lazily on first use (D11).
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
createLeaseClient,
|
|
12
|
+
isLeaseConflict,
|
|
13
|
+
TRANSPORT_CODE,
|
|
14
|
+
DEGRADED_AUDIT_WINDOW_MS,
|
|
15
|
+
} from './client.js'
|
|
16
|
+
export { createLineageResolver } from './lineage.js'
|
|
17
|
+
export { spawnDaemon } from './spawn.js'
|
|
18
|
+
export {
|
|
19
|
+
createLeaseTools,
|
|
20
|
+
REPLY_TOOL,
|
|
21
|
+
MAX_HOLD_MINUTES,
|
|
22
|
+
DEFAULT_HOLD_MINUTES,
|
|
23
|
+
} from './tools.js'
|
|
24
|
+
export { createFileLeasePlugin, name, inject, apply } from './plugin.js'
|
|
25
|
+
// The read-only ownership projection behind the status surface (D13). Pure, so a
|
|
26
|
+
// caller can shape a snapshot without a client, a clock or a socket.
|
|
27
|
+
export { buildLeaseView, VIEW_SCHEMA_VERSION, ARBITER_STATES } from './view.js'
|
|
28
|
+
// D11 wiring: the process-wide client and the owner/scope rule the managed
|
|
29
|
+
// editors share.
|
|
30
|
+
export { getSharedLeaseClient, createEditorLease, defaultLeaseEndpoint } from './singleton.js'
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Lineage resolution: which delegation chain a session belongs to, and the
|
|
2
|
+
// short label that chain shows to the model.
|
|
3
|
+
//
|
|
4
|
+
// The broker never interprets identity (design D8): `owner.id` is an opaque key.
|
|
5
|
+
// This module is the client-side rule that produces it (design D9): a root
|
|
6
|
+
// session is its own lineage, and a delegated child inherits its initiator's
|
|
7
|
+
// lineage, so a parent holding a file for the turn never refuses its own
|
|
8
|
+
// subagent. Two different chains still conflict — that is the whole point of
|
|
9
|
+
// the column being inherited rather than shared globally.
|
|
10
|
+
//
|
|
11
|
+
// Pure by construction: the only outside access is `sessions.get(id)?.header`,
|
|
12
|
+
// read behind a `try`. Every step that cannot be answered (missing header, a
|
|
13
|
+
// cycle, a hop cap, a throwing registry) degrades to the deepest ancestor we can
|
|
14
|
+
// name — never to a foreign conversation and never to an exception.
|
|
15
|
+
|
|
16
|
+
/** Hard stop for the ancestor walk; a chain this deep is a bug, not a tree. */
|
|
17
|
+
const MAX_HOPS = 16
|
|
18
|
+
|
|
19
|
+
/** Longest label handed to the model or the status panel. */
|
|
20
|
+
const MAX_LABEL_LENGTH = 60
|
|
21
|
+
|
|
22
|
+
/** Last-resort label: better a generic noun than an empty string. */
|
|
23
|
+
const FALLBACK_LABEL = 'session'
|
|
24
|
+
|
|
25
|
+
/** An absolute POSIX path, a win32 drive path, or a UNC path, at a word start. */
|
|
26
|
+
const ABSOLUTE_PATH_RUN = /(^|\s)((?:[A-Za-z]:[\\/]|\\\\|\/)[^\s]*)/g
|
|
27
|
+
|
|
28
|
+
/** @returns {string | undefined} the value when it is a non-empty string */
|
|
29
|
+
function text(value) {
|
|
30
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Same rule for display strings: a blank title is not a title, so it must not
|
|
35
|
+
* shadow the next candidate (a whitespace-only label would collapse to the
|
|
36
|
+
* generic fallback instead of naming the agent).
|
|
37
|
+
* @returns {string | undefined}
|
|
38
|
+
*/
|
|
39
|
+
function visible(value) {
|
|
40
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The final segment of a path-like run (`/a/b/c.js` → `c.js`). Used only to keep
|
|
45
|
+
* an absolute path out of a label; the result is never used as an identifier.
|
|
46
|
+
* @param {string} run
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function lastSegment(run) {
|
|
50
|
+
const segments = run.split(/[\\/]/)
|
|
51
|
+
for (let index = segments.length - 1; index >= 0; index -= 1) {
|
|
52
|
+
const segment = segments[index]
|
|
53
|
+
if (segment.length > 0) return segment
|
|
54
|
+
}
|
|
55
|
+
return ''
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Turn one raw string into a label that is safe to show: one line, no absolute
|
|
60
|
+
* paths, at most `MAX_LABEL_LENGTH` characters, never empty.
|
|
61
|
+
* @param {string | undefined} raw
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
function shorten(raw) {
|
|
65
|
+
const source = text(raw)
|
|
66
|
+
if (source === undefined) return FALLBACK_LABEL
|
|
67
|
+
const oneLine = source.split('\n')[0].replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim()
|
|
68
|
+
if (oneLine.length === 0) return FALLBACK_LABEL
|
|
69
|
+
// A session title may embed the file or directory it was opened on. The label
|
|
70
|
+
// is a name, not a locator, so every absolute run collapses to its basename.
|
|
71
|
+
const stripped = oneLine.replace(ABSOLUTE_PATH_RUN, (_match, lead, run) => `${lead}${lastSegment(run)}`).trim()
|
|
72
|
+
const label = stripped.length > 0 ? stripped : FALLBACK_LABEL
|
|
73
|
+
if (label.length <= MAX_LABEL_LENGTH) return label
|
|
74
|
+
return `${label.slice(0, MAX_LABEL_LENGTH - 1)}…`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @typedef {object} LineageResolverOptions
|
|
79
|
+
* @property {{ get?: (id: string) => any } | undefined} [sessions] live sessions registry (`sessions.get(id)?.header`)
|
|
80
|
+
* @property {Map<string, string>} [cache] session id → resolved lineage id; injectable so a caller can pre-warm or inspect it
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @typedef {object} LineageResolver
|
|
85
|
+
* @property {(agent: any) => string} lineageOf the opaque `owner.id` for this agent's chain
|
|
86
|
+
* @property {(agent: any) => string} labelOf the short, path-free label for this agent
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create one lineage resolver.
|
|
91
|
+
* @param {LineageResolverOptions} [options]
|
|
92
|
+
* @returns {LineageResolver}
|
|
93
|
+
*/
|
|
94
|
+
export function createLineageResolver({ sessions, cache = new Map() } = {}) {
|
|
95
|
+
/**
|
|
96
|
+
* Read one session header. The registry is outside our control (it may be a
|
|
97
|
+
* proxy, a settled handle, or absent entirely), so every failure reads as
|
|
98
|
+
* "no header" instead of propagating into a turn.
|
|
99
|
+
* @param {string} id
|
|
100
|
+
* @returns {any}
|
|
101
|
+
*/
|
|
102
|
+
function headerOf(id) {
|
|
103
|
+
if (typeof id !== 'string' || id.length === 0) return undefined
|
|
104
|
+
try {
|
|
105
|
+
const session = sessions?.get?.(id)
|
|
106
|
+
const header = session?.header
|
|
107
|
+
return header !== null && typeof header === 'object' ? header : undefined
|
|
108
|
+
} catch {
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The id this session is known by: the durable header's id, else the session's
|
|
115
|
+
* own id, else the agent's id. Non-empty by construction — `owner.id` may not
|
|
116
|
+
* be an empty string on the wire.
|
|
117
|
+
* @param {any} agent
|
|
118
|
+
* @param {any} header
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
function ownIdOf(agent, header) {
|
|
122
|
+
return (
|
|
123
|
+
text(header?.id) ??
|
|
124
|
+
text(agent?.session?.id) ??
|
|
125
|
+
text(agent?.id) ??
|
|
126
|
+
FALLBACK_LABEL
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve an agent to its lineage id.
|
|
132
|
+
* @param {any} agent
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
function lineageOf(agent) {
|
|
136
|
+
try {
|
|
137
|
+
const header = agent?.session?.header
|
|
138
|
+
const own = ownIdOf(agent, header)
|
|
139
|
+
// A root session is its own lineage — including a resume/fork incarnation
|
|
140
|
+
// that still carries a `parentSession` header at depth 0.
|
|
141
|
+
if ((header?.delegationDepth ?? 0) === 0) return own
|
|
142
|
+
const cached = cache.get(own)
|
|
143
|
+
if (typeof cached === 'string' && cached.length > 0) return cached
|
|
144
|
+
const parentId = text(header?.parentSession)
|
|
145
|
+
// A child with no parent header cannot be placed in a chain; being its own
|
|
146
|
+
// lineage keeps it conflict-free against nothing at all, which is the
|
|
147
|
+
// least surprising answer.
|
|
148
|
+
if (parentId === undefined || parentId === own) return own
|
|
149
|
+
let currentId = parentId
|
|
150
|
+
/** @type {string} */
|
|
151
|
+
let resolved = parentId
|
|
152
|
+
let complete = false
|
|
153
|
+
const seen = new Set([own, parentId])
|
|
154
|
+
for (let hop = 0; hop < MAX_HOPS; hop += 1) {
|
|
155
|
+
const ancestor = headerOf(currentId)
|
|
156
|
+
if (ancestor === undefined) break
|
|
157
|
+
if ((ancestor.delegationDepth ?? 0) === 0) {
|
|
158
|
+
resolved = text(ancestor.id) ?? currentId
|
|
159
|
+
complete = true
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
const nextId = text(ancestor.parentSession)
|
|
163
|
+
if (nextId === undefined || seen.has(nextId)) break
|
|
164
|
+
seen.add(nextId)
|
|
165
|
+
resolved = nextId
|
|
166
|
+
currentId = nextId
|
|
167
|
+
}
|
|
168
|
+
// Only complete walks are remembered. A degraded answer (a parent that is
|
|
169
|
+
// not loaded yet) stays uncached so a later lookup can still find the root
|
|
170
|
+
// — a stale lineage id would silently merge two different chains.
|
|
171
|
+
if (complete) cache.set(own, resolved)
|
|
172
|
+
return resolved
|
|
173
|
+
} catch {
|
|
174
|
+
// The ugly-but-total fallback: the direct parent id, which still shares
|
|
175
|
+
// one lineage across the whole subtree.
|
|
176
|
+
return (
|
|
177
|
+
text(agent?.session?.header?.parentSession) ??
|
|
178
|
+
text(agent?.session?.id) ??
|
|
179
|
+
text(agent?.id) ??
|
|
180
|
+
FALLBACK_LABEL
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The short label for an agent: its session title, else its display name,
|
|
187
|
+
* else `'session'`. Never a path, never longer than 60 characters.
|
|
188
|
+
* @param {any} agent
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
function labelOf(agent) {
|
|
192
|
+
try {
|
|
193
|
+
const header = agent?.session?.header
|
|
194
|
+
const raw =
|
|
195
|
+
visible(header?.title) ??
|
|
196
|
+
visible(agent?.displayName) ??
|
|
197
|
+
visible(agent?.name) ??
|
|
198
|
+
visible(agent?.label) ??
|
|
199
|
+
FALLBACK_LABEL
|
|
200
|
+
return shorten(raw)
|
|
201
|
+
} catch {
|
|
202
|
+
return FALLBACK_LABEL
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { lineageOf, labelOf }
|
|
207
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Node contracts consumed by the lease CLIENT subtree, declared next to the code
|
|
2
|
+
// that uses them.
|
|
3
|
+
//
|
|
4
|
+
// `checkJs` runs with `types: []` (no @types/node), so the Node surface this
|
|
5
|
+
// package compiles against is declared by hand. types/platform.d.ts covers what
|
|
6
|
+
// the rest of the bundle consumes and src/lease-broker/node-contracts.d.ts
|
|
7
|
+
// covers the daemon; the client is the only consumer of `node:url`, of
|
|
8
|
+
// `spawn`, and of the `execPath` named export, so those stay here. Ambient
|
|
9
|
+
// declarations merge, which is why this file extends `node:child_process` and
|
|
10
|
+
// `node:process` instead of redeclaring them.
|
|
11
|
+
|
|
12
|
+
declare module 'node:url' {
|
|
13
|
+
export function fileURLToPath(url: string | URL): string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module 'node:child_process' {
|
|
17
|
+
export interface ChildProcess {
|
|
18
|
+
readonly pid?: number;
|
|
19
|
+
readonly exitCode: number | null;
|
|
20
|
+
readonly signalCode: string | null;
|
|
21
|
+
unref(): void;
|
|
22
|
+
kill(signal?: string): boolean;
|
|
23
|
+
on(event: 'exit', listener: (code: number | null, signal: string | null) => void): ChildProcess;
|
|
24
|
+
on(event: 'error', listener: (error: Error) => void): ChildProcess;
|
|
25
|
+
}
|
|
26
|
+
export function spawn(
|
|
27
|
+
file: string,
|
|
28
|
+
args: string[],
|
|
29
|
+
options: {
|
|
30
|
+
env: Record<string, string | undefined>;
|
|
31
|
+
detached: boolean;
|
|
32
|
+
stdio: string;
|
|
33
|
+
windowsHide: boolean;
|
|
34
|
+
},
|
|
35
|
+
): ChildProcess;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// `node:process` really does export `execPath` as a named export; the shared
|
|
39
|
+
// declaration only models the default export, which has no `execPath` field.
|
|
40
|
+
declare module 'node:process' {
|
|
41
|
+
export const execPath: string;
|
|
42
|
+
}
|