@hwip/cli 0.1.22 → 0.1.23
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/README.md +8 -1
- package/dist/args.js +1 -0
- package/dist/cli.js +2 -0
- package/dist/resume-hint.d.ts +27 -0
- package/dist/resume-hint.js +59 -0
- package/dist/wrapper.d.ts +11 -0
- package/dist/wrapper.js +37 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -799,6 +799,13 @@ installs on one machine don't fight over it):
|
|
|
799
799
|
hwip: listener 1.25.0 is stale — restarting on 1.26.0
|
|
800
800
|
```
|
|
801
801
|
|
|
802
|
+
With the login service installed, launchd (or systemd) does the restart —
|
|
803
|
+
and it starts the install the plist was written from, which need not be
|
|
804
|
+
the `hwip` on your PATH (Homebrew's next to an nvm node's, say). When the
|
|
805
|
+
restarted daemon is still the older build, `hwip cc` says which install
|
|
806
|
+
the service runs instead of restarting it again on every launch; re-run
|
|
807
|
+
`hwip listener --install-service` from the `hwip` you use to repoint it.
|
|
808
|
+
|
|
802
809
|
If the PID file is missing (a different account started the daemon, or
|
|
803
810
|
it was removed by hand) the singleton guard can't see it. Find the real
|
|
804
811
|
process instead:
|
|
@@ -1353,7 +1360,7 @@ hwip notify --title "Hello" --json
|
|
|
1353
1360
|
| `forget <session>` | Drop an ended session from this machine's record, so it leaves the app's past list (and the resume whitelist — the same thing deleting the row on the phone does). Refuses a session that is still running: kill it first. `--ended` forgets every remembered session tmux no longer has, which is what a machine that launches agents under a fresh name per task eventually needs |
|
|
1354
1361
|
| `test` | Verify connection and API key |
|
|
1355
1362
|
| `trust [deviceId]` | Advanced — nothing needs it: every device on the account is pinned the first time hwip seals for it, and a changed key replaces its pin with a warning. Shows this computer's key fingerprint and the devices pinned here — compare each with the fingerprint the app shows on that device's page. It also lists the computers of the account not pinned here yet, with their fingerprints; the first send seals for each and pins it. With a `deviceId`, pin the key the account now lists for that device: prints the pinned and the listed fingerprint and asks first, `--yes` skips the question. `--reset` forgets every pin (each is pinned again as hwip sees it), and until then the listener answers a request for a screen in plaintext. Exit 0 done, 1 refused or declined, 2 not a device id |
|
|
1356
|
-
| `cc` · `codex` · `gemini` · `cursor` · `hermes` · `pi` · `opencode` · `kimi` · `grok` · `agy` · `qwen` · `auggie` · `kilo` · `copilot` · `kiro-cli` · `droid` | Run the agent in a `hwip-<project>` tmux session — reattaches a detached session of that project (newest suffix first) when there is one, else auto-suffixes `-2`, `-3`, …. Auto-spawns the background listener on first invocation so the phone picker just works; when that daemon stops within a second and a half (no key, no tmux, a rejected key) it prints the daemon's last log line instead of "autostarted". Once a day it asks npm whether a newer hwip is out and prints the update line (cached in `~/.hwip/update-check.json`; skipped under `CI` or `HWIP_NO_UPDATE_CHECK`). Trailing args pass through to the agent (`hwip cc --resume "..."`). On node 22.15+ the wrapper hands its process to tmux rather than waiting on it, so a running session shows no `hwip cc` of its own in `ps` — older runtimes and Windows keep the waiting wrapper `--detach` / `--label <x>` (front of the args) create without attaching / pin the name; `--wt <name>` runs it in a git worktree made for the session — see "Run agents through the wrapper" above. |
|
|
1363
|
+
| `cc` · `codex` · `gemini` · `cursor` · `hermes` · `pi` · `opencode` · `kimi` · `grok` · `agy` · `qwen` · `auggie` · `kilo` · `copilot` · `kiro-cli` · `droid` | Run the agent in a `hwip-<project>` tmux session — reattaches a detached session of that project (newest suffix first) when there is one, else auto-suffixes `-2`, `-3`, …. Auto-spawns the background listener on first invocation so the phone picker just works; when that daemon stops within a second and a half (no key, no tmux, a rejected key) it prints the daemon's last log line instead of "autostarted". Once a day it asks npm whether a newer hwip is out and prints the update line (cached in `~/.hwip/update-check.json`; skipped under `CI` or `HWIP_NO_UPDATE_CHECK`). Trailing args pass through to the agent (`hwip cc --resume "..."`). When the tmux session ends (not on a detach), it prints the command that picks the conversation back up — `hwip: to resume: claude --resume <id> (or hwip cc --resume <id>)` — for `cc` · `codex` · `hermes` · `pi` · `opencode`, from the id the listener recorded while the session ran (a session shorter than one 5 s sweep has none yet, and gets no line); the tmux command runs under a small `sh` for this, so no node process waits on the session. On node 22.15+ the wrapper hands its process to tmux rather than waiting on it, so a running session shows no `hwip cc` of its own in `ps` — older runtimes and Windows keep the waiting wrapper `--detach` / `--label <x>` (front of the args) create without attaching / pin the name; `--wt <name>` runs it in a git worktree made for the session — see "Run agents through the wrapper" above. |
|
|
1357
1364
|
| `worktree [clean]` | List the git worktrees `--wt` made (path, branch, and `in use` / `clean` / `never used` / what keeps it: uncommitted, untracked or ignored files, commits not pushed). `clean` removes the ones nothing keeps and prints why the others stay; `--dry-run` removes nothing and records nothing. A worktree you made by hand is never listed or removed. Exit 0, also when some stay; `clean` exits 1 when git refused a removal it had cleared or tmux could not be asked (the list still prints, without `in use`); 2 for an unknown subcommand. See "Run agents through the wrapper" for when the listener does the same on its own |
|
|
1358
1365
|
| `schedule` | Start an agent session on a timer, in the background. The listener starts it, so it has to be running (installing the listener as a login service keeps it up). A run missed while the computer slept or the listener was down is skipped, never started late, and the phone is told. At most 20 schedules and 3 scheduled sessions at once; a session is asked to end after `--max-runtime` (never killed). Schedules live in `~/.local/state/hwip/schedules`. `schedule add <agent> (--at HH:MM \| --on YYYY-MM-DD --at HH:MM \| --every <N>m\|<N>h) [-- <agent args…>]` adds one: `<agent>` is an agent command above (`cc`, `codex`, …) and the project is this one; `--on` runs it once. `schedule add --session <name> (…) --prompt <text>` types the prompt into that running session instead of starting one (waits up to 30 min for it to be idle and its terminal quiet). `schedule [list]` shows them, with the next run and the last result (`--json` for scripts). `schedule rm <id>` removes one; a session it already started keeps running. Options: `--days <days>` (with `--at`: `daily`, `weekdays`, or day names like `mon,wed,fri`), `--prompt <text>` (typed into the session once the agent's status rules say it is idle: Claude Code, Codex, Gemini CLI and pi only), `--label <x>`, `--wt <branch>` (a git worktree for the branch, not with `--label`), `--max-runtime <N>m\|<N>h` (default 2h, at most the `--every` period; 5m to 24h). See *Scheduled sessions* |
|
|
1359
1366
|
| `sh` | Open (or reattach) `hwip-<project>-sh`, a login shell the phone may type into — the one shell pane the listener does not refuse. `--label <x>` / `--detach` as for the agents; inside tmux it never attaches and prints a `switch-client` hint. Refuses a session of that name it did not open. Input must be addressed to this machine, is audited, and agents (`agent send`, `hwip_agent_send`) skip it — see [Defense](#defense) |
|
package/dist/args.js
CHANGED
|
@@ -203,6 +203,7 @@ exports.COMMAND_FLAGS = {
|
|
|
203
203
|
'install-service', 'uninstall-service', 'service-status',
|
|
204
204
|
],
|
|
205
205
|
'remote-hook': [],
|
|
206
|
+
'resume-hint': ['session', 'type', 'at'],
|
|
206
207
|
};
|
|
207
208
|
/**
|
|
208
209
|
* Flags the command would ignore, in the order typed. A typo (`--titel`) used
|
package/dist/cli.js
CHANGED
|
@@ -815,6 +815,8 @@ const main = async () => {
|
|
|
815
815
|
return (await import('./listener.js')).handleListener(args);
|
|
816
816
|
case 'remote-hook':
|
|
817
817
|
return handleRemoteHook(args);
|
|
818
|
+
case 'resume-hint':
|
|
819
|
+
return (await import('./resume-hint.js')).handleResumeHint(args);
|
|
818
820
|
default:
|
|
819
821
|
printError(`Unknown command: ${command}`, (0, args_js_1.flagOn)(args, 'json', true));
|
|
820
822
|
printUsage();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The line `hwip cc` / `hwip pi` leaves once its tmux session has ended: the
|
|
3
|
+
* command that picks the conversation back up (`claude --resume <id>`).
|
|
4
|
+
*/
|
|
5
|
+
import { type RemoteAgent } from './remote-agents.js';
|
|
6
|
+
import { type KnownSession } from './session-registry.js';
|
|
7
|
+
export interface ResumeHintDeps {
|
|
8
|
+
recall: (name: string, agentKind: string) => KnownSession | null;
|
|
9
|
+
/** Whether tmux still has the session: a detach returns from `tmux new` as well as an exit does. */
|
|
10
|
+
alive: (name: string) => boolean;
|
|
11
|
+
cwd: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What to print after the tmux session `session` of `agent` returned, or null
|
|
15
|
+
* when there is nothing true to say. `since` is when this `hwip` launched (ms):
|
|
16
|
+
* a record the sweep has not touched since is an earlier run of the same name,
|
|
17
|
+
* whose conversation is not the one just closed. A session shorter than one
|
|
18
|
+
* sweep has no record yet, and gets no line rather than a guess.
|
|
19
|
+
*/
|
|
20
|
+
export declare const resumeHint: (session: string, agent: Pick<RemoteAgent, "kind" | "binary" | "subcommands" | "resumeArgs">, since: number, deps?: ResumeHintDeps) => string | null;
|
|
21
|
+
/**
|
|
22
|
+
* `hwip resume-hint --session <tmux name> --type <agent kind> --at <ms>` — the
|
|
23
|
+
* call `withResumeHint` leaves after tmux. Not for people: it always exits 0,
|
|
24
|
+
* so a missing record or a failed read never changes the agent's exit status.
|
|
25
|
+
*/
|
|
26
|
+
export declare const handleResumeHint: (args: Record<string, string | boolean>) => number;
|
|
27
|
+
//# sourceMappingURL=resume-hint.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The line `hwip cc` / `hwip pi` leaves once its tmux session has ended: the
|
|
4
|
+
* command that picks the conversation back up (`claude --resume <id>`).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.handleResumeHint = exports.resumeHint = void 0;
|
|
8
|
+
const remote_agents_js_1 = require("./remote-agents.js");
|
|
9
|
+
const session_registry_js_1 = require("./session-registry.js");
|
|
10
|
+
const tmux_liveness_js_1 = require("./tmux-liveness.js");
|
|
11
|
+
const wrapper_js_1 = require("./wrapper.js");
|
|
12
|
+
const defaultDeps = () => ({
|
|
13
|
+
recall: (name, kind) => (0, session_registry_js_1.recallRun)(name, kind),
|
|
14
|
+
// `unknown` included: no tmux server answering after `tmux new` returned is the last session gone with it.
|
|
15
|
+
alive: (name) => (0, tmux_liveness_js_1.sessionLiveness)(name) === 'running',
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* What to print after the tmux session `session` of `agent` returned, or null
|
|
20
|
+
* when there is nothing true to say. `since` is when this `hwip` launched (ms):
|
|
21
|
+
* a record the sweep has not touched since is an earlier run of the same name,
|
|
22
|
+
* whose conversation is not the one just closed. A session shorter than one
|
|
23
|
+
* sweep has no record yet, and gets no line rather than a guess.
|
|
24
|
+
*/
|
|
25
|
+
const resumeHint = (session, agent, since, deps = defaultDeps()) => {
|
|
26
|
+
if (!agent.resumeArgs || deps.alive(session))
|
|
27
|
+
return null;
|
|
28
|
+
const run = deps.recall(session, agent.kind);
|
|
29
|
+
if (!run?.agentSessionId || Date.parse(run.lastSeenAt) < since)
|
|
30
|
+
return null;
|
|
31
|
+
const args = agent.resumeArgs(run.agentSessionId, run.cwd);
|
|
32
|
+
if (!args)
|
|
33
|
+
return null;
|
|
34
|
+
const where = run.cwd === deps.cwd ? '' : ` in ${(0, wrapper_js_1.shellQuote)(run.cwd)}`;
|
|
35
|
+
const rest = args.map(wrapper_js_1.shellQuote).join(' ');
|
|
36
|
+
return `hwip: to resume${where}: ${agent.binary} ${rest} (or hwip ${agent.subcommands[0]} ${rest})`;
|
|
37
|
+
};
|
|
38
|
+
exports.resumeHint = resumeHint;
|
|
39
|
+
/**
|
|
40
|
+
* `hwip resume-hint --session <tmux name> --type <agent kind> --at <ms>` — the
|
|
41
|
+
* call `withResumeHint` leaves after tmux. Not for people: it always exits 0,
|
|
42
|
+
* so a missing record or a failed read never changes the agent's exit status.
|
|
43
|
+
*/
|
|
44
|
+
const handleResumeHint = (args) => {
|
|
45
|
+
const agent = remote_agents_js_1.REMOTE_AGENTS.find((a) => a.kind === args.type);
|
|
46
|
+
const since = Number(args.at);
|
|
47
|
+
if (typeof args.session !== 'string' || !agent || !Number.isFinite(since))
|
|
48
|
+
return 0;
|
|
49
|
+
try {
|
|
50
|
+
const line = (0, exports.resumeHint)(args.session, agent, since);
|
|
51
|
+
if (line)
|
|
52
|
+
console.log(line);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
};
|
|
59
|
+
exports.handleResumeHint = handleResumeHint;
|
package/dist/wrapper.d.ts
CHANGED
|
@@ -121,7 +121,18 @@ export declare const splitAgentOptions: (extra: string[]) => {
|
|
|
121
121
|
rest: string[];
|
|
122
122
|
error?: string;
|
|
123
123
|
};
|
|
124
|
+
export declare const shellQuote: (s: string) => string;
|
|
124
125
|
export declare const targetForAgent: (agent: string, extra: string[], agentKind?: string, opts?: AgentLaunchOptions) => SpawnTarget;
|
|
126
|
+
/**
|
|
127
|
+
* `target` (an attached `tmux new`) run under `sh`, which outlives tmux by the
|
|
128
|
+
* one `hwip resume-hint` call and then exits with tmux's status. `sh` rather
|
|
129
|
+
* than this process waiting: the wrapper hands itself over with execve so no
|
|
130
|
+
* node heap sits resident for the whole session, and `sh` costs next to none.
|
|
131
|
+
*/
|
|
132
|
+
export declare const withResumeHint: (target: SpawnTarget, agent: Pick<RemoteAgent, "kind" | "resumeArgs">, since: number, hwip?: {
|
|
133
|
+
node: string;
|
|
134
|
+
cli: string;
|
|
135
|
+
}) => SpawnTarget;
|
|
125
136
|
/**
|
|
126
137
|
* Whether the daemon on record is running a different build than the one
|
|
127
138
|
* we're launching from. `npm i -g @hwip/cli` swaps dist/ but leaves the
|
package/dist/wrapper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleShellSession = exports.startShellSession = exports.shellTargets = exports.readShellSessionState = exports.planShellSession = exports.handleAgentSession = exports.tmuxMissingMessage = exports.inPaneNote = exports.handOffExec = exports.ensureListenerRunning = exports.takeListenerLaunchMarker = exports.listenerVersionDrifted = exports.targetForAgent = exports.splitAgentOptions = exports.sanitizeLabel = exports.findAvailableSession = exports.familyNames = exports.firstFreeSession = exports.tmuxSessionName = exports.installedTmuxVersion = exports.agentLaunchArgs = exports.agentLaunchEnv = exports.herdrEnvArgs = exports.herdrSessionArgs = exports.sessionSidecarArgs = exports.detectProjectName = exports.detectProjectDir = exports.safeBasename = void 0;
|
|
3
|
+
exports.handleShellSession = exports.startShellSession = exports.shellTargets = exports.readShellSessionState = exports.planShellSession = exports.handleAgentSession = exports.tmuxMissingMessage = exports.inPaneNote = exports.handOffExec = exports.ensureListenerRunning = exports.takeListenerLaunchMarker = exports.listenerVersionDrifted = exports.withResumeHint = exports.targetForAgent = exports.shellQuote = exports.splitAgentOptions = exports.sanitizeLabel = exports.findAvailableSession = exports.familyNames = exports.firstFreeSession = exports.tmuxSessionName = exports.installedTmuxVersion = exports.agentLaunchArgs = exports.agentLaunchEnv = exports.herdrEnvArgs = exports.herdrSessionArgs = exports.sessionSidecarArgs = exports.detectProjectName = exports.detectProjectDir = exports.safeBasename = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* `hwip cc` / `hwip codex` / `hwip gemini` — spawn an agent inside a named
|
|
6
6
|
* tmux session so the resident listener (`hwip listener`) can address it
|
|
@@ -331,6 +331,7 @@ exports.splitAgentOptions = splitAgentOptions;
|
|
|
331
331
|
/** POSIX shell-quote so passthrough args survive being joined into a tmux shell-command string. */
|
|
332
332
|
const SHELL_SAFE = /^[\w\-./=:@%+,]+$/;
|
|
333
333
|
const shellQuote = (s) => s.length > 0 && SHELL_SAFE.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
|
|
334
|
+
exports.shellQuote = shellQuote;
|
|
334
335
|
const targetForAgent = (agent, extra, agentKind, opts = {}) => {
|
|
335
336
|
// Already inside tmux → no nested session, just run the agent in the
|
|
336
337
|
// current pane. Nested tmux prefix collisions are confusing and the
|
|
@@ -364,7 +365,7 @@ const targetForAgent = (agent, extra, agentKind, opts = {}) => {
|
|
|
364
365
|
// tmux joins trailing argv into a single shell-command, so flags like
|
|
365
366
|
// `--resume` would be eaten by tmux's own parser. Build one quoted
|
|
366
367
|
// shell string instead, which tmux passes through verbatim.
|
|
367
|
-
const shellCmd = [agent, ...extra].map(shellQuote).join(' ');
|
|
368
|
+
const shellCmd = [agent, ...extra].map(exports.shellQuote).join(' ');
|
|
368
369
|
// `-d`: create without attaching, so no TTY is needed and `new` fails
|
|
369
370
|
// loudly on a taken name instead of `-A` silently attaching to whatever
|
|
370
371
|
// runs there. `-c`: the agent's cwd must be the directory the session is
|
|
@@ -383,6 +384,22 @@ const targetForAgent = (agent, extra, agentKind, opts = {}) => {
|
|
|
383
384
|
return { kind: 'tmux-new', cmd: 'tmux', args: ['new', '-A', '-s', session, ...herdrEnv, ...agentEnv, shellCmd, ...sidecar, ...(0, exports.herdrSessionArgs)(session)], session };
|
|
384
385
|
};
|
|
385
386
|
exports.targetForAgent = targetForAgent;
|
|
387
|
+
/**
|
|
388
|
+
* `target` (an attached `tmux new`) run under `sh`, which outlives tmux by the
|
|
389
|
+
* one `hwip resume-hint` call and then exits with tmux's status. `sh` rather
|
|
390
|
+
* than this process waiting: the wrapper hands itself over with execve so no
|
|
391
|
+
* node heap sits resident for the whole session, and `sh` costs next to none.
|
|
392
|
+
*/
|
|
393
|
+
const withResumeHint = (target, agent, since, hwip = { node: process.execPath, cli: process.argv[1] ?? '' }) => {
|
|
394
|
+
if (target.kind !== 'tmux-new' || !target.session || !agent.resumeArgs || !hwip.cli)
|
|
395
|
+
return target;
|
|
396
|
+
const hint = [hwip.node, hwip.cli, 'resume-hint', '--session', target.session, '--type', agent.kind, '--at', String(since)]
|
|
397
|
+
.map(exports.shellQuote)
|
|
398
|
+
.join(' ');
|
|
399
|
+
const script = `tmux "$@"; s=$?; ${hint} || :; exit $s`;
|
|
400
|
+
return { ...target, cmd: 'sh', args: ['-c', script, 'sh', ...target.args] };
|
|
401
|
+
};
|
|
402
|
+
exports.withResumeHint = withResumeHint;
|
|
386
403
|
// ── Background listener auto-start ────────────────────────────────────
|
|
387
404
|
/**
|
|
388
405
|
* Whether the daemon on record is running a different build than the one
|
|
@@ -444,6 +461,15 @@ const ensureListenerRunning = async (launchedByListener = (0, exports.takeListen
|
|
|
444
461
|
}
|
|
445
462
|
if (reportFailedStart(await (0, listener_process_js_1.awaitServiceListener)(logOffset, pid)))
|
|
446
463
|
return;
|
|
464
|
+
// The service starts whatever install it was registered from, which
|
|
465
|
+
// need not be this one (`hwip` from Homebrew, the plist from an nvm
|
|
466
|
+
// node). Restarting it brings the same old build back, and every
|
|
467
|
+
// `hwip cc` after would restart it again without ever saying why.
|
|
468
|
+
const restarted = (0, listener_process_js_1.runningListenerVersion)();
|
|
469
|
+
if ((0, exports.listenerVersionDrifted)(restarted, config_js_1.VERSION)) {
|
|
470
|
+
console.error(staleServiceNote(restarted, (0, listener_service_js_1.serviceStatus)().cliPath));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
447
473
|
if (pid === null)
|
|
448
474
|
console.log(`hwip: listener started by ${owner} (log: ${listener_process_js_1.LISTENER_LOG_FILE})`);
|
|
449
475
|
else
|
|
@@ -474,6 +500,10 @@ const ensureListenerRunning = async (launchedByListener = (0, exports.takeListen
|
|
|
474
500
|
}
|
|
475
501
|
};
|
|
476
502
|
exports.ensureListenerRunning = ensureListenerRunning;
|
|
503
|
+
/** What to say when the login service came back on an older install than this one. */
|
|
504
|
+
const staleServiceNote = (running, serviceCli) => `hwip: the login service restarted the listener on ${running ?? 'an unstamped build'}, not ${config_js_1.VERSION} — ` +
|
|
505
|
+
`it runs ${serviceCli ?? 'another install'}.\n` +
|
|
506
|
+
' Point it at this install: hwip listener --install-service';
|
|
477
507
|
/** Say why a just-started listener is not running; true when it said anything. */
|
|
478
508
|
const reportFailedStart = (started) => {
|
|
479
509
|
const why = (0, listener_process_js_1.startFailure)(started);
|
|
@@ -558,6 +588,8 @@ const tmuxMissing = (command) => (0, exports.tmuxMissingMessage)(command, (0, tm
|
|
|
558
588
|
const handleAgentSession = async (agent, extra = [], opts = {}, deps = {}) => {
|
|
559
589
|
// First of all, before `ensureWorktree` runs git (its hooks would inherit the marker).
|
|
560
590
|
const launchedByListener = (0, exports.takeListenerLaunchMarker)();
|
|
591
|
+
// The resume hint trusts only a registry record the sweep wrote after this.
|
|
592
|
+
const launchedAt = Date.now();
|
|
561
593
|
// Best-effort: make sure the phone-bridge daemon is running, and running
|
|
562
594
|
// the build we were launched from. The user shouldn't need to remember a
|
|
563
595
|
// second command for the picker on their phone to work.
|
|
@@ -609,7 +641,8 @@ const handleAgentSession = async (agent, extra = [], opts = {}, deps = {}) => {
|
|
|
609
641
|
return 1;
|
|
610
642
|
}
|
|
611
643
|
}
|
|
612
|
-
|
|
644
|
+
const target = { kind, cmd, args, session, env };
|
|
645
|
+
return (deps.launch ?? launchTarget)((0, exports.withResumeHint)(target, agent, launchedAt), agent.binary);
|
|
613
646
|
};
|
|
614
647
|
exports.handleAgentSession = handleAgentSession;
|
|
615
648
|
/**
|
|
@@ -666,7 +699,7 @@ const launchTarget = async ({ kind, cmd, args, session, env: agentEnv }, what) =
|
|
|
666
699
|
// someone whose agent died inside a perfectly good pane to "run
|
|
667
700
|
// from a plain shell pane" would send them after the wrong thing.
|
|
668
701
|
if (kind === 'tmux-new' && code && code !== 0 && dur < 2000) {
|
|
669
|
-
console.error(`hwip:
|
|
702
|
+
console.error(`hwip: tmux exited ${code} after ${dur}ms.\n` +
|
|
670
703
|
` If this terminal is itself inside tmux (or an iTerm/Warp\n` +
|
|
671
704
|
` tmux-integration pane), run \`hwip cc\` from a plain shell\n` +
|
|
672
705
|
` pane instead — \`tmux new\` needs a real TTY to attach.`);
|