@hwip/cli 0.0.1 → 0.1.1
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/LICENSE +190 -0
- package/NOTICE +11 -0
- package/README.md +1983 -0
- package/dist/agent-list.d.ts +76 -0
- package/dist/agent-list.js +102 -0
- package/dist/agent-read.d.ts +105 -0
- package/dist/agent-read.js +309 -0
- package/dist/agent-rules-fetch.d.ts +51 -0
- package/dist/agent-rules-fetch.js +275 -0
- package/dist/agent-rules.default.d.ts +30 -0
- package/dist/agent-rules.default.js +305 -0
- package/dist/agent-session.d.ts +23 -0
- package/dist/agent-session.js +44 -0
- package/dist/agent-state.d.ts +84 -0
- package/dist/agent-state.js +188 -0
- package/dist/agent-target.d.ts +60 -0
- package/dist/agent-target.js +81 -0
- package/dist/agents.d.ts +31 -0
- package/dist/agents.js +75 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +76 -0
- package/dist/args.d.ts +74 -0
- package/dist/args.js +254 -0
- package/dist/ask-context-attach.d.ts +73 -0
- package/dist/ask-context-attach.js +151 -0
- package/dist/ask-context-screen.d.ts +21 -0
- package/dist/ask-context-screen.js +49 -0
- package/dist/ask-context-switch.d.ts +31 -0
- package/dist/ask-context-switch.js +74 -0
- package/dist/ask-context.d.ts +84 -0
- package/dist/ask-context.js +172 -0
- package/dist/ask-pending.d.ts +47 -0
- package/dist/ask-pending.js +221 -0
- package/dist/ask-seal.d.ts +120 -0
- package/dist/ask-seal.js +179 -0
- package/dist/ask.d.ts +107 -0
- package/dist/ask.js +551 -0
- package/dist/blocked-prompt-seal.d.ts +39 -0
- package/dist/blocked-prompt-seal.js +92 -0
- package/dist/blocked-prompt.d.ts +62 -0
- package/dist/blocked-prompt.js +518 -0
- package/dist/channel.d.ts +67 -0
- package/dist/channel.js +136 -0
- package/dist/check-update.d.ts +28 -0
- package/dist/check-update.js +153 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +813 -0
- package/dist/codex-config.d.ts +24 -0
- package/dist/codex-config.js +169 -0
- package/dist/codex-transcript.d.ts +59 -0
- package/dist/codex-transcript.js +373 -0
- package/dist/command-refusal.d.ts +56 -0
- package/dist/command-refusal.js +127 -0
- package/dist/command-scan.d.ts +46 -0
- package/dist/command-scan.js +154 -0
- package/dist/command-seal.d.ts +57 -0
- package/dist/command-seal.js +88 -0
- package/dist/command-seq.d.ts +30 -0
- package/dist/command-seq.js +150 -0
- package/dist/config.d.ts +156 -0
- package/dist/config.js +392 -0
- package/dist/core-sections.d.ts +18 -0
- package/dist/core-sections.js +28 -0
- package/dist/crypto.d.ts +278 -0
- package/dist/crypto.js +698 -0
- package/dist/desktop-notify.d.ts +33 -0
- package/dist/desktop-notify.js +49 -0
- package/dist/device-directory.d.ts +36 -0
- package/dist/device-directory.js +97 -0
- package/dist/device-presence.d.ts +17 -0
- package/dist/device-presence.js +62 -0
- package/dist/downloads.d.ts +29 -0
- package/dist/downloads.js +106 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +28 -0
- package/dist/file-crypto.d.ts +94 -0
- package/dist/file-crypto.js +278 -0
- package/dist/file-view-budget.d.ts +15 -0
- package/dist/file-view-budget.js +34 -0
- package/dist/file-view-switch.d.ts +28 -0
- package/dist/file-view-switch.js +122 -0
- package/dist/file-view.d.ts +108 -0
- package/dist/file-view.js +327 -0
- package/dist/gate.d.ts +141 -0
- package/dist/gate.js +336 -0
- package/dist/gemini-transcript.d.ts +32 -0
- package/dist/gemini-transcript.js +189 -0
- package/dist/http-failure.d.ts +98 -0
- package/dist/http-failure.js +349 -0
- package/dist/hwip-core.generated.d.ts +7 -0
- package/dist/hwip-core.generated.js +14 -0
- package/dist/hwip-hook.d.ts +77 -0
- package/dist/hwip-hook.js +340 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/input-sequencer.d.ts +65 -0
- package/dist/input-sequencer.js +142 -0
- package/dist/insecure-url.d.ts +16 -0
- package/dist/insecure-url.js +76 -0
- package/dist/installer.d.ts +96 -0
- package/dist/installer.js +850 -0
- package/dist/inventory-offload.d.ts +49 -0
- package/dist/inventory-offload.js +209 -0
- package/dist/inventory-worker.d.ts +2 -0
- package/dist/inventory-worker.js +27 -0
- package/dist/jsonc-doc.d.ts +106 -0
- package/dist/jsonc-doc.js +354 -0
- package/dist/keep-awake.d.ts +32 -0
- package/dist/keep-awake.js +46 -0
- package/dist/key-changes.d.ts +48 -0
- package/dist/key-changes.js +140 -0
- package/dist/lan-auth.d.ts +140 -0
- package/dist/lan-auth.js +217 -0
- package/dist/lan-endpoint.d.ts +41 -0
- package/dist/lan-endpoint.js +115 -0
- package/dist/lan-receiver.d.ts +122 -0
- package/dist/lan-receiver.js +510 -0
- package/dist/lan-sender.d.ts +76 -0
- package/dist/lan-sender.js +184 -0
- package/dist/lan-transfer.d.ts +36 -0
- package/dist/lan-transfer.js +54 -0
- package/dist/listener-device-id.d.ts +20 -0
- package/dist/listener-device-id.js +133 -0
- package/dist/listener-log.d.ts +4 -0
- package/dist/listener-log.js +23 -0
- package/dist/listener-process.d.ts +200 -0
- package/dist/listener-process.js +470 -0
- package/dist/listener-service.d.ts +225 -0
- package/dist/listener-service.js +682 -0
- package/dist/listener-worktree-cleanup.d.ts +27 -0
- package/dist/listener-worktree-cleanup.js +62 -0
- package/dist/listener.d.ts +1325 -0
- package/dist/listener.js +5681 -0
- package/dist/login-address.d.ts +25 -0
- package/dist/login-address.js +97 -0
- package/dist/login-page.d.ts +29 -0
- package/dist/login-page.js +93 -0
- package/dist/login.d.ts +98 -0
- package/dist/login.js +388 -0
- package/dist/mcp-command.d.ts +45 -0
- package/dist/mcp-command.js +50 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.js +29 -0
- package/dist/node-support.d.ts +19 -0
- package/dist/node-support.js +38 -0
- package/dist/notify-failure.d.ts +57 -0
- package/dist/notify-failure.js +150 -0
- package/dist/opencode-transcript.d.ts +139 -0
- package/dist/opencode-transcript.js +390 -0
- package/dist/optional-features.d.ts +6 -0
- package/dist/optional-features.js +19 -0
- package/dist/pane-fit.d.ts +40 -0
- package/dist/pane-fit.js +119 -0
- package/dist/peer-key.d.ts +33 -0
- package/dist/peer-key.js +34 -0
- package/dist/pi-transcript.d.ts +26 -0
- package/dist/pi-transcript.js +255 -0
- package/dist/presence.d.ts +14 -0
- package/dist/presence.js +124 -0
- package/dist/private-files.d.ts +51 -0
- package/dist/private-files.js +218 -0
- package/dist/project-switch.d.ts +19 -0
- package/dist/project-switch.js +60 -0
- package/dist/redact.d.ts +10 -0
- package/dist/redact.js +21 -0
- package/dist/remote-agents.d.ts +410 -0
- package/dist/remote-agents.js +1218 -0
- package/dist/remote-hook.d.ts +12 -0
- package/dist/remote-hook.js +196 -0
- package/dist/replace-file.d.ts +50 -0
- package/dist/replace-file.js +328 -0
- package/dist/schedule-command.d.ts +35 -0
- package/dist/schedule-command.js +260 -0
- package/dist/schedule-launch.d.ts +15 -0
- package/dist/schedule-launch.js +24 -0
- package/dist/schedule-ready.d.ts +44 -0
- package/dist/schedule-ready.js +71 -0
- package/dist/schedule-remote.d.ts +77 -0
- package/dist/schedule-remote.js +167 -0
- package/dist/schedule-runner.d.ts +89 -0
- package/dist/schedule-runner.js +452 -0
- package/dist/schedule.d.ts +149 -0
- package/dist/schedule.js +428 -0
- package/dist/secret-mask.d.ts +29 -0
- package/dist/secret-mask.js +245 -0
- package/dist/self-register.d.ts +45 -0
- package/dist/self-register.js +67 -0
- package/dist/send.d.ts +56 -0
- package/dist/send.js +104 -0
- package/dist/session-diff.d.ts +161 -0
- package/dist/session-diff.js +445 -0
- package/dist/session-registry.d.ts +143 -0
- package/dist/session-registry.js +216 -0
- package/dist/sessions-report.d.ts +59 -0
- package/dist/sessions-report.js +99 -0
- package/dist/shell-audit.d.ts +34 -0
- package/dist/shell-audit.js +47 -0
- package/dist/subagent-transcripts.d.ts +167 -0
- package/dist/subagent-transcripts.js +437 -0
- package/dist/subscriber-trust.d.ts +128 -0
- package/dist/subscriber-trust.js +397 -0
- package/dist/templates.d.ts +105 -0
- package/dist/templates.js +871 -0
- package/dist/tmux-install.d.ts +44 -0
- package/dist/tmux-install.js +65 -0
- package/dist/tmux-liveness.d.ts +45 -0
- package/dist/tmux-liveness.js +71 -0
- package/dist/tmux-pane.d.ts +55 -0
- package/dist/tmux-pane.js +126 -0
- package/dist/tmux-socket.d.ts +20 -0
- package/dist/tmux-socket.js +292 -0
- package/dist/todo-fold.d.ts +31 -0
- package/dist/todo-fold.js +102 -0
- package/dist/transcript-tail.d.ts +312 -0
- package/dist/transcript-tail.js +718 -0
- package/dist/trust-command.d.ts +49 -0
- package/dist/trust-command.js +200 -0
- package/dist/trust-lock.d.ts +26 -0
- package/dist/trust-lock.js +94 -0
- package/dist/trust-pins.d.ts +37 -0
- package/dist/trust-pins.js +82 -0
- package/dist/turn-recorder-wire.d.ts +70 -0
- package/dist/turn-recorder-wire.js +165 -0
- package/dist/turn-recorder.d.ts +190 -0
- package/dist/turn-recorder.js +695 -0
- package/dist/turn-ring.d.ts +93 -0
- package/dist/turn-ring.js +418 -0
- package/dist/turn-watch.d.ts +177 -0
- package/dist/turn-watch.js +630 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.js +2 -0
- package/dist/uninstall.d.ts +55 -0
- package/dist/uninstall.js +507 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +421 -0
- package/dist/verify.d.ts +47 -0
- package/dist/verify.js +396 -0
- package/dist/worktree.d.ts +144 -0
- package/dist/worktree.js +310 -0
- package/dist/wrapper.d.ts +254 -0
- package/dist/wrapper.js +704 -0
- package/dist/ws-url.d.ts +109 -0
- package/dist/ws-url.js +202 -0
- package/package.json +78 -10
- package/bin/hwip.js +0 -3
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tmuxArgs = exports.socketKey = exports.invalidateTmuxSocketCache = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const listener_log_js_1 = require("./listener-log.js");
|
|
8
|
+
// ─── tmux socket discovery ──────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* macOS sets a per-user `TMPDIR` like `/var/folders/xz/.../T/`, and tmux
|
|
11
|
+
* (started from a regular shell there) lays its socket at
|
|
12
|
+
* `<TMPDIR>/tmux-<uid>/default`. When the listener is spawned from a
|
|
13
|
+
* shell with a different TMPDIR — or no TMPDIR at all (cron, launchd,
|
|
14
|
+
* IDE-managed terminals) — tmux defaults to `/tmp/tmux-<uid>/default`
|
|
15
|
+
* and the user's real server is invisible. We probe a small list of
|
|
16
|
+
* common locations and use `-S <path>` for every subsequent tmux call
|
|
17
|
+
* once a live server is found.
|
|
18
|
+
*
|
|
19
|
+
* Caching is one-way: a successful discovery sticks for the process
|
|
20
|
+
* lifetime, but failure does NOT — we re-probe every cycle so the
|
|
21
|
+
* listener picks up a tmux server that gets started AFTER the listener
|
|
22
|
+
* itself (very common: the user opens `hwip cc` after starting the
|
|
23
|
+
* daemon). If tmux dies and respawns under a different path the user
|
|
24
|
+
* has to restart the listener (rare).
|
|
25
|
+
*/
|
|
26
|
+
let cachedSocketPath = null;
|
|
27
|
+
/** True once we've confirmed a working socket. `cachedSocketPath` of
|
|
28
|
+
* `null` is ambiguous on its own — it can mean either "use default
|
|
29
|
+
* (we verified it works)" or "we haven't checked yet". This flag
|
|
30
|
+
* removes the ambiguity so we don't re-probe every collectSessions
|
|
31
|
+
* cycle (which was spamming the log with "tmux: default socket OK"). */
|
|
32
|
+
let cacheValid = false;
|
|
33
|
+
/**
|
|
34
|
+
* Mark the cached socket as no longer trustworthy — call this when a
|
|
35
|
+
* tmux command fails against the cached path. The next findTmuxSocket()
|
|
36
|
+
* will redo full discovery (default probe → /var/folders walk → lsof
|
|
37
|
+
* fallback) instead of returning a stale answer. Without this the
|
|
38
|
+
* listener wedged at "reported 0 session(s)" forever after a tmux
|
|
39
|
+
* server restart, even when a new server was live and discoverable.
|
|
40
|
+
*/
|
|
41
|
+
const invalidateTmuxSocketCache = () => {
|
|
42
|
+
cacheValid = false;
|
|
43
|
+
cachedSocketPath = null;
|
|
44
|
+
};
|
|
45
|
+
exports.invalidateTmuxSocketCache = invalidateTmuxSocketCache;
|
|
46
|
+
const probeTmuxSocketDetail = (socketPath) => {
|
|
47
|
+
const args = socketPath ? ['-S', socketPath, 'list-sessions'] : ['list-sessions'];
|
|
48
|
+
const r = (0, child_process_1.spawnSync)('tmux', args, {
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
51
|
+
});
|
|
52
|
+
if (r.status === 0)
|
|
53
|
+
return { ok: true };
|
|
54
|
+
const err = (r.stderr ?? '').trim();
|
|
55
|
+
return { ok: false, stderr: err || undefined };
|
|
56
|
+
};
|
|
57
|
+
const probeTmuxSocket = (socketPath) => probeTmuxSocketDetail(socketPath).ok;
|
|
58
|
+
/**
|
|
59
|
+
* List every socket file inside a `tmux-<uid>/` directory. tmux's
|
|
60
|
+
* default socket name is `default`, but users can change it with
|
|
61
|
+
* `tmux -L <name>` or via .tmux.conf — so we probe every file we find,
|
|
62
|
+
* not just `default`. Returns absolute socket paths.
|
|
63
|
+
*/
|
|
64
|
+
const listSocketsIn = (dir) => {
|
|
65
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
66
|
+
return [];
|
|
67
|
+
try {
|
|
68
|
+
return (0, fs_1.readdirSync)(dir).map((name) => `${dir}/${name}`);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* One name per socket file: the path with its symlinks resolved, or the path
|
|
76
|
+
* as given when nothing is there to resolve. macOS reaches the same directory
|
|
77
|
+
* as `/tmp/…` and `/private/tmp/…`, and as `/var/folders/…` and
|
|
78
|
+
* `/private/var/folders/…`; without this each dead socket there is probed
|
|
79
|
+
* once per spelling.
|
|
80
|
+
*/
|
|
81
|
+
const socketKey = (path) => {
|
|
82
|
+
try {
|
|
83
|
+
return (0, fs_1.realpathSync)(path);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return path;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
exports.socketKey = socketKey;
|
|
90
|
+
/**
|
|
91
|
+
* Final-fallback socket discovery: find tmux server processes via `ps`
|
|
92
|
+
* and ask `lsof` what unix socket each is bound to. Handles the cases
|
|
93
|
+
* filesystem walking can't:
|
|
94
|
+
* - macOS auto-cleanup deleted the socket file while the server kept
|
|
95
|
+
* running (the most likely cause of "no server running" errors when
|
|
96
|
+
* a tmux session is clearly alive in another iTerm/Warp pane)
|
|
97
|
+
* - The user runs tmux with `-L <name>` or `-S <unusual-path>` that we
|
|
98
|
+
* never thought to enumerate
|
|
99
|
+
*
|
|
100
|
+
* `lsof` on macOS may report sockets as `(deleted)`. Even then, if the
|
|
101
|
+
* server still has the inode open we can still tmux-attach by recreating
|
|
102
|
+
* the path — but for now we only return paths that still exist on disk
|
|
103
|
+
* so tmux's connect logic isn't confused. If the path is gone, the user
|
|
104
|
+
* has to `tmux kill-server` + restart anyway.
|
|
105
|
+
*
|
|
106
|
+
* Returns the first path `isLive` accepts, and stops there: every tmux
|
|
107
|
+
* process — each `tmux new -A` client, not only the server — costs one
|
|
108
|
+
* `lsof`, seconds apiece on a loaded daemon.
|
|
109
|
+
*/
|
|
110
|
+
const findTmuxViaProcess = (isLive) => {
|
|
111
|
+
const username = (0, os_1.userInfo)().username;
|
|
112
|
+
const ps = (0, child_process_1.spawnSync)('ps', ['-A', '-o', 'pid=,user=,command='], {
|
|
113
|
+
encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
114
|
+
});
|
|
115
|
+
if (ps.status !== 0)
|
|
116
|
+
return undefined;
|
|
117
|
+
const tmuxPids = [];
|
|
118
|
+
for (const line of (ps.stdout ?? '').split('\n')) {
|
|
119
|
+
const m = line.match(/^\s*(\d+)\s+(\S+)\s+(.+)$/);
|
|
120
|
+
if (!m)
|
|
121
|
+
continue;
|
|
122
|
+
const [, pid, user, cmd] = m;
|
|
123
|
+
if (user !== username)
|
|
124
|
+
continue;
|
|
125
|
+
// Server processes show up as `tmux: server` (with the colon) on
|
|
126
|
+
// some versions; client/wrapper invocations show up as `tmux new`
|
|
127
|
+
// / `tmux attach` etc. lsof works on either.
|
|
128
|
+
if (!/(^|[^\w-])tmux($|[:\s])/.test(cmd))
|
|
129
|
+
continue;
|
|
130
|
+
tmuxPids.push(pid);
|
|
131
|
+
}
|
|
132
|
+
for (const pid of tmuxPids) {
|
|
133
|
+
const lsof = (0, child_process_1.spawnSync)('lsof', ['-p', pid, '-Fn'], {
|
|
134
|
+
encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
135
|
+
});
|
|
136
|
+
if (lsof.status !== 0)
|
|
137
|
+
continue;
|
|
138
|
+
// `-Fn` prints names prefixed with `n`; one per line. Filter for
|
|
139
|
+
// tmux-shaped socket paths.
|
|
140
|
+
for (const lline of (lsof.stdout ?? '').split('\n')) {
|
|
141
|
+
if (!lline.startsWith('n'))
|
|
142
|
+
continue;
|
|
143
|
+
const path = lline.slice(1);
|
|
144
|
+
if (!/\/tmux-\d+\//.test(path))
|
|
145
|
+
continue;
|
|
146
|
+
if (path.includes('(deleted)'))
|
|
147
|
+
continue;
|
|
148
|
+
if (isLive(path))
|
|
149
|
+
return path;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
};
|
|
154
|
+
/** Walk `/var/folders` for user-owned `tmux-<uid>/*` socket files. Each
|
|
155
|
+
* subdir is wrapped in its own try/catch — entries that belong to other
|
|
156
|
+
* users (or that we otherwise can't read) must skip cleanly, not abort
|
|
157
|
+
* the whole walk. */
|
|
158
|
+
const walkVarFolders = (uid) => {
|
|
159
|
+
const found = [];
|
|
160
|
+
const root = '/var/folders';
|
|
161
|
+
if (!(0, fs_1.existsSync)(root))
|
|
162
|
+
return found;
|
|
163
|
+
let topEntries;
|
|
164
|
+
try {
|
|
165
|
+
topEntries = (0, fs_1.readdirSync)(root);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return found;
|
|
169
|
+
}
|
|
170
|
+
for (const a of topEntries) {
|
|
171
|
+
const aPath = `${root}/${a}`;
|
|
172
|
+
let subEntries;
|
|
173
|
+
try {
|
|
174
|
+
subEntries = (0, fs_1.readdirSync)(aPath);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
for (const b of subEntries) {
|
|
180
|
+
found.push(...listSocketsIn(`${aPath}/${b}/T/tmux-${uid}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return found;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Track whether the "no server anywhere" diagnostic was already logged
|
|
187
|
+
* this run. We want the user to see the path list *once* on first
|
|
188
|
+
* failure, then go quiet until we either find a server or notice a new
|
|
189
|
+
* candidate file appearing — otherwise every 30-s cycle would spam the
|
|
190
|
+
* full probe report.
|
|
191
|
+
*/
|
|
192
|
+
let warnedNoServer = false;
|
|
193
|
+
const findTmuxSocket = () => {
|
|
194
|
+
// Successful discovery sticks. Failure does NOT — we want to pick
|
|
195
|
+
// up a tmux server that the user launches *after* `hwip listener`.
|
|
196
|
+
if (cacheValid)
|
|
197
|
+
return cachedSocketPath;
|
|
198
|
+
// Explicit override — for users with `tmux -L <name>` setups or
|
|
199
|
+
// unusual socket locations. Skip discovery entirely if set and
|
|
200
|
+
// probeable.
|
|
201
|
+
const override = process.env.HWIP_TMUX_SOCKET;
|
|
202
|
+
if (override) {
|
|
203
|
+
if (probeTmuxSocket(override)) {
|
|
204
|
+
cachedSocketPath = override;
|
|
205
|
+
cacheValid = true;
|
|
206
|
+
(0, listener_log_js_1.log)(`tmux socket → ${override} (from HWIP_TMUX_SOCKET)`);
|
|
207
|
+
warnedNoServer = false;
|
|
208
|
+
return override;
|
|
209
|
+
}
|
|
210
|
+
// Fall through to standard discovery if override fails — better
|
|
211
|
+
// than failing silently. We re-log this every cycle (no
|
|
212
|
+
// `warnedNoServer`) because it's a user-supplied setting we want
|
|
213
|
+
// to keep nagging about.
|
|
214
|
+
(0, listener_log_js_1.log)(`tmux: HWIP_TMUX_SOCKET=${override} probe failed, falling back to auto-discovery`);
|
|
215
|
+
}
|
|
216
|
+
// Default first — succeeds when the shell that launched us shares
|
|
217
|
+
// tmux's view, and costs one tmux call. It must run before the scans
|
|
218
|
+
// below: `findTmuxViaProcess` runs up to one `lsof` per tmux process (every
|
|
219
|
+
// `tmux new -A` client, not just the server), which took 11–19s+ with 14
|
|
220
|
+
// sessions on a loaded daemon — longer than a fresh inventory worker's
|
|
221
|
+
// whole 20s budget.
|
|
222
|
+
if (probeTmuxSocket(null)) {
|
|
223
|
+
cachedSocketPath = null; // null means "use default"
|
|
224
|
+
cacheValid = true;
|
|
225
|
+
if (!warnedNoServer)
|
|
226
|
+
(0, listener_log_js_1.log)('tmux: default socket OK');
|
|
227
|
+
warnedNoServer = false;
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const uid = (0, os_1.userInfo)().uid;
|
|
231
|
+
const probed = new Set();
|
|
232
|
+
const isLive = (path) => {
|
|
233
|
+
const key = (0, exports.socketKey)(path);
|
|
234
|
+
if (probed.has(key))
|
|
235
|
+
return false;
|
|
236
|
+
probed.add(key);
|
|
237
|
+
return (0, fs_1.existsSync)(path) && probeTmuxSocket(path);
|
|
238
|
+
};
|
|
239
|
+
// Every socket file in any `tmux-<uid>/` dir next — `tmux -L <name>`
|
|
240
|
+
// puts its socket beside `default`, so one probe per file finds it.
|
|
241
|
+
const envDir = process.env.TMUX_TMPDIR || process.env.TMPDIR;
|
|
242
|
+
const files = [
|
|
243
|
+
...(envDir ? listSocketsIn(`${envDir.replace(/\/+$/, '')}/tmux-${uid}`) : []),
|
|
244
|
+
...walkVarFolders(uid),
|
|
245
|
+
...listSocketsIn(`/tmp/tmux-${uid}`),
|
|
246
|
+
...listSocketsIn(`/private/tmp/tmux-${uid}`),
|
|
247
|
+
];
|
|
248
|
+
// Process-based discovery last — it is the only path that finds a `-S`
|
|
249
|
+
// path the walks never look in, but it is also the one that costs seconds.
|
|
250
|
+
const live = files.find(isLive) ?? findTmuxViaProcess(isLive);
|
|
251
|
+
if (live) {
|
|
252
|
+
cachedSocketPath = live;
|
|
253
|
+
cacheValid = true;
|
|
254
|
+
(0, listener_log_js_1.log)(`tmux socket → ${live}`);
|
|
255
|
+
warnedNoServer = false;
|
|
256
|
+
return live;
|
|
257
|
+
}
|
|
258
|
+
const unique = [...probed];
|
|
259
|
+
// No live tmux yet. Log the full probe report once, then stay quiet
|
|
260
|
+
// until something works — otherwise the user gets a 4-line dump
|
|
261
|
+
// every 30 seconds while they're still bringing tmux up. Include the
|
|
262
|
+
// tmux binary identification + stderr for failed probes so the user
|
|
263
|
+
// can spot version mismatches (homebrew on /usr/local vs /opt/homebrew)
|
|
264
|
+
// or stale socket files.
|
|
265
|
+
if (!warnedNoServer) {
|
|
266
|
+
const which = (0, child_process_1.spawnSync)('which', ['tmux'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
267
|
+
const tmuxPath = (which.stdout ?? '').trim() || '(not on PATH)';
|
|
268
|
+
const ver = (0, child_process_1.spawnSync)('tmux', ['-V'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
269
|
+
const tmuxVer = (ver.stdout ?? '').trim() || '?';
|
|
270
|
+
(0, listener_log_js_1.log)(`tmux: no live server yet — using ${tmuxPath} (${tmuxVer})`);
|
|
271
|
+
(0, listener_log_js_1.log)(`tmux: probed ${unique.length} candidate(s):`);
|
|
272
|
+
for (const path of unique) {
|
|
273
|
+
if (!(0, fs_1.existsSync)(path)) {
|
|
274
|
+
(0, listener_log_js_1.log)(` - ${path} (no socket file)`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const detail = probeTmuxSocketDetail(path);
|
|
278
|
+
(0, listener_log_js_1.log)(` ✗ ${path} (${detail.stderr ?? 'probe failed without stderr'})`);
|
|
279
|
+
}
|
|
280
|
+
(0, listener_log_js_1.log)(`tmux: will retry each cycle. If your tmux uses a custom socket,`);
|
|
281
|
+
(0, listener_log_js_1.log)(` run \`tmux info | head -1\` in the same shell as 'hwip cc'`);
|
|
282
|
+
(0, listener_log_js_1.log)(` and pass it via: HWIP_TMUX_SOCKET=<path> hwip listener`);
|
|
283
|
+
warnedNoServer = true;
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
};
|
|
287
|
+
/** Prepend `-S <socket>` when we've discovered a non-default tmux server. */
|
|
288
|
+
const tmuxArgs = (args) => {
|
|
289
|
+
const sock = findTmuxSocket();
|
|
290
|
+
return sock ? ['-S', sock, ...args] : args;
|
|
291
|
+
};
|
|
292
|
+
exports.tmuxArgs = tmuxArgs;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's own to-do list, folded out of Claude Code's task tools.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code does not write the list down whole: TaskCreate adds one task,
|
|
5
|
+
* TaskUpdate moves one, TaskList reads them back. What the timeline wants is the
|
|
6
|
+
* list as it stands, so this folds those steps into it.
|
|
7
|
+
*
|
|
8
|
+
* Only the *results* are read — the user entry's top-level `toolUseResult` —
|
|
9
|
+
* because they carry everything at once: a created task's id comes back there and
|
|
10
|
+
* nowhere in the call (measured 2026-10-09, one transcript: TaskCreate input is
|
|
11
|
+
* `{subject, description, activeForm}`, its result `{task: {id, subject}}`;
|
|
12
|
+
* TaskUpdate's result `{taskId, statusChange: {from, to}}`; TaskList's
|
|
13
|
+
* `{tasks: [{id, subject, status}]}`). Folding results alone means a call and its
|
|
14
|
+
* result landing in different reads never have to be matched up.
|
|
15
|
+
*/
|
|
16
|
+
export type TodoStatus = 'pending' | 'in_progress' | 'completed';
|
|
17
|
+
export interface TodoItem {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly status: TodoStatus;
|
|
21
|
+
}
|
|
22
|
+
export interface TodoList {
|
|
23
|
+
readonly items: readonly TodoItem[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The list after one transcript entry. Returns `list` itself — the same
|
|
27
|
+
* reference — when the entry is not a task result, so a caller tells "changed"
|
|
28
|
+
* from "not" by identity.
|
|
29
|
+
*/
|
|
30
|
+
export declare const applyTodoEntry: (list: TodoList | undefined, entry: Record<string, unknown>) => TodoList | undefined;
|
|
31
|
+
//# sourceMappingURL=todo-fold.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The agent's own to-do list, folded out of Claude Code's task tools.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code does not write the list down whole: TaskCreate adds one task,
|
|
6
|
+
* TaskUpdate moves one, TaskList reads them back. What the timeline wants is the
|
|
7
|
+
* list as it stands, so this folds those steps into it.
|
|
8
|
+
*
|
|
9
|
+
* Only the *results* are read — the user entry's top-level `toolUseResult` —
|
|
10
|
+
* because they carry everything at once: a created task's id comes back there and
|
|
11
|
+
* nowhere in the call (measured 2026-10-09, one transcript: TaskCreate input is
|
|
12
|
+
* `{subject, description, activeForm}`, its result `{task: {id, subject}}`;
|
|
13
|
+
* TaskUpdate's result `{taskId, statusChange: {from, to}}`; TaskList's
|
|
14
|
+
* `{tasks: [{id, subject, status}]}`). Folding results alone means a call and its
|
|
15
|
+
* result landing in different reads never have to be matched up.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.applyTodoEntry = void 0;
|
|
19
|
+
const isTodoStatus = (value) => value === 'pending' || value === 'in_progress' || value === 'completed';
|
|
20
|
+
// `record`, `nonBlank` and `listed` take `unknown` on purpose: they are the parse
|
|
21
|
+
// boundary, reading the vendor's JSON field by field.
|
|
22
|
+
const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : undefined; // narrowed by the object/array check
|
|
23
|
+
const nonBlank = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
24
|
+
const allDone = (list) => list.items.length > 0 && list.items.every((item) => item.status === 'completed');
|
|
25
|
+
/** TaskCreate: one more task, pending. A finished list is replaced, not added to. */
|
|
26
|
+
const created = (list, task) => {
|
|
27
|
+
const id = nonBlank(task.id);
|
|
28
|
+
const text = nonBlank(task.subject);
|
|
29
|
+
if (!id || !text)
|
|
30
|
+
return list;
|
|
31
|
+
const item = { id, text, status: 'pending' };
|
|
32
|
+
// Every task done and a new one arrives: that is the next list starting.
|
|
33
|
+
if (!list || allDone(list))
|
|
34
|
+
return { items: [item] };
|
|
35
|
+
if (list.items.some((existing) => existing.id === id))
|
|
36
|
+
return list;
|
|
37
|
+
return { items: [...list.items, item] };
|
|
38
|
+
};
|
|
39
|
+
/** TaskUpdate: one task moved. `to` undefined = a status outside the three, which takes it off the list. */
|
|
40
|
+
const updated = (list, taskId, to) => {
|
|
41
|
+
// A task this list never saw was made before the read began: nothing to move.
|
|
42
|
+
const task = list?.items.find((item) => item.id === taskId);
|
|
43
|
+
// Already there (or never seen): the same list, so no `todo` goes out again.
|
|
44
|
+
if (!list || !task || task.status === to)
|
|
45
|
+
return list;
|
|
46
|
+
if (!to)
|
|
47
|
+
return { items: list.items.filter((item) => item.id !== taskId) };
|
|
48
|
+
return { items: list.items.map((item) => (item.id === taskId && item.status !== to ? { ...item, status: to } : item)) };
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* TaskList: the tasks it named are refreshed and any it adds are taken, but a
|
|
52
|
+
* task it leaves out stays. The one TaskList measured returned only pending and
|
|
53
|
+
* in-progress tasks, so treating its answer as the whole list would drop every
|
|
54
|
+
* finished one and turn 7/7 into 0/4.
|
|
55
|
+
*/
|
|
56
|
+
const listed = (list, tasks) => {
|
|
57
|
+
const named = [];
|
|
58
|
+
for (const raw of tasks) {
|
|
59
|
+
const task = record(raw);
|
|
60
|
+
const id = nonBlank(task?.id);
|
|
61
|
+
const text = nonBlank(task?.subject);
|
|
62
|
+
if (!id || !text || !isTodoStatus(task?.status))
|
|
63
|
+
return list;
|
|
64
|
+
named.push({ id, text, status: task.status });
|
|
65
|
+
}
|
|
66
|
+
if (!named.length)
|
|
67
|
+
return list;
|
|
68
|
+
const byId = new Map(named.map((item) => [item.id, item]));
|
|
69
|
+
const kept = (list?.items ?? []).map((item) => byId.get(item.id) ?? item);
|
|
70
|
+
const known = new Set(kept.map((item) => item.id));
|
|
71
|
+
const items = [...kept, ...named.filter((item) => !known.has(item.id))];
|
|
72
|
+
const same = list && items.length === list.items.length && items.every((item, i) => sameItem(item, list.items[i]));
|
|
73
|
+
return same ? list : { items };
|
|
74
|
+
};
|
|
75
|
+
const sameItem = (a, b) => a.id === b.id && a.text === b.text && a.status === b.status;
|
|
76
|
+
/**
|
|
77
|
+
* The list after one transcript entry. Returns `list` itself — the same
|
|
78
|
+
* reference — when the entry is not a task result, so a caller tells "changed"
|
|
79
|
+
* from "not" by identity.
|
|
80
|
+
*/
|
|
81
|
+
const applyTodoEntry = (list, entry) => {
|
|
82
|
+
if (entry.type !== 'user')
|
|
83
|
+
return list;
|
|
84
|
+
const result = record(entry.toolUseResult);
|
|
85
|
+
if (!result)
|
|
86
|
+
return list;
|
|
87
|
+
const task = record(result.task);
|
|
88
|
+
// A task that says its status (TaskGet's answer, unmeasured) is a read, not a create:
|
|
89
|
+
// folded like a one-task TaskList, so it cannot restart a finished list.
|
|
90
|
+
if (task && 'status' in task)
|
|
91
|
+
return listed(list, [task]);
|
|
92
|
+
if (task)
|
|
93
|
+
return created(list, task);
|
|
94
|
+
const taskId = nonBlank(result.taskId);
|
|
95
|
+
const change = record(result.statusChange);
|
|
96
|
+
if (taskId && change)
|
|
97
|
+
return updated(list, taskId, isTodoStatus(change.to) ? change.to : undefined);
|
|
98
|
+
if (Array.isArray(result.tasks))
|
|
99
|
+
return listed(list, result.tasks);
|
|
100
|
+
return list;
|
|
101
|
+
};
|
|
102
|
+
exports.applyTodoEntry = applyTodoEntry;
|