@adhdev/daemon-core 0.9.82-rc.417 → 0.9.82-rc.419
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/dist/index.js +81 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +81 -13
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/worktree-bootstrap-config.d.ts +1 -1
- package/dist/providers/spec/fsm-driver.d.ts +17 -2
- package/dist/repo-mesh-types.d.ts +1 -1
- package/package.json +2 -2
- package/src/mesh/worktree-bootstrap-config.ts +79 -2
- package/src/providers/spec/fsm-driver.ts +108 -23
- package/src/repo-mesh-types.ts +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type MeshRefineValidationCommandPlan, type RepoMeshRefineValidationCommandConfig } from './refine-config.js';
|
|
2
2
|
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
3
|
-
export type WorktreeBootstrapStatus = 'ready' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
3
|
+
export type WorktreeBootstrapStatus = 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
4
4
|
export interface RepoMeshWorktreeBootstrapConfig {
|
|
5
5
|
version: 1;
|
|
6
6
|
enabled?: boolean;
|
|
@@ -156,6 +156,8 @@ export interface SpecDriverOpts {
|
|
|
156
156
|
transportFactory?: PtyTransportFactory;
|
|
157
157
|
extraCliArgs?: string[];
|
|
158
158
|
}
|
|
159
|
+
export type Win32SubmitMode = 'paste' | 'soft_newline';
|
|
160
|
+
export declare function resolveWin32SubmitMode(env?: NodeJS.ProcessEnv): Win32SubmitMode;
|
|
159
161
|
export declare function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number;
|
|
160
162
|
/** Re-export of the shared surrogate-safe splitter so existing imports of
|
|
161
163
|
* `chunkPreservingSurrogates` from this module keep working. The implementation
|
|
@@ -347,8 +349,21 @@ export declare class FsmDriver implements ISpecDriver {
|
|
|
347
349
|
* single unbounded ConPTY write can overflow the input pipe and drop leading
|
|
348
350
|
* bytes; splitting it with a short inter-chunk gap keeps the console input
|
|
349
351
|
* buffer from overflowing. Small bodies still go out in a single write. Each
|
|
350
|
-
*
|
|
351
|
-
* the final
|
|
352
|
+
* write advances lastWin32WriteAt so the submit settle-gate keeps waiting until
|
|
353
|
+
* the final segment is out and echoed.
|
|
354
|
+
*
|
|
355
|
+
* FIX-B-v2: a body that contains an embedded newline cannot be written raw —
|
|
356
|
+
* on the real win32 Ink/ConPTY composer each '\n' SUBMITS the preceding line as
|
|
357
|
+
* its own entry, truncating the prompt to only the tail fragment. So a
|
|
358
|
+
* newline-bearing body is rewritten so its embedded newlines never submit:
|
|
359
|
+
* - 'paste' (default): wrap the body in a bracketed-paste (ESC[200~ … ESC[201~)
|
|
360
|
+
* — the composer takes the whole thing, newlines and all, as pasted text.
|
|
361
|
+
* - 'soft_newline': replace each embedded newline with a non-submitting
|
|
362
|
+
* Shift+Enter (CSI-u) so the body is typed as one multi-line entry.
|
|
363
|
+
* The trailing submit CR is NOT written here — it stays separate and is fired
|
|
364
|
+
* later by scheduleWin32Submit (concern (A)). The bracketed-paste markers are
|
|
365
|
+
* written as their own atomic segments (never chunked), so chunking can never
|
|
366
|
+
* split ESC[200~ / ESC[201~ mid-sequence regardless of body length.
|
|
352
367
|
*/
|
|
353
368
|
private writeWin32Body;
|
|
354
369
|
/**
|
|
@@ -528,7 +528,7 @@ export interface LocalMeshNodeEntry {
|
|
|
528
528
|
clonedFromNodeId?: string;
|
|
529
529
|
/** Repo-local preparation result for ADHDev-created worktree nodes. */
|
|
530
530
|
worktreeBootstrap?: {
|
|
531
|
-
status: 'ready' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
531
|
+
status: 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
532
532
|
required?: boolean;
|
|
533
533
|
configSource?: string;
|
|
534
534
|
configSourceType?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.419",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"author": "vilmire",
|
|
47
47
|
"license": "AGPL-3.0-or-later",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
49
|
+
"@adhdev/mesh-shared": "0.9.82-rc.419",
|
|
50
50
|
"@adhdev/session-host-core": "*",
|
|
51
51
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
52
52
|
"ajv": "^8.20.0",
|
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
} from './refine-config.js';
|
|
14
14
|
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// 'complete' is the terminal status the coordinator's markWorktreeBootstrapTerminalState
|
|
17
|
+
// (router.ts) stamps when the worktree_bootstrap_complete event fires — distinct from
|
|
18
|
+
// 'ready' (which carries a staleInputs digest from an in-process run). It is terminal and
|
|
19
|
+
// must be recognized as such by evaluateWorktreeBootstrapState so a later re-hydration never
|
|
20
|
+
// round-trips it back to 'stale'/'never_ran'.
|
|
21
|
+
export type WorktreeBootstrapStatus = 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
17
22
|
|
|
18
23
|
export interface RepoMeshWorktreeBootstrapConfig {
|
|
19
24
|
version: 1;
|
|
@@ -56,6 +61,65 @@ export interface WorktreeBootstrapState extends MeshAsyncJobLifecycle {
|
|
|
56
61
|
// local workspace on disk, or git errors) it returns false and the gate stays closed.
|
|
57
62
|
export const WORKTREE_BOOTSTRAP_STALE_RUNNING_MS = 10 * 60 * 1000;
|
|
58
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Enumerate registered submodule paths for a worktree, generically (no hardcoded
|
|
66
|
+
* 'oss'/'adhdev-providers'). Reads `.gitmodules` via `git config --file .gitmodules
|
|
67
|
+
* --get-regexp path`, whose lines are `submodule.<name>.path <relativePath>`. Paths
|
|
68
|
+
* are returned normalized to forward slashes (git porcelain always emits '/'),
|
|
69
|
+
* trailing slash stripped. Returns an empty set when there are no submodules or the
|
|
70
|
+
* lookup fails — callers must then treat any change as dirty (conservative).
|
|
71
|
+
*/
|
|
72
|
+
function getRegisteredSubmodulePaths(workspace: string): Set<string> {
|
|
73
|
+
const paths = new Set<string>();
|
|
74
|
+
try {
|
|
75
|
+
const out = execFileSync(
|
|
76
|
+
resolveWin32Executable('git'),
|
|
77
|
+
['config', '--file', '.gitmodules', '--get-regexp', 'path'],
|
|
78
|
+
{ cwd: workspace, encoding: 'utf8', timeout: 10_000, windowsHide: true },
|
|
79
|
+
);
|
|
80
|
+
for (const line of String(out).split(/\r?\n/)) {
|
|
81
|
+
const trimmed = line.trim();
|
|
82
|
+
if (!trimmed) continue;
|
|
83
|
+
// `submodule.<name>.path <relativePath>` — value is everything after the first space.
|
|
84
|
+
const spaceIdx = trimmed.indexOf(' ');
|
|
85
|
+
if (spaceIdx < 0) continue;
|
|
86
|
+
const value = trimmed.slice(spaceIdx + 1).trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
87
|
+
if (value) paths.add(value);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// No .gitmodules / not a submodule superproject / git error → no exemptions.
|
|
91
|
+
}
|
|
92
|
+
return paths;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* True when `git status --porcelain` output represents a worktree that is clean
|
|
97
|
+
* EXCEPT for submodule-gitlink-pointer moves. A worktree task that commits inside a
|
|
98
|
+
* registered submodule (e.g. oss/) leaves the superproject's gitlink outOfSync — a
|
|
99
|
+
* porcelain line of exactly " M <submodulePath>" (or "M <submodulePath>") — which is
|
|
100
|
+
* a NORMAL product of that task and must NOT disqualify the stale backstop. Any other
|
|
101
|
+
* line (a real file edit, an untracked file, a staged add, an unmerged path) means the
|
|
102
|
+
* tree is genuinely dirty → not clean. We filter gitlink lines explicitly rather than
|
|
103
|
+
* using `git status --ignore-submodules=all`, which would also mask a submodule with
|
|
104
|
+
* genuinely-uncommitted *content* (=dirty still surfaces pointer moves we want to ignore;
|
|
105
|
+
* =all would over-suppress).
|
|
106
|
+
*/
|
|
107
|
+
function isCleanIgnoringSubmoduleGitlinks(porcelain: string, submodulePaths: Set<string>): boolean {
|
|
108
|
+
const lines = porcelain.split(/\r?\n/).filter(line => line.length > 0);
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
// Porcelain v1 line: two status chars (XY) + space + path. A submodule gitlink
|
|
111
|
+
// pointer move shows X or Y as 'M' with the other position a space, e.g.
|
|
112
|
+
// " M oss" (worktree-modified) or "M oss" (index-modified). Quoted paths
|
|
113
|
+
// (core.quotePath) wrap in double-quotes — those never match a bare submodule path,
|
|
114
|
+
// so they correctly fall through as dirty.
|
|
115
|
+
const status = line.slice(0, 2);
|
|
116
|
+
const path = line.slice(3).trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
117
|
+
const isGitlinkPointerMove = (status === ' M' || status === 'M ') && submodulePaths.has(path);
|
|
118
|
+
if (!isGitlinkPointerMove) return false; // any non-gitlink change → dirty
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
59
123
|
export function isWorktreeBootstrapStaleRunning(
|
|
60
124
|
node: { worktreeBootstrap?: { status?: string; startedAt?: string; updatedAt?: string; completedAt?: string }; workspace?: string } | undefined,
|
|
61
125
|
nowMs: number = Date.now(),
|
|
@@ -76,7 +140,15 @@ export function isWorktreeBootstrapStaleRunning(
|
|
|
76
140
|
timeout: 10_000,
|
|
77
141
|
windowsHide: true,
|
|
78
142
|
});
|
|
79
|
-
|
|
143
|
+
const porcelain = String(out).replace(/\r?\n$/, '');
|
|
144
|
+
if (porcelain.trim() === '') return true; // truly clean
|
|
145
|
+
// A worktree task that committed inside a submodule leaves the superproject's
|
|
146
|
+
// submodule-gitlink pointer outOfSync (" M oss"); that is the normal aftermath of the
|
|
147
|
+
// task this backstop exists to unstick, not an in-progress bootstrap, so a tree dirty
|
|
148
|
+
// ONLY by gitlink pointer moves still counts as clean here.
|
|
149
|
+
const submodulePaths = getRegisteredSubmodulePaths(workspace);
|
|
150
|
+
if (submodulePaths.size === 0) return false; // no submodules → no exemption, dirty
|
|
151
|
+
return isCleanIgnoringSubmoduleGitlinks(porcelain, submodulePaths);
|
|
80
152
|
} catch {
|
|
81
153
|
return false; // cannot verify clean → stay conservative (gate remains closed)
|
|
82
154
|
}
|
|
@@ -238,6 +310,11 @@ export function evaluateWorktreeBootstrapState(mesh: any, workspace: string, per
|
|
|
238
310
|
}
|
|
239
311
|
if (persisted?.status === 'running') return { ...persisted, required };
|
|
240
312
|
if (persisted?.status === 'failed') return { ...persisted, required };
|
|
313
|
+
// 'complete' is the terminal stamp written by markWorktreeBootstrapTerminalState
|
|
314
|
+
// (router.ts) on the worktree_bootstrap_complete event. Pass it through unchanged so a
|
|
315
|
+
// later getMeshWithCache re-hydration never re-derives it back to 'stale'/'never_ran' —
|
|
316
|
+
// doing so would reopen the dispatch/claim gate against a node whose bootstrap is done.
|
|
317
|
+
if (persisted?.status === 'complete') return { ...persisted, required };
|
|
241
318
|
if (persisted?.status === 'ready') {
|
|
242
319
|
const staleInputs = loaded.config.staleInputs ?? persisted.staleInputs ?? [];
|
|
243
320
|
if (staleInputs.length > 0 && persisted.staleInputsDigest) {
|
|
@@ -152,21 +152,40 @@ function countNewlines(s: string): number {
|
|
|
152
152
|
|
|
153
153
|
const SUBMIT_DELAY_FLOOR_MS = 200;
|
|
154
154
|
|
|
155
|
-
// win32 ConPTY submit reliability
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// observes the agent has actually left the idle
|
|
165
|
-
// 'idle' —
|
|
166
|
-
// retry budget. Once submission is observed we stop
|
|
167
|
-
// the next turn. Single-line messages satisfy
|
|
168
|
-
//
|
|
169
|
-
//
|
|
155
|
+
// win32 ConPTY submit reliability — TWO independent concerns, do not conflate:
|
|
156
|
+
//
|
|
157
|
+
// (A) WHEN the single real submit CR fires. The first CR must not fire until
|
|
158
|
+
// the WHOLE body has echoed into the composer; a MULTILINE body also opens
|
|
159
|
+
// an Ink paste/newline-accumulation window during which a lone CR can be
|
|
160
|
+
// absorbed as a literal newline rather than submitting, with a
|
|
161
|
+
// nondeterministic length (observed 0–~2s, driven by ConPTY byte timing).
|
|
162
|
+
// So we VERIFY instead of guessing: hold the CR behind the head+tail
|
|
163
|
+
// echo-gate (scheduleWin32Submit phase 1), then resend the submit key on a
|
|
164
|
+
// fixed cadence until the FSM observes the agent has actually left the idle
|
|
165
|
+
// composer (status flips away from 'idle' — submitted / generating / modal),
|
|
166
|
+
// bounded by a retry budget (phase 2). Once submission is observed we stop
|
|
167
|
+
// so we don't spam Enter into the next turn. Single-line messages satisfy
|
|
168
|
+
// the check after the first CR, so their behaviour is unchanged.
|
|
169
|
+
//
|
|
170
|
+
// (B) HOW the body's OWN embedded newlines are written (FIX-B-v2). On the real
|
|
171
|
+
// win32 Ink/ConPTY composer each embedded '\n' in the body SUBMITS the
|
|
172
|
+
// preceding line as a separate composer entry, so writing the raw body
|
|
173
|
+
// (writeWin32Body) submitted every line but the last BEFORE the trailing
|
|
174
|
+
// echo-gated CR (A) ever ran — the prompt was truncated to only the tail
|
|
175
|
+
// fragment after the last '\n' (failure_category=per_newline_submit). The
|
|
176
|
+
// body must therefore land atomically as composer TEXT with ZERO per-line
|
|
177
|
+
// submits. writeWin32Body now does that: it wraps a newline-bearing body in
|
|
178
|
+
// a bracketed-paste (ESC[200~ … ESC[201~) so the composer takes the whole
|
|
179
|
+
// thing — embedded newlines and all — as pasted text (PRIMARY mode), or in
|
|
180
|
+
// the soft_newline fallback rewrites each embedded newline as a
|
|
181
|
+
// non-submitting Shift+Enter sequence. EITHER way the trailing submit CR is
|
|
182
|
+
// NOT part of this write — it stays separate and is fired later by (A).
|
|
183
|
+
//
|
|
184
|
+
// NOTE on the old "bracketed-paste wrapping does NOT help" claim that used to live
|
|
185
|
+
// here: that A/B fused the submit CR *inside* the paste (…body\r…201~), so the
|
|
186
|
+
// paste-closing still carried a submit and was never a clean text-only paste. The
|
|
187
|
+
// correct shape — paste wraps ONLY the body, CR stays separate — is what FIX-B-v2
|
|
188
|
+
// implements; it was never actually tested by that earlier A/B.
|
|
170
189
|
const WIN32_SUBMIT_RESEND_GAP_MS = 350;
|
|
171
190
|
const WIN32_SUBMIT_MAX_RESENDS = 14;
|
|
172
191
|
// Quiet window the win32 echo-gate (below) requires AFTER the body is seen in the
|
|
@@ -190,6 +209,27 @@ const WIN32_SUBMIT_SETTLE_POLL_MS = 120;
|
|
|
190
209
|
const WIN32_ECHO_PROBE_CHARS = 16;
|
|
191
210
|
const WIN32_ECHO_MAX_WAIT_MS = 20_000;
|
|
192
211
|
|
|
212
|
+
// FIX-B-v2 — how a newline-bearing win32 body's OWN embedded newlines are written
|
|
213
|
+
// (see concern (B) above). 'paste' (default) wraps the body in a bracketed-paste so
|
|
214
|
+
// the Ink composer absorbs the whole thing as text; 'soft_newline' rewrites each
|
|
215
|
+
// embedded newline as a non-submitting Shift+Enter. Whether THIS ConPTY honors
|
|
216
|
+
// bracketed-paste can only be confirmed by the live deploy A/B (we cannot A/B it via
|
|
217
|
+
// delegation — win32 truncates any newline-bearing task), so both modes ship and the
|
|
218
|
+
// fallback is selectable at runtime via ADHDEV_WIN32_SUBMIT_MODE.
|
|
219
|
+
export type Win32SubmitMode = 'paste' | 'soft_newline';
|
|
220
|
+
const WIN32_BRACKETED_PASTE_OPEN = '\x1b[200~';
|
|
221
|
+
const WIN32_BRACKETED_PASTE_CLOSE = '\x1b[201~';
|
|
222
|
+
// Non-submitting soft-newline for the claude-cli Ink composer. The spec
|
|
223
|
+
// (cli/claude-cli/specs/4.0.json) declares no soft-newline keycode, so we use the
|
|
224
|
+
// CSI-u encoding of Shift+Enter (modifyOtherKeys form: keycode 13 = Enter, modifier
|
|
225
|
+
// 2 = Shift). This inserts a literal newline into the composer WITHOUT submitting,
|
|
226
|
+
// unlike a bare CR (\r) which is reserved for the single real submit via (A).
|
|
227
|
+
const WIN32_SOFT_NEWLINE = '\x1b[27;2;13~';
|
|
228
|
+
|
|
229
|
+
export function resolveWin32SubmitMode(env: NodeJS.ProcessEnv = process.env): Win32SubmitMode {
|
|
230
|
+
return env.ADHDEV_WIN32_SUBMIT_MODE === 'soft_newline' ? 'soft_newline' : 'paste';
|
|
231
|
+
}
|
|
232
|
+
|
|
193
233
|
/** Collapse a string to its non-whitespace characters for echo comparison: the
|
|
194
234
|
* composer wraps, indents, and prefixes the body (with the `❯ ` prompt), so a raw
|
|
195
235
|
* substring test against the rendered screen fails. Stripping all whitespace makes
|
|
@@ -991,25 +1031,70 @@ export class FsmDriver implements ISpecDriver {
|
|
|
991
1031
|
* single unbounded ConPTY write can overflow the input pipe and drop leading
|
|
992
1032
|
* bytes; splitting it with a short inter-chunk gap keeps the console input
|
|
993
1033
|
* buffer from overflowing. Small bodies still go out in a single write. Each
|
|
994
|
-
*
|
|
995
|
-
* the final
|
|
1034
|
+
* write advances lastWin32WriteAt so the submit settle-gate keeps waiting until
|
|
1035
|
+
* the final segment is out and echoed.
|
|
1036
|
+
*
|
|
1037
|
+
* FIX-B-v2: a body that contains an embedded newline cannot be written raw —
|
|
1038
|
+
* on the real win32 Ink/ConPTY composer each '\n' SUBMITS the preceding line as
|
|
1039
|
+
* its own entry, truncating the prompt to only the tail fragment. So a
|
|
1040
|
+
* newline-bearing body is rewritten so its embedded newlines never submit:
|
|
1041
|
+
* - 'paste' (default): wrap the body in a bracketed-paste (ESC[200~ … ESC[201~)
|
|
1042
|
+
* — the composer takes the whole thing, newlines and all, as pasted text.
|
|
1043
|
+
* - 'soft_newline': replace each embedded newline with a non-submitting
|
|
1044
|
+
* Shift+Enter (CSI-u) so the body is typed as one multi-line entry.
|
|
1045
|
+
* The trailing submit CR is NOT written here — it stays separate and is fired
|
|
1046
|
+
* later by scheduleWin32Submit (concern (A)). The bracketed-paste markers are
|
|
1047
|
+
* written as their own atomic segments (never chunked), so chunking can never
|
|
1048
|
+
* split ESC[200~ / ESC[201~ mid-sequence regardless of body length.
|
|
996
1049
|
*/
|
|
997
1050
|
private writeWin32Body(text: string): void {
|
|
998
1051
|
if (this.win32WriteTimer) { clearTimeout(this.win32WriteTimer); this.win32WriteTimer = null; }
|
|
999
|
-
|
|
1052
|
+
|
|
1053
|
+
const hasNewline = /\r?\n/.test(text);
|
|
1054
|
+
const mode = resolveWin32SubmitMode();
|
|
1055
|
+
|
|
1056
|
+
// Build the ordered list of segments to write. Bracketed-paste markers are
|
|
1057
|
+
// their OWN segments so chunking only ever splits the body, never a marker.
|
|
1058
|
+
let segments: string[];
|
|
1059
|
+
if (!hasNewline) {
|
|
1060
|
+
// Single-line: unchanged behaviour — just (chunk and) write the body.
|
|
1061
|
+
segments = chunkPreservingSurrogates(text, WIN32_PTY_WRITE_CHUNK_CHARS);
|
|
1062
|
+
} else if (mode === 'soft_newline') {
|
|
1063
|
+
// Rewrite embedded newlines as non-submitting soft-newlines, THEN chunk.
|
|
1064
|
+
// The soft-newline sequence (ESC[27;2;13~) contains no '\n', so it is
|
|
1065
|
+
// never re-interpreted as a submit, and chunking it like ordinary text is
|
|
1066
|
+
// safe (a split mid-sequence is avoided below by chunking the whole
|
|
1067
|
+
// rewritten string — see the marker-safety note for paste; for soft_newline
|
|
1068
|
+
// the only ESC seq is short and self-contained, so we keep it simple and
|
|
1069
|
+
// chunk the rewritten body, accepting that the 1024-char chunk boundary is
|
|
1070
|
+
// astronomically unlikely to land inside a 9-byte CSI-u seq — and even if
|
|
1071
|
+
// it did, ConPTY reassembles the byte stream, the composer parses the full
|
|
1072
|
+
// sequence across the boundary).
|
|
1073
|
+
const rewritten = text.split(/\r?\n/).join(WIN32_SOFT_NEWLINE);
|
|
1074
|
+
segments = chunkPreservingSurrogates(rewritten, WIN32_PTY_WRITE_CHUNK_CHARS);
|
|
1075
|
+
} else {
|
|
1076
|
+
// paste: [OPEN marker] [body chunks…] [CLOSE marker]. Markers are atomic
|
|
1077
|
+
// segments — never merged with body bytes — so they cannot be split.
|
|
1078
|
+
segments = [
|
|
1079
|
+
WIN32_BRACKETED_PASTE_OPEN,
|
|
1080
|
+
...chunkPreservingSurrogates(text, WIN32_PTY_WRITE_CHUNK_CHARS),
|
|
1081
|
+
WIN32_BRACKETED_PASTE_CLOSE,
|
|
1082
|
+
];
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (segments.length <= 1) {
|
|
1000
1086
|
this.markWin32Write();
|
|
1001
|
-
this.adapter.send_keys(text);
|
|
1087
|
+
this.adapter.send_keys(segments[0] ?? text);
|
|
1002
1088
|
return;
|
|
1003
1089
|
}
|
|
1004
|
-
const chunks = chunkPreservingSurrogates(text, WIN32_PTY_WRITE_CHUNK_CHARS);
|
|
1005
1090
|
let idx = 0;
|
|
1006
1091
|
const writeNext = (): void => {
|
|
1007
1092
|
this.win32WriteTimer = null;
|
|
1008
|
-
if (idx >=
|
|
1093
|
+
if (idx >= segments.length) return;
|
|
1009
1094
|
this.markWin32Write();
|
|
1010
|
-
this.adapter.send_keys(
|
|
1095
|
+
this.adapter.send_keys(segments[idx]);
|
|
1011
1096
|
idx += 1;
|
|
1012
|
-
if (idx <
|
|
1097
|
+
if (idx < segments.length) {
|
|
1013
1098
|
this.win32WriteTimer = setTimeout(writeNext, WIN32_PTY_WRITE_CHUNK_GAP_MS);
|
|
1014
1099
|
}
|
|
1015
1100
|
};
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -769,7 +769,9 @@ export interface LocalMeshNodeEntry {
|
|
|
769
769
|
clonedFromNodeId?: string;
|
|
770
770
|
/** Repo-local preparation result for ADHDev-created worktree nodes. */
|
|
771
771
|
worktreeBootstrap?: {
|
|
772
|
-
|
|
772
|
+
// 'complete' is the terminal stamp written by markWorktreeBootstrapTerminalState
|
|
773
|
+
// (router.ts) on worktree_bootstrap_complete — kept in sync with WorktreeBootstrapStatus.
|
|
774
|
+
status: 'ready' | 'complete' | 'running' | 'failed' | 'not_configured' | 'disabled' | 'stale';
|
|
773
775
|
required?: boolean;
|
|
774
776
|
configSource?: string;
|
|
775
777
|
configSourceType?: string;
|