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.
Files changed (89) hide show
  1. package/cordis.patch.yml +7 -18
  2. package/lib/client.file-lease-badge.js +307 -0
  3. package/lib/client.js +20 -30
  4. package/lib/client.settings-page.js +10 -34
  5. package/package.json +2 -2
  6. package/src/capabilities/store/checked-contract.d.ts +2 -2
  7. package/src/capabilities/store/fs-adapter.js +2 -3
  8. package/src/context-guard/index.js +2 -2
  9. package/src/delegate/supervision-mount.js +5 -5
  10. package/src/delegate/tool.js +2 -10
  11. package/src/file-lease/client.js +1066 -0
  12. package/src/file-lease/index.js +30 -0
  13. package/src/file-lease/lineage.js +207 -0
  14. package/src/file-lease/node-contracts.d.ts +42 -0
  15. package/src/file-lease/plugin.js +789 -0
  16. package/src/file-lease/singleton.js +155 -0
  17. package/src/file-lease/spawn.js +62 -0
  18. package/src/file-lease/tools.js +459 -0
  19. package/src/file-lease/view.js +144 -0
  20. package/src/hashline-edit/index.js +76 -42
  21. package/src/hashline-edit/write-tool.js +90 -0
  22. package/src/intent-gate/matcher.js +27 -0
  23. package/src/lease-broker/entry.js +98 -0
  24. package/src/lease-broker/node-contracts.d.ts +63 -0
  25. package/src/lease-broker/protocol.js +236 -0
  26. package/src/lease-broker/server.js +399 -0
  27. package/src/lease-broker/state.js +425 -0
  28. package/src/lsp/admin.js +24 -13
  29. package/src/lsp/index.js +5 -2
  30. package/src/lsp/registry.js +83 -17
  31. package/src/lsp/tools.js +137 -27
  32. package/src/notify/commands.js +15 -2
  33. package/src/notify/index.js +48 -10
  34. package/src/notify/web-channel.js +26 -3
  35. package/src/process/index.js +11 -0
  36. package/src/settings/index.js +2 -44
  37. package/src/settings/sections.js +29 -28
  38. package/src/shared/audit.js +6 -1
  39. package/src/todo-driver/index.js +1 -6
  40. package/src/worktree/cards.js +6 -21
  41. package/src/worktree/lane-core.js +1 -19
  42. package/src/worktree/lane-workflows.js +11 -32
  43. package/lib/client.edit-lock-maintenance.js +0 -592
  44. package/lib/client.edit-lock-panel.js +0 -405
  45. package/src/edit-lock/adapter.js +0 -57
  46. package/src/edit-lock/admin-ledger.js +0 -149
  47. package/src/edit-lock/admin-recovery.js +0 -151
  48. package/src/edit-lock/admission.js +0 -82
  49. package/src/edit-lock/binding-attestation.js +0 -29
  50. package/src/edit-lock/binding-guidance.js +0 -72
  51. package/src/edit-lock/binding-machine.js +0 -312
  52. package/src/edit-lock/call-context.js +0 -65
  53. package/src/edit-lock/derived-facts.js +0 -67
  54. package/src/edit-lock/domain.js +0 -165
  55. package/src/edit-lock/domains.js +0 -170
  56. package/src/edit-lock/host.js +0 -149
  57. package/src/edit-lock/incarnations.js +0 -150
  58. package/src/edit-lock/index.js +0 -1303
  59. package/src/edit-lock/inspect.js +0 -219
  60. package/src/edit-lock/lifecycle.js +0 -579
  61. package/src/edit-lock/limits.js +0 -61
  62. package/src/edit-lock/maintenance.js +0 -310
  63. package/src/edit-lock/manager.js +0 -875
  64. package/src/edit-lock/negotiation.js +0 -132
  65. package/src/edit-lock/operation-history.js +0 -214
  66. package/src/edit-lock/peer-client.js +0 -86
  67. package/src/edit-lock/peer-transport.js +0 -76
  68. package/src/edit-lock/peer.js +0 -87
  69. package/src/edit-lock/publisher.js +0 -191
  70. package/src/edit-lock/read-authority.js +0 -101
  71. package/src/edit-lock/recovery.js +0 -139
  72. package/src/edit-lock/remote.js +0 -362
  73. package/src/edit-lock/request-binding.js +0 -42
  74. package/src/edit-lock/request-data.js +0 -40
  75. package/src/edit-lock/reservation.js +0 -301
  76. package/src/edit-lock/reserved-runtime.js +0 -45
  77. package/src/edit-lock/resource-identity.js +0 -130
  78. package/src/edit-lock/runtime.js +0 -145
  79. package/src/edit-lock/settle.js +0 -144
  80. package/src/edit-lock/snapshot.js +0 -175
  81. package/src/edit-lock/stale-sweep.js +0 -120
  82. package/src/edit-lock/state.js +0 -462
  83. package/src/edit-lock/store.js +0 -327
  84. package/src/edit-lock/tool-scope.js +0 -29
  85. package/src/edit-lock/view.js +0 -170
  86. package/src/edit-lock/write-scopes.js +0 -48
  87. package/src/edit-lock/write-tool.js +0 -45
  88. package/src/shared/delegate-edit-lifecycle.js +0 -36
  89. 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
+ }