@phnx-labs/agents-cli 1.22.41 → 1.22.43
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/CHANGELOG.md +86 -0
- package/README.md +10 -4
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +25 -7
- package/dist/cli/command-registry.d.ts +2 -3
- package/dist/cli/command-registry.js +4 -8
- package/dist/commands/accounts.d.ts +29 -0
- package/dist/commands/accounts.js +220 -29
- package/dist/commands/alias.d.ts +7 -4
- package/dist/commands/alias.js +34 -9
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +108 -0
- package/dist/commands/browser-sessions-picker.js +42 -62
- package/dist/commands/browser.js +30 -38
- package/dist/commands/computer-actions.d.ts +1 -1
- package/dist/commands/computer-actions.js +1 -1
- package/dist/commands/computer-sessions-picker.js +32 -55
- package/dist/commands/computer.d.ts +1 -1
- package/dist/commands/computer.js +22 -8
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +5 -3
- package/dist/commands/doctor.js +23 -8
- package/dist/commands/exec.js +3 -2
- package/dist/commands/feed.js +2 -31
- package/dist/commands/harness.js +3 -3
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/insights.js +163 -85
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/mine.js +1 -1
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/org.d.ts +6 -0
- package/dist/commands/org.js +175 -0
- package/dist/commands/packages.js +1 -1
- package/dist/commands/profiles.js +2 -2
- package/dist/commands/prune.js +1 -1
- package/dist/commands/routines.js +44 -26
- package/dist/commands/run-account-picker.d.ts +19 -0
- package/dist/commands/run-account-picker.js +81 -0
- package/dist/commands/sessions-picker-factory.d.ts +40 -0
- package/dist/commands/sessions-picker-factory.js +67 -0
- package/dist/commands/sessions-picker.js +124 -122
- package/dist/commands/setup-browser.js +4 -4
- package/dist/commands/setup-mine.js +1 -1
- package/dist/commands/setup-preferences.js +2 -2
- package/dist/commands/setup.js +10 -4
- package/dist/commands/snapshot.d.ts +1 -1
- package/dist/commands/snapshot.js +4 -4
- package/dist/commands/status.d.ts +4 -2
- package/dist/commands/status.js +10 -7
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +30 -3
- package/dist/commands/versions.js +1 -1
- package/dist/commands/view.js +7 -1
- package/dist/commands/webhook.js +1 -1
- package/dist/lib/account-capabilities.d.ts +10 -0
- package/dist/lib/account-capabilities.js +34 -0
- package/dist/lib/accounting/rotate.d.ts +5 -3
- package/dist/lib/accounting/rotate.js +36 -7
- package/dist/lib/accounting/usage.d.ts +18 -4
- package/dist/lib/accounting/usage.js +138 -27
- package/dist/lib/agent-spec/agents.d.ts +584 -0
- package/dist/lib/agent-spec/agents.js +3127 -0
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +2 -584
- package/dist/lib/agents.js +2 -3127
- package/dist/lib/browser/drivers/local.d.ts +8 -2
- package/dist/lib/browser/drivers/local.js +8 -2
- package/dist/lib/browser/drivers/ssh.d.ts +8 -2
- package/dist/lib/browser/drivers/ssh.js +10 -4
- package/dist/lib/browser/hygiene.d.ts +1 -1
- package/dist/lib/browser/hygiene.js +5 -1
- package/dist/lib/browser/profiles.d.ts +74 -1
- package/dist/lib/browser/profiles.js +114 -5
- package/dist/lib/browser/runtime-state.d.ts +11 -5
- package/dist/lib/browser/runtime-state.js +13 -9
- package/dist/lib/browser/service.d.ts +68 -30
- package/dist/lib/browser/service.js +305 -194
- package/dist/lib/browser/types.d.ts +95 -3
- package/dist/lib/browser/types.js +51 -0
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/cloud/rush.d.ts +0 -15
- package/dist/lib/cloud/rush.js +0 -35
- package/dist/lib/computer/actions.d.ts +1 -1
- package/dist/lib/computer/actions.js +1 -1
- package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
- package/dist/lib/computer/dispatch.d.ts +1 -1
- package/dist/lib/computer/download.d.ts +1 -1
- package/dist/lib/computer/download.js +3 -3
- package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
- package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
- package/dist/lib/crabbox/runtimes.d.ts +6 -2
- package/dist/lib/crabbox/runtimes.js +21 -5
- package/dist/lib/daemon/daemon.js +87 -59
- package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +28 -11
- package/dist/lib/{runner.js → daemon/runner.js} +143 -123
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +4 -4
- package/dist/lib/devices/fleet-inventory.js +1 -1
- package/dist/lib/devices/harness-inventory.js +1 -1
- package/dist/lib/devices/health-report.js +1 -1
- package/dist/lib/devices/registry.js +4 -22
- package/dist/lib/doctor-diff.d.ts +1 -1
- package/dist/lib/doctor-diff.js +1 -1
- package/dist/lib/drift-sync.d.ts +1 -1
- package/dist/lib/drift-sync.js +2 -2
- package/dist/lib/drift.d.ts +1 -1
- package/dist/lib/drift.js +1 -1
- package/dist/lib/entitlement.d.ts +31 -0
- package/dist/lib/entitlement.js +137 -0
- package/dist/lib/exec.js +52 -205
- package/dist/lib/feed/feed.js +2 -8
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/format.js +16 -0
- package/dist/lib/fs-atomic.d.ts +7 -0
- package/dist/lib/fs-atomic.js +9 -0
- package/dist/lib/github/pr-mergeable.d.ts +49 -0
- package/dist/lib/github/pr-mergeable.js +157 -0
- package/dist/lib/github/pr-verdict.d.ts +67 -0
- package/dist/lib/github/pr-verdict.js +72 -0
- package/dist/lib/harness/adapter.d.ts +161 -0
- package/dist/lib/harness/adapter.js +47 -0
- package/dist/lib/harness/adapters/claude.d.ts +2 -0
- package/dist/lib/harness/adapters/claude.js +110 -0
- package/dist/lib/harness/adapters/codex.d.ts +2 -0
- package/dist/lib/harness/adapters/codex.js +83 -0
- package/dist/lib/harness/adapters/copilot.d.ts +2 -0
- package/dist/lib/harness/adapters/copilot.js +23 -0
- package/dist/lib/harness/adapters/cursor.d.ts +2 -0
- package/dist/lib/harness/adapters/cursor.js +38 -0
- package/dist/lib/harness/adapters/droid.d.ts +2 -0
- package/dist/lib/harness/adapters/droid.js +17 -0
- package/dist/lib/harness/adapters/grok.d.ts +2 -0
- package/dist/lib/harness/adapters/grok.js +14 -0
- package/dist/lib/harness/adapters/kimi.d.ts +2 -0
- package/dist/lib/harness/adapters/kimi.js +49 -0
- package/dist/lib/harness/adapters/muse.d.ts +2 -0
- package/dist/lib/harness/adapters/muse.js +44 -0
- package/dist/lib/harness/adapters/opencode.d.ts +2 -0
- package/dist/lib/harness/adapters/opencode.js +14 -0
- package/dist/lib/harness/exec-config-version.d.ts +8 -0
- package/dist/lib/harness/exec-config-version.js +22 -0
- package/dist/lib/harness/index.d.ts +1 -0
- package/dist/lib/harness/index.js +34 -0
- package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
- package/dist/lib/{hooks.js → hooks/install.js} +11 -11
- package/dist/lib/hosts/passthrough.d.ts +10 -0
- package/dist/lib/hosts/passthrough.js +28 -6
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/import.js +1 -1
- package/dist/lib/installations/index.d.ts +4 -4
- package/dist/lib/installations/index.js +3 -4
- package/dist/lib/installations/migrate.js +6 -6
- package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
- package/dist/lib/{shims.js → installations/shims.js} +13 -115
- package/dist/lib/installations/store.d.ts +141 -3
- package/dist/lib/installations/store.js +475 -5
- package/dist/lib/installations/versions.d.ts +40 -125
- package/dist/lib/installations/versions.js +57 -462
- package/dist/lib/isolation-boundary-report.d.ts +1 -1
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/mailbox-gc.js +2 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.js +19 -8
- package/dist/lib/monitors/config.d.ts +2 -2
- package/dist/lib/monitors/config.js +3 -2
- package/dist/lib/monitors/dispatch.d.ts +1 -1
- package/dist/lib/monitors/dispatch.js +2 -2
- package/dist/lib/monitors/state.d.ts +1 -1
- package/dist/lib/origin-machine.d.ts +18 -0
- package/dist/lib/origin-machine.js +34 -0
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/perf/spool.js +1 -1
- package/dist/lib/plugins/plugins.js +1 -1
- package/dist/lib/plugins/skills.js +1 -1
- package/dist/lib/prix-account.d.ts +158 -0
- package/dist/lib/prix-account.js +214 -0
- package/dist/lib/profiles.d.ts +7 -0
- package/dist/lib/profiles.js +26 -1
- package/dist/lib/project-key.js +2 -2
- package/dist/lib/project-launch.js +1 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/resource-inventory.d.ts +1 -1
- package/dist/lib/resource-inventory.js +1 -1
- package/dist/lib/rules/rules.js +1 -1
- package/dist/lib/sandbox.d.ts +24 -0
- package/dist/lib/sandbox.js +101 -0
- package/dist/lib/scheduling/routines.d.ts +18 -9
- package/dist/lib/scheduling/routines.js +17 -9
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.js +9 -1
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.js +1 -1
- package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
- package/dist/lib/self-heal/checks/path.js +1 -1
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/service-manifest.d.ts +20 -0
- package/dist/lib/service-manifest.js +34 -0
- package/dist/lib/session/cloud.js +1 -1
- package/dist/lib/session/db.js +1 -1
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +3 -3
- package/dist/lib/session/fork.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -1
- package/dist/lib/session/origin-machine.d.ts +1 -18
- package/dist/lib/session/origin-machine.js +4 -34
- package/dist/lib/session/parse.js +24 -42
- package/dist/lib/session/recovery.js +1 -1
- package/dist/lib/session/relative-time.d.ts +1 -44
- package/dist/lib/session/relative-time.js +4 -120
- package/dist/lib/session/remote-list.js +1 -1
- package/dist/lib/session/remote.js +2 -2
- package/dist/lib/session/short-id.d.ts +1 -17
- package/dist/lib/session/short-id.js +4 -20
- package/dist/lib/session/width.d.ts +1 -29
- package/dist/lib/session/width.js +4 -101
- package/dist/lib/snapshot.d.ts +2 -2
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/staleness/prune.d.ts +1 -1
- package/dist/lib/staleness/prune.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +4 -1
- package/dist/lib/startup/command-registry.js +12 -5
- package/dist/lib/teams/agents.js +2 -19
- package/dist/lib/teams/parsers.js +20 -26
- package/dist/lib/teams/placement-probe.js +1 -1
- package/dist/lib/teams/registry.js +2 -21
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/text/relative-time.d.ts +44 -0
- package/dist/lib/text/relative-time.js +120 -0
- package/dist/lib/text/short-id.d.ts +17 -0
- package/dist/lib/text/short-id.js +20 -0
- package/dist/lib/text/width.d.ts +29 -0
- package/dist/lib/text/width.js +101 -0
- package/dist/lib/triggers/handlers.d.ts +7 -0
- package/dist/lib/triggers/handlers.js +9 -1
- package/dist/lib/triggers/webhook.d.ts +1 -0
- package/dist/lib/triggers/webhook.js +3 -2
- package/dist/lib/uninstall.js +1 -1
- package/dist/lib/version.d.ts +9 -2
- package/dist/lib/version.js +11 -9
- package/dist/lib/view-types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/commands/tickets.d.ts +0 -2
- package/dist/commands/tickets.js +0 -43
- package/dist/lib/observe-aliases.d.ts +0 -30
- package/dist/lib/observe-aliases.js +0 -38
- package/dist/lib/tickets/list.d.ts +0 -53
- package/dist/lib/tickets/list.js +0 -153
- /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const LOADED_COMMAND_NAMES = [
|
|
2
|
-
'accounts', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
2
|
+
'accounts', 'auth', 'org', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
3
3
|
'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use', 'list',
|
|
4
4
|
'remove', 'rm', 'purge', 'update', 'prune', 'import', 'registry', 'search', 'install',
|
|
5
5
|
'routines', 'monitors', 'projects', 'run', 'open', 'reconnect', 'fork', 'config',
|
|
6
|
-
'models', 'modes', 'trash', 'restore', 'doctor', 'apply',
|
|
6
|
+
'models', 'modes', 'trash', 'restore', 'doctor', 'apply',
|
|
7
7
|
'route', 'harness', 'harnesses', 'secrets', 'menubar', 'beta', 'sync',
|
|
8
8
|
'refresh-rules', 'factory', 'usage', 'insights', 'perf', 'trends',
|
|
9
|
-
'
|
|
9
|
+
'pty', 'tmux', 'watchdog', 'browser', 'computer', 'logs', 'events',
|
|
10
10
|
'ssh', 'devices', 'fleet', 'repos', 'repo', 'setup', 'uninstall', 'upgrade', 'sessions',
|
|
11
|
-
'teams', '
|
|
11
|
+
'teams', 'cloud', 'message', 'send', 'notify', 'feed',
|
|
12
12
|
'mailboxes', 'mailbox', 'serve', 'artifacts', 'unshare', 'audit', 'webhooks',
|
|
13
13
|
'humans', 'daemon',
|
|
14
14
|
];
|
|
@@ -41,7 +41,10 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
|
|
|
41
41
|
* under `agents artifacts share` (RUSH-2580). login/logout/budget/bench/mine/
|
|
42
42
|
* cost/output/profiles/snapshot/cp/resume/roster moved under nested homes
|
|
43
43
|
* (cli-surface-consolidate). `timeline` was removed as a duplicated surface —
|
|
44
|
-
* use `agents feed --filter updates` (RUSH-2692).
|
|
44
|
+
* use `agents feed --filter updates` (RUSH-2692). `status` moved under
|
|
45
|
+
* `agents sync status` (RUSH-2864). `tickets` was removed — use `linear`
|
|
46
|
+
* (linear-cli) (RUSH-2932). `alias` moved under `agents setup alias` (RUSH-2965).
|
|
47
|
+
* `inbox` was a pure alias of `agents feed` (RUSH-2984).
|
|
45
48
|
*/
|
|
46
49
|
export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
47
50
|
'webhook',
|
|
@@ -60,6 +63,10 @@ export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
|
60
63
|
'set',
|
|
61
64
|
'share',
|
|
62
65
|
'timeline',
|
|
66
|
+
'status',
|
|
67
|
+
'tickets',
|
|
68
|
+
'alias',
|
|
69
|
+
'inbox',
|
|
63
70
|
]);
|
|
64
71
|
export function isKnownTopLevelCommand(name) {
|
|
65
72
|
return KNOWN_TOP_LEVEL_COMMANDS.has(name);
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -34,6 +34,7 @@ import { resolveRemoteOsSync } from '../hosts/remote-os.js';
|
|
|
34
34
|
import { pullRemoteLogDelta, REMOTE_MIRROR_MAX_BYTES } from '../hosts/progress.js';
|
|
35
35
|
import { createRemoteWorktree, ensureRemoteRepo } from './remoteWorktree.js';
|
|
36
36
|
import { getTeam, isTeamDisbanded } from './registry.js';
|
|
37
|
+
import { atomicWriteJsonSync } from '../fs-atomic.js';
|
|
37
38
|
import { resolvePlacement, classifyExclusions, NoViableDeviceError } from './scheduler.js';
|
|
38
39
|
import { probePoolSignals } from './placement-probe.js';
|
|
39
40
|
import { readMaxConcurrentCaps } from '../device-config.js';
|
|
@@ -321,24 +322,6 @@ function extractTimestamp(raw) {
|
|
|
321
322
|
}
|
|
322
323
|
return null;
|
|
323
324
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Atomic JSON write: writes to a unique sibling tmp file then renames over the
|
|
326
|
-
* target. rename(2) is atomic on POSIX, so a process killed mid-write leaves
|
|
327
|
-
* either the previous valid meta.json or the new one, never a torn,
|
|
328
|
-
* unparseable file (RUSH-2429). Mirrors the identical pattern already used by
|
|
329
|
-
* `teams/registry.ts`'s `atomicWriteJson`.
|
|
330
|
-
*/
|
|
331
|
-
async function atomicWriteJson(p, data) {
|
|
332
|
-
const tmp = `${p}.tmp.${process.pid}.${randomUUID()}`;
|
|
333
|
-
await fs.writeFile(tmp, JSON.stringify(data, null, 2));
|
|
334
|
-
try {
|
|
335
|
-
await fs.rename(tmp, p);
|
|
336
|
-
}
|
|
337
|
-
catch (err) {
|
|
338
|
-
await fs.unlink(tmp).catch(() => { });
|
|
339
|
-
throw err;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
325
|
/** Resolve a mode string to a validated Mode, falling back to the given default. */
|
|
343
326
|
export function resolveMode(requestedMode, defaultMode = 'plan') {
|
|
344
327
|
const normalizedDefault = normalizeModeValue(defaultMode);
|
|
@@ -1011,7 +994,7 @@ export class AgentProcess {
|
|
|
1011
994
|
remote_log_offset: this.remoteLogOffset,
|
|
1012
995
|
};
|
|
1013
996
|
const metaPath = await this.getMetaPath();
|
|
1014
|
-
|
|
997
|
+
atomicWriteJsonSync(metaPath, meta);
|
|
1015
998
|
}
|
|
1016
999
|
/**
|
|
1017
1000
|
* Rename an unreadable meta.json out of the way so it stops silently
|
|
@@ -9,34 +9,28 @@
|
|
|
9
9
|
import { extractFileOpsFromBash } from './file_ops.js';
|
|
10
10
|
const claudeToolUseMap = new Map();
|
|
11
11
|
const droidToolUseMap = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Registry-dispatch table: each teams `AgentType` to its live team-event
|
|
14
|
+
* normalizer. Replaces the per-type name-chain — the harness axis of Move 3.
|
|
15
|
+
* warp (and any unlisted type) is absent, so it falls through to the generic
|
|
16
|
+
* unknown-event shape below, exactly as the old `else` did.
|
|
17
|
+
*/
|
|
18
|
+
const TEAM_EVENT_NORMALIZERS = {
|
|
19
|
+
codex: normalizeCodex,
|
|
20
|
+
cursor: normalizeCursor,
|
|
21
|
+
gemini: normalizeGemini,
|
|
22
|
+
claude: normalizeClaude,
|
|
23
|
+
opencode: normalizeOpencode,
|
|
24
|
+
grok: normalizeGrok,
|
|
25
|
+
antigravity: normalizeAntigravity,
|
|
26
|
+
kimi: normalizeKimi,
|
|
27
|
+
droid: normalizeDroid,
|
|
28
|
+
};
|
|
12
29
|
/** Normalize a raw JSON event from any agent type into an array of unified event objects. */
|
|
13
30
|
export function normalizeEvents(agentType, raw) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
else if (agentType === 'cursor') {
|
|
18
|
-
return normalizeCursor(raw);
|
|
19
|
-
}
|
|
20
|
-
else if (agentType === 'gemini') {
|
|
21
|
-
return normalizeGemini(raw);
|
|
22
|
-
}
|
|
23
|
-
else if (agentType === 'claude') {
|
|
24
|
-
return normalizeClaude(raw);
|
|
25
|
-
}
|
|
26
|
-
else if (agentType === 'opencode') {
|
|
27
|
-
return normalizeOpencode(raw);
|
|
28
|
-
}
|
|
29
|
-
else if (agentType === 'grok') {
|
|
30
|
-
return normalizeGrok(raw);
|
|
31
|
-
}
|
|
32
|
-
else if (agentType === 'antigravity') {
|
|
33
|
-
return normalizeAntigravity(raw);
|
|
34
|
-
}
|
|
35
|
-
else if (agentType === 'kimi') {
|
|
36
|
-
return normalizeKimi(raw);
|
|
37
|
-
}
|
|
38
|
-
else if (agentType === 'droid') {
|
|
39
|
-
return normalizeDroid(raw);
|
|
31
|
+
const normalizer = TEAM_EVENT_NORMALIZERS[agentType];
|
|
32
|
+
if (normalizer) {
|
|
33
|
+
return normalizer(raw);
|
|
40
34
|
}
|
|
41
35
|
const timestamp = new Date().toISOString();
|
|
42
36
|
return [{
|
|
@@ -26,7 +26,7 @@ import { probeFleetStats, headroom } from '../devices/health.js';
|
|
|
26
26
|
import { loadDevicesSync } from '../devices/registry.js';
|
|
27
27
|
import { buildSshInvocation, writeAskpassShim } from '../devices/connect.js';
|
|
28
28
|
import { buildReadyProbeCommand, parseReadyProbe, viewAgentSignedIn, viewHasAgent, } from '../hosts/ready.js';
|
|
29
|
-
import { localMachineId } from '../
|
|
29
|
+
import { localMachineId } from '../origin-machine.js';
|
|
30
30
|
import { normalizeHost } from '../machine-id.js';
|
|
31
31
|
import { checkCliAvailable, checkCliSignedIn } from './agents.js';
|
|
32
32
|
/** Per-remote readiness probe budget — matches the health probe's short window
|
|
@@ -10,32 +10,13 @@
|
|
|
10
10
|
import * as fs from 'fs/promises';
|
|
11
11
|
import * as fsSync from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
13
|
-
import { randomBytes } from 'crypto';
|
|
14
13
|
import lockfile from 'proper-lockfile';
|
|
15
14
|
import { getTeamsRegistryPath } from '../state.js';
|
|
16
15
|
import { emit } from '../feed/events.js';
|
|
16
|
+
import { atomicWriteJsonSync } from '../fs-atomic.js';
|
|
17
17
|
async function registryPath() {
|
|
18
18
|
return getTeamsRegistryPath();
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Atomic JSON write: writes to a unique sibling tmp file then renames over
|
|
22
|
-
* the target. rename(2) is atomic on POSIX, so a crashed write leaves the
|
|
23
|
-
* old file untouched instead of producing a half-written registry that
|
|
24
|
-
* loadTeams() would reject.
|
|
25
|
-
*/
|
|
26
|
-
async function atomicWriteJson(p, data) {
|
|
27
|
-
await fs.mkdir(path.dirname(p), { recursive: true });
|
|
28
|
-
const tmp = `${p}.tmp.${process.pid}.${randomBytes(4).toString('hex')}`;
|
|
29
|
-
const body = JSON.stringify(data, null, 2);
|
|
30
|
-
await fs.writeFile(tmp, body);
|
|
31
|
-
try {
|
|
32
|
-
await fs.rename(tmp, p);
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
await fs.unlink(tmp).catch(() => { });
|
|
36
|
-
throw err;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
20
|
/**
|
|
40
21
|
* Run `fn` while holding an exclusive cross-process lock on the registry
|
|
41
22
|
* file. proper-lockfile requires the target to exist, so we touch it first.
|
|
@@ -91,7 +72,7 @@ export async function loadTeams() {
|
|
|
91
72
|
}
|
|
92
73
|
async function saveTeams(reg) {
|
|
93
74
|
const p = await registryPath();
|
|
94
|
-
|
|
75
|
+
atomicWriteJsonSync(p, reg);
|
|
95
76
|
}
|
|
96
77
|
/** Create a new team. Throws if a team with the same name already exists. */
|
|
97
78
|
export async function createTeam(name, options) {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* resolves to a null placement so the existing local spawn path runs unchanged,
|
|
35
35
|
* letting the local machine participate in a pool as just another member.
|
|
36
36
|
*/
|
|
37
|
-
import { machineId } from '../
|
|
37
|
+
import { machineId } from '../machine-id.js';
|
|
38
38
|
/**
|
|
39
39
|
* No device in the pool can host the teammate. Carries the per-device reasons so
|
|
40
40
|
* the caller can print exactly why the pool is unusable instead of silently
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable "time since" formatting for session timestamps. Lives here (not
|
|
3
|
+
* inline in `sessions.ts`) so both the session list renderer and the remote
|
|
4
|
+
* offline-cache banner (`remote.ts`) share one formatter — `sessions.ts` imports
|
|
5
|
+
* `remote.ts`, so a back-import would cycle.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatRelativeTime(isoTimestamp: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Compact age — `now`, `45m`, `3h`, `2d`, then the calendar date. This is the
|
|
10
|
+
* *creation* half of a session's time cell: it renders right next to the long
|
|
11
|
+
* last-activity label, and one row can afford one long form, not two.
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatCompactAge(isoTimestamp: string): string;
|
|
14
|
+
/** The two time fields a session listing shows for one row. */
|
|
15
|
+
export interface SessionAgeParts {
|
|
16
|
+
/**
|
|
17
|
+
* Compact age of the session's creation, set only when the session ran long
|
|
18
|
+
* enough (>= a minute) for it to say something `last` does not.
|
|
19
|
+
*/
|
|
20
|
+
created?: string;
|
|
21
|
+
/** Last-activity label — the long relative form the listing sorts by. */
|
|
22
|
+
last: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Split a session's timestamps into the two fields a listing renders: when it
|
|
26
|
+
* was created and when it was last active. One last-activity label answers
|
|
27
|
+
* neither "is this the old session I'm looking for" nor "how long did it run" —
|
|
28
|
+
* a session touched an hour ago may have started last week.
|
|
29
|
+
*/
|
|
30
|
+
export declare function sessionAgeParts(createdIso: string, lastActivityIso?: string): SessionAgeParts;
|
|
31
|
+
/**
|
|
32
|
+
* Parse a time filter string (relative like '7d' or an ISO timestamp) into epoch
|
|
33
|
+
* milliseconds. Backs `--since` on `sessions`, `cost`, `teams`, and `output`, and
|
|
34
|
+
* `--unused` on `secrets list`.
|
|
35
|
+
*
|
|
36
|
+
* It lives here, in a module with no imports, rather than in `discover.ts` where
|
|
37
|
+
* it started: `discover.ts` loads `../sqlite.js`, so importing this one function
|
|
38
|
+
* from there pulls `node:sqlite` into the caller's module graph and Node prints
|
|
39
|
+
* `ExperimentalWarning: SQLite …` on stderr. That is invisible in a command that
|
|
40
|
+
* already touches the session DB, and a broken contract in one that doesn't —
|
|
41
|
+
* `monitors --json` asserts a clean stderr. `discover.ts` re-exports it, so
|
|
42
|
+
* existing importers are unaffected.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseTimeFilter(input: string): number;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable "time since" formatting for session timestamps. Lives here (not
|
|
3
|
+
* inline in `sessions.ts`) so both the session list renderer and the remote
|
|
4
|
+
* offline-cache banner (`remote.ts`) share one formatter — `sessions.ts` imports
|
|
5
|
+
* `remote.ts`, so a back-import would cycle.
|
|
6
|
+
*/
|
|
7
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
8
|
+
/**
|
|
9
|
+
* Calendar label for a timestamp older than the relative window. A 2-digit year
|
|
10
|
+
* is appended outside the current year so "Jun 28" is never ambiguous across
|
|
11
|
+
* years (e.g. "Jun 28 '25").
|
|
12
|
+
*/
|
|
13
|
+
function calendarLabel(thenMs, nowMs) {
|
|
14
|
+
const d = new Date(thenMs);
|
|
15
|
+
const label = `${MONTHS[d.getMonth()]} ${d.getDate()}`;
|
|
16
|
+
return d.getFullYear() === new Date(nowMs).getFullYear()
|
|
17
|
+
? label
|
|
18
|
+
: `${label} '${String(d.getFullYear()).slice(-2)}`;
|
|
19
|
+
}
|
|
20
|
+
export function formatRelativeTime(isoTimestamp) {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const then = new Date(isoTimestamp).getTime();
|
|
23
|
+
if (isNaN(then))
|
|
24
|
+
return isoTimestamp;
|
|
25
|
+
const diffMs = now - then;
|
|
26
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
27
|
+
const diffHrs = Math.floor(diffMs / 3_600_000);
|
|
28
|
+
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
29
|
+
if (diffMin < 1)
|
|
30
|
+
return 'just now';
|
|
31
|
+
if (diffMin < 60)
|
|
32
|
+
return `${diffMin} min ago`;
|
|
33
|
+
if (diffHrs < 24)
|
|
34
|
+
return `${diffHrs} hour${diffHrs === 1 ? '' : 's'} ago`;
|
|
35
|
+
if (diffDays < 7)
|
|
36
|
+
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
37
|
+
// Older: show the date.
|
|
38
|
+
return calendarLabel(then, now);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Compact age — `now`, `45m`, `3h`, `2d`, then the calendar date. This is the
|
|
42
|
+
* *creation* half of a session's time cell: it renders right next to the long
|
|
43
|
+
* last-activity label, and one row can afford one long form, not two.
|
|
44
|
+
*/
|
|
45
|
+
export function formatCompactAge(isoTimestamp) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const then = new Date(isoTimestamp).getTime();
|
|
48
|
+
if (isNaN(then))
|
|
49
|
+
return isoTimestamp;
|
|
50
|
+
const diffMs = now - then;
|
|
51
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
52
|
+
const diffHrs = Math.floor(diffMs / 3_600_000);
|
|
53
|
+
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
54
|
+
if (diffMin < 1)
|
|
55
|
+
return 'now';
|
|
56
|
+
if (diffMin < 60)
|
|
57
|
+
return `${diffMin}m`;
|
|
58
|
+
if (diffHrs < 24)
|
|
59
|
+
return `${diffHrs}h`;
|
|
60
|
+
if (diffDays < 7)
|
|
61
|
+
return `${diffDays}d`;
|
|
62
|
+
return calendarLabel(then, now);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Shortest creation→last-activity gap worth rendering as two fields. Below it
|
|
66
|
+
* both halves name the same minute, so the row would carry a duplicate instead
|
|
67
|
+
* of information.
|
|
68
|
+
*/
|
|
69
|
+
const SPAN_MIN_MS = 60_000;
|
|
70
|
+
/**
|
|
71
|
+
* Split a session's timestamps into the two fields a listing renders: when it
|
|
72
|
+
* was created and when it was last active. One last-activity label answers
|
|
73
|
+
* neither "is this the old session I'm looking for" nor "how long did it run" —
|
|
74
|
+
* a session touched an hour ago may have started last week.
|
|
75
|
+
*/
|
|
76
|
+
export function sessionAgeParts(createdIso, lastActivityIso) {
|
|
77
|
+
const lastIso = lastActivityIso ?? createdIso;
|
|
78
|
+
const last = formatRelativeTime(lastIso);
|
|
79
|
+
const createdMs = new Date(createdIso).getTime();
|
|
80
|
+
const lastMs = new Date(lastIso).getTime();
|
|
81
|
+
if (isNaN(createdMs) || isNaN(lastMs) || lastMs - createdMs < SPAN_MIN_MS)
|
|
82
|
+
return { last };
|
|
83
|
+
return { created: formatCompactAge(createdIso), last };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse a time filter string (relative like '7d' or an ISO timestamp) into epoch
|
|
87
|
+
* milliseconds. Backs `--since` on `sessions`, `cost`, `teams`, and `output`, and
|
|
88
|
+
* `--unused` on `secrets list`.
|
|
89
|
+
*
|
|
90
|
+
* It lives here, in a module with no imports, rather than in `discover.ts` where
|
|
91
|
+
* it started: `discover.ts` loads `../sqlite.js`, so importing this one function
|
|
92
|
+
* from there pulls `node:sqlite` into the caller's module graph and Node prints
|
|
93
|
+
* `ExperimentalWarning: SQLite …` on stderr. That is invisible in a command that
|
|
94
|
+
* already touches the session DB, and a broken contract in one that doesn't —
|
|
95
|
+
* `monitors --json` asserts a clean stderr. `discover.ts` re-exports it, so
|
|
96
|
+
* existing importers are unaffected.
|
|
97
|
+
*/
|
|
98
|
+
export function parseTimeFilter(input) {
|
|
99
|
+
// Units: m=minute, h=hour, d=day, w=week, mo=month(30d), y=year(365d). `mo`
|
|
100
|
+
// must precede the single-letter alternatives so "1mo" isn't read as "1m"+"o".
|
|
101
|
+
const relativeMatch = input.match(/^(\d+)(mo|[mhdwy])$/i);
|
|
102
|
+
if (relativeMatch) {
|
|
103
|
+
const value = parseInt(relativeMatch[1], 10);
|
|
104
|
+
const unit = relativeMatch[2].toLowerCase();
|
|
105
|
+
if (unit === 'm')
|
|
106
|
+
return Date.now() - value * 60_000;
|
|
107
|
+
if (unit === 'h')
|
|
108
|
+
return Date.now() - value * 3_600_000;
|
|
109
|
+
if (unit === 'd')
|
|
110
|
+
return Date.now() - value * 86_400_000;
|
|
111
|
+
if (unit === 'w')
|
|
112
|
+
return Date.now() - value * 7 * 86_400_000;
|
|
113
|
+
if (unit === 'mo')
|
|
114
|
+
return Date.now() - value * 30 * 86_400_000;
|
|
115
|
+
if (unit === 'y')
|
|
116
|
+
return Date.now() - value * 365 * 86_400_000;
|
|
117
|
+
}
|
|
118
|
+
const ts = new Date(input).getTime();
|
|
119
|
+
return Number.isNaN(ts) ? 0 : ts;
|
|
120
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical derivation of a session's 8-char `shortId`.
|
|
3
|
+
*
|
|
4
|
+
* Every session parser used to inline `id.slice(0, 8)` — some with a
|
|
5
|
+
* `.replace(prefix, '')` strip first (`session_`, `api-`, `ses_`). That strip is
|
|
6
|
+
* the bug: an id that is *only* its prefix (a bare `session_` directory, an id of
|
|
7
|
+
* exactly `api-`) strips to `''`, and `''.slice(0, 8)` is still `''`. An empty
|
|
8
|
+
* shortId then passes the `short_id TEXT NOT NULL` constraint (empty string is not
|
|
9
|
+
* NULL) yet matches nothing in the `short_id LIKE ?` picker lookups — a silently
|
|
10
|
+
* corrupt index row.
|
|
11
|
+
*
|
|
12
|
+
* This helper is the single source of truth: it strips the optional prefix, takes
|
|
13
|
+
* the first 8 chars, and — the whole point — guarantees a NON-EMPTY result by
|
|
14
|
+
* falling back to the unstripped id when the strip empties it, so a row always
|
|
15
|
+
* stays addressable by its shortId.
|
|
16
|
+
*/
|
|
17
|
+
export declare function deriveShortId(id: string, stripPrefix?: RegExp): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical derivation of a session's 8-char `shortId`.
|
|
3
|
+
*
|
|
4
|
+
* Every session parser used to inline `id.slice(0, 8)` — some with a
|
|
5
|
+
* `.replace(prefix, '')` strip first (`session_`, `api-`, `ses_`). That strip is
|
|
6
|
+
* the bug: an id that is *only* its prefix (a bare `session_` directory, an id of
|
|
7
|
+
* exactly `api-`) strips to `''`, and `''.slice(0, 8)` is still `''`. An empty
|
|
8
|
+
* shortId then passes the `short_id TEXT NOT NULL` constraint (empty string is not
|
|
9
|
+
* NULL) yet matches nothing in the `short_id LIKE ?` picker lookups — a silently
|
|
10
|
+
* corrupt index row.
|
|
11
|
+
*
|
|
12
|
+
* This helper is the single source of truth: it strips the optional prefix, takes
|
|
13
|
+
* the first 8 chars, and — the whole point — guarantees a NON-EMPTY result by
|
|
14
|
+
* falling back to the unstripped id when the strip empties it, so a row always
|
|
15
|
+
* stays addressable by its shortId.
|
|
16
|
+
*/
|
|
17
|
+
export function deriveShortId(id, stripPrefix) {
|
|
18
|
+
const stripped = stripPrefix ? id.replace(stripPrefix, '') : id;
|
|
19
|
+
return stripped.slice(0, 8) || id.slice(0, 8) || id;
|
|
20
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal display-width helpers.
|
|
3
|
+
*
|
|
4
|
+
* `String.length` is the wrong ruler for a terminal: it over-counts ANSI colour
|
|
5
|
+
* escapes (chalk output) and under-counts wide glyphs (CJK, emoji) which occupy
|
|
6
|
+
* two cells. The result is the drifting, wrapping session-table line users see
|
|
7
|
+
* under tmux and over `--device` SSH. Every renderer that sizes a session-table
|
|
8
|
+
* cell measures and truncates through this module so alignment is computed once,
|
|
9
|
+
* correctly, from the same source of truth.
|
|
10
|
+
*/
|
|
11
|
+
/** Strip SGR colour + OSC-8 hyperlink escapes so width is measured on visible characters only. */
|
|
12
|
+
export declare function stripAnsi(s: string): string;
|
|
13
|
+
/** Visible display width of a string, ANSI-aware and wide-char-aware. */
|
|
14
|
+
export declare function stringWidth(s: string): number;
|
|
15
|
+
/**
|
|
16
|
+
* Truncate to a target display width, appending '…' when shortened. Operates on
|
|
17
|
+
* the visible (ANSI-stripped) string; callers colour the result afterwards so
|
|
18
|
+
* the ellipsis is never inserted mid-escape.
|
|
19
|
+
*/
|
|
20
|
+
export declare function truncateToWidth(s: string, max: number): string;
|
|
21
|
+
/** Right-pad with spaces to a target display width. Never truncates. */
|
|
22
|
+
export declare function padToWidth(s: string, width: number): string;
|
|
23
|
+
/**
|
|
24
|
+
* Effective terminal width. Reads `$COLUMNS` first so it survives tmux and
|
|
25
|
+
* `--device` SSH (where `process.stdout.columns` is unset or wrong), falls back to
|
|
26
|
+
* the TTY's reported width, then to `fallback`. Clamped to a sane band so a
|
|
27
|
+
* bogus value can't produce a 0-wide or absurdly long table.
|
|
28
|
+
*/
|
|
29
|
+
export declare function terminalWidth(fallback?: number): number;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal display-width helpers.
|
|
3
|
+
*
|
|
4
|
+
* `String.length` is the wrong ruler for a terminal: it over-counts ANSI colour
|
|
5
|
+
* escapes (chalk output) and under-counts wide glyphs (CJK, emoji) which occupy
|
|
6
|
+
* two cells. The result is the drifting, wrapping session-table line users see
|
|
7
|
+
* under tmux and over `--device` SSH. Every renderer that sizes a session-table
|
|
8
|
+
* cell measures and truncates through this module so alignment is computed once,
|
|
9
|
+
* correctly, from the same source of truth.
|
|
10
|
+
*/
|
|
11
|
+
/** SGR colour sequences emitted by chalk (e.g. `\x1b[32m`). */
|
|
12
|
+
const SGR_REGEX = /\x1b\[[0-9;]*m/g;
|
|
13
|
+
/**
|
|
14
|
+
* OSC-8 hyperlink sequences (`\x1b]8;;<url>\x1b\\` … `\x1b]8;;\x1b\\`, or with a
|
|
15
|
+
* BEL `\x07` terminator) emitted by `osc8()` in render.ts for clickable
|
|
16
|
+
* ticket/PR/path badges. These carry the URL, which is NOT visible — leaving it
|
|
17
|
+
* in over-counts width and, worse, lets a truncation land *inside* the escape and
|
|
18
|
+
* corrupt it. Stripping both the opener and closer leaves only the visible label
|
|
19
|
+
* between them, so width is measured on what the user sees and `truncateToWidth`
|
|
20
|
+
* can never cut a hyperlink in half.
|
|
21
|
+
*/
|
|
22
|
+
const OSC8_REGEX = /\x1b\]8;[^;]*;.*?(?:\x1b\\|\x07)/g;
|
|
23
|
+
/** Strip SGR colour + OSC-8 hyperlink escapes so width is measured on visible characters only. */
|
|
24
|
+
export function stripAnsi(s) {
|
|
25
|
+
return s.replace(OSC8_REGEX, '').replace(SGR_REGEX, '');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Display cells for one code point: 0 for zero-width combining/ZWJ/variation
|
|
29
|
+
* selectors, 2 for East-Asian-wide and emoji ranges, 1 otherwise. Compact and
|
|
30
|
+
* dependency-free — covers the glyphs that actually show up in prompts/titles.
|
|
31
|
+
*/
|
|
32
|
+
function charWidth(cp) {
|
|
33
|
+
if (cp === 0)
|
|
34
|
+
return 0;
|
|
35
|
+
// Zero-width: combining marks, zero-width joiner, variation selectors.
|
|
36
|
+
if ((cp >= 0x0300 && cp <= 0x036f) ||
|
|
37
|
+
cp === 0x200b || cp === 0x200d ||
|
|
38
|
+
(cp >= 0xfe00 && cp <= 0xfe0f))
|
|
39
|
+
return 0;
|
|
40
|
+
// Wide (2 cells): CJK, Hangul, fullwidth forms, emoji & pictographs.
|
|
41
|
+
if ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
|
|
42
|
+
(cp >= 0x2e80 && cp <= 0xa4cf) || // CJK radicals … Yi
|
|
43
|
+
(cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables
|
|
44
|
+
(cp >= 0xf900 && cp <= 0xfaff) || // CJK compatibility ideographs
|
|
45
|
+
(cp >= 0xfe30 && cp <= 0xfe4f) || // CJK compatibility forms
|
|
46
|
+
(cp >= 0xff00 && cp <= 0xff60) || // Fullwidth forms
|
|
47
|
+
(cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth signs
|
|
48
|
+
(cp >= 0x1f300 && cp <= 0x1faff) || // emoji & symbols
|
|
49
|
+
(cp >= 0x20000 && cp <= 0x3fffd) // CJK Ext-B and beyond
|
|
50
|
+
)
|
|
51
|
+
return 2;
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
/** Visible display width of a string, ANSI-aware and wide-char-aware. */
|
|
55
|
+
export function stringWidth(s) {
|
|
56
|
+
const plain = stripAnsi(s);
|
|
57
|
+
let w = 0;
|
|
58
|
+
for (const ch of plain)
|
|
59
|
+
w += charWidth(ch.codePointAt(0));
|
|
60
|
+
return w;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Truncate to a target display width, appending '…' when shortened. Operates on
|
|
64
|
+
* the visible (ANSI-stripped) string; callers colour the result afterwards so
|
|
65
|
+
* the ellipsis is never inserted mid-escape.
|
|
66
|
+
*/
|
|
67
|
+
export function truncateToWidth(s, max) {
|
|
68
|
+
if (max <= 0)
|
|
69
|
+
return '';
|
|
70
|
+
const plain = stripAnsi(s);
|
|
71
|
+
if (stringWidth(plain) <= max)
|
|
72
|
+
return plain;
|
|
73
|
+
let w = 0;
|
|
74
|
+
let out = '';
|
|
75
|
+
for (const ch of plain) {
|
|
76
|
+
const cw = charWidth(ch.codePointAt(0));
|
|
77
|
+
if (w + cw > max - 1)
|
|
78
|
+
break; // reserve one cell for the ellipsis
|
|
79
|
+
out += ch;
|
|
80
|
+
w += cw;
|
|
81
|
+
}
|
|
82
|
+
return out + '…';
|
|
83
|
+
}
|
|
84
|
+
/** Right-pad with spaces to a target display width. Never truncates. */
|
|
85
|
+
export function padToWidth(s, width) {
|
|
86
|
+
const pad = width - stringWidth(s);
|
|
87
|
+
return pad > 0 ? s + ' '.repeat(pad) : s;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Effective terminal width. Reads `$COLUMNS` first so it survives tmux and
|
|
91
|
+
* `--device` SSH (where `process.stdout.columns` is unset or wrong), falls back to
|
|
92
|
+
* the TTY's reported width, then to `fallback`. Clamped to a sane band so a
|
|
93
|
+
* bogus value can't produce a 0-wide or absurdly long table.
|
|
94
|
+
*/
|
|
95
|
+
export function terminalWidth(fallback = 100) {
|
|
96
|
+
const env = Number.parseInt(process.env.COLUMNS ?? '', 10);
|
|
97
|
+
const raw = Number.isFinite(env) && env > 0
|
|
98
|
+
? env
|
|
99
|
+
: (process.stdout.columns || fallback);
|
|
100
|
+
return Math.max(60, Math.min(200, raw));
|
|
101
|
+
}
|
|
@@ -116,6 +116,13 @@ export interface SlackMessageContext {
|
|
|
116
116
|
user: string;
|
|
117
117
|
/** Slash command name (`/agents`), or '' for an event delivery. */
|
|
118
118
|
command: string;
|
|
119
|
+
/**
|
|
120
|
+
* Slash-command reply URL (`https://hooks.slack.com/commands/…`), or '' for an
|
|
121
|
+
* event delivery. Slack accepts a POST here for 30 minutes with no token and no
|
|
122
|
+
* channel membership, so it is the only reply path that works before the app has
|
|
123
|
+
* been invited to a channel.
|
|
124
|
+
*/
|
|
125
|
+
response_url: string;
|
|
119
126
|
}
|
|
120
127
|
/**
|
|
121
128
|
* Split a Slack message into a `{{slack.*}}` context. Strips a leading bot
|
|
@@ -294,6 +294,7 @@ export function parseSlackMessage(payload) {
|
|
|
294
294
|
thread_ts: asString(payload.thread_ts),
|
|
295
295
|
user: asString(payload.user),
|
|
296
296
|
command: asString(payload.command),
|
|
297
|
+
response_url: asString(payload.response_url),
|
|
297
298
|
};
|
|
298
299
|
}
|
|
299
300
|
/**
|
|
@@ -352,7 +353,7 @@ function dispatchDefault(config) {
|
|
|
352
353
|
// Import lazily so the runner module (heavy) is only loaded when a handler
|
|
353
354
|
// actually needs to spawn. Tests inject dispatch functions, so this path is
|
|
354
355
|
// not exercised in unit tests.
|
|
355
|
-
return import('../runner.js').then((m) => m.executeJobDetached(config));
|
|
356
|
+
return import('../daemon/runner.js').then((m) => m.executeJobDetached(config));
|
|
356
357
|
}
|
|
357
358
|
/**
|
|
358
359
|
* Execute a handler's action. Runs the configured agent/workflow/command or
|
|
@@ -414,6 +415,13 @@ async function executeHandlerAction(handler, webhook, context, opts) {
|
|
|
414
415
|
...(substitutedCwd ? { cwd: substitutedCwd } : {}),
|
|
415
416
|
...(hostFields.host ? { host: hostFields.host } : {}),
|
|
416
417
|
...(hostFields.hostStrategy ? { hostStrategy: hostFields.hostStrategy } : {}),
|
|
418
|
+
// A handler is not a routine, so its name can never appear in this device's
|
|
419
|
+
// routine activation manifest — without this marker the gate answered "not
|
|
420
|
+
// activated here" and every delivery was skipped while the receiver logged
|
|
421
|
+
// `fired` (the same shape as RUSH-2681's monitor bug). The `routine:`
|
|
422
|
+
// delegate below deliberately omits it: that fires a REAL routine, which
|
|
423
|
+
// keeps its own activation gate.
|
|
424
|
+
dispatchedBy: 'webhook',
|
|
417
425
|
};
|
|
418
426
|
const dispatch = handler.run.agent
|
|
419
427
|
? (opts.dispatchAgent ?? dispatchDefault)
|
|
@@ -237,6 +237,7 @@ export interface WebhookServerOptions {
|
|
|
237
237
|
* Start a localhost-bound receiver. It accepts only:
|
|
238
238
|
* POST /hooks/github with X-Hub-Signature-256
|
|
239
239
|
* POST /hooks/linear with Linear-Signature + fresh webhookTimestamp
|
|
240
|
+
* POST /hooks/slack with X-Slack-Signature + fresh X-Slack-Request-Timestamp
|
|
240
241
|
*
|
|
241
242
|
* **The ack is asynchronous (RUSH-2548).** Once a delivery has passed signature
|
|
242
243
|
* verification, freshness, dedup, and rate limiting, the receiver writes
|
|
@@ -16,7 +16,7 @@ import * as fs from 'fs';
|
|
|
16
16
|
import * as http from 'http';
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import { jobRunsOnThisDevice, listJobs, substituteWebhookPrompt } from '../scheduling/routines.js';
|
|
19
|
-
import { executeJobDetached } from '../runner.js';
|
|
19
|
+
import { executeJobDetached } from '../daemon/runner.js';
|
|
20
20
|
import { emit } from '../feed/events.js';
|
|
21
21
|
import { listHandlers, handlerMatchesWebhook, executeHandler, buildWebhookContext, } from './handlers.js';
|
|
22
22
|
/** Read `repository.full_name` (`owner/name`) from a webhook payload, if present. */
|
|
@@ -516,6 +516,7 @@ export function waitForListening(server) {
|
|
|
516
516
|
* Start a localhost-bound receiver. It accepts only:
|
|
517
517
|
* POST /hooks/github with X-Hub-Signature-256
|
|
518
518
|
* POST /hooks/linear with Linear-Signature + fresh webhookTimestamp
|
|
519
|
+
* POST /hooks/slack with X-Slack-Signature + fresh X-Slack-Request-Timestamp
|
|
519
520
|
*
|
|
520
521
|
* **The ack is asynchronous (RUSH-2548).** Once a delivery has passed signature
|
|
521
522
|
* verification, freshness, dedup, and rate limiting, the receiver writes
|
|
@@ -689,7 +690,7 @@ export function startWebhookServer(options) {
|
|
|
689
690
|
inFlight.add(slackId);
|
|
690
691
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
691
692
|
res.end(slack.type === 'slash_command'
|
|
692
|
-
? JSON.stringify({ response_type: 'ephemeral', text: 'On it — replying in this
|
|
693
|
+
? JSON.stringify({ response_type: 'ephemeral', text: 'On it — replying in this channel.' })
|
|
693
694
|
: '');
|
|
694
695
|
void settleDelivery(slackWebhook, slackId).finally(() => inFlight.delete(slackId));
|
|
695
696
|
return;
|
package/dist/lib/uninstall.js
CHANGED
|
@@ -19,7 +19,7 @@ import * as fs from 'fs';
|
|
|
19
19
|
import * as os from 'os';
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import { AGENTS, ALL_AGENT_IDS } from './agents.js';
|
|
22
|
-
import { getAgentConfigPath, getConfigSymlinkVersion, stripShimPathLines, releaseAdoptedLauncher, } from './shims.js';
|
|
22
|
+
import { getAgentConfigPath, getConfigSymlinkVersion, stripShimPathLines, releaseAdoptedLauncher, } from './installations/shims.js';
|
|
23
23
|
import { moveDirCrossDevice, copyDirStrippingAgentsSymlinks } from './config-transfer.js';
|
|
24
24
|
import { getUserAgentsDir, getBackupsDir, getHistoryDir, getShimsDir, getLegacySystemAgentsDir, } from './state.js';
|
|
25
25
|
/** Home dir honoring the AGENTS_REAL_HOME test/override, mirroring shims.ts. */
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -43,8 +43,15 @@ export declare function resolveInstalledLayout(): InstallLayout | null;
|
|
|
43
43
|
* binary can't read its own bundled package.json), fall back to the on-disk
|
|
44
44
|
* install found via the launcher symlink so callers like the menu bar don't see
|
|
45
45
|
* a bogus `unknown`.
|
|
46
|
+
*
|
|
47
|
+
* `pkgJsonPath` is an optional override of the shipping package.json path,
|
|
48
|
+
* accepted by both this function and {@link getCliVersionFresh}. Every
|
|
49
|
+
* production call site uses the zero-argument form and gets the real on-disk
|
|
50
|
+
* path below; the parameter exists only so a unit test can point both
|
|
51
|
+
* functions at a fixture package.json instead of the real one shared by every
|
|
52
|
+
* parallel test fork.
|
|
46
53
|
*/
|
|
47
|
-
export declare function getCliVersion(): string;
|
|
54
|
+
export declare function getCliVersion(pkgJsonPath?: string): string;
|
|
48
55
|
/**
|
|
49
56
|
* Read the version from package.json on disk every call, bypassing the cache.
|
|
50
57
|
*
|
|
@@ -55,4 +62,4 @@ export declare function getCliVersion(): string;
|
|
|
55
62
|
* is now stale and should reload onto the new code (self-healing). Returns
|
|
56
63
|
* 'unknown' on any error.
|
|
57
64
|
*/
|
|
58
|
-
export declare function getCliVersionFresh(): string;
|
|
65
|
+
export declare function getCliVersionFresh(pkgJsonPath?: string): string;
|