@phnx-labs/agents-cli 1.22.16 → 1.22.17

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.
@@ -16,7 +16,7 @@
16
16
  import * as fs from 'fs';
17
17
  import * as path from 'path';
18
18
  import { spawnSync } from 'child_process';
19
- import { appendActivityEvent, } from './activity.js';
19
+ import { appendActivityEvent, readRecentActivity, } from './activity.js';
20
20
  import { resolveProjectNameForCwd, listProjectDefs } from './projects.js';
21
21
  import { getHistoryDir } from './state.js';
22
22
  import { machineId } from './machine-id.js';
@@ -30,7 +30,7 @@ export const STATUS_TITLE_MAX_CHARS = 60;
30
30
  * Resolve who is posting. Order:
31
31
  * 1. Explicit --session flag
32
32
  * 2. Env: AGENT_SESSION_ID / AGENTS_SESSION_ID / basename(AGENTS_MAILBOX_DIR)
33
- * 3. Env AGENT_LAUNCH_ID → match in pid registry
33
+ * 3. Env AGENT_LAUNCH_ID → match in pid registry or activity index
34
34
  * 4. Walk parent PIDs from startPid (default process.ppid) through by-pid registry
35
35
  */
36
36
  export function resolvePostIdentity(input) {
@@ -55,8 +55,12 @@ export function resolvePostIdentity(input) {
55
55
  registry = walkPidRegistry(start, getParent, readEntry);
56
56
  }
57
57
  }
58
+ const activity = launchId && !envSession && !registry?.sessionId
59
+ ? readRecentActivity({ root: input.activityRoot, maxBytesPerSession: 64 * 1024 })
60
+ .find((event) => event.launchId === launchId)
61
+ : undefined;
58
62
  // Prefer env session (explicit + managed run), fill gaps from registry.
59
- const sessionId = envSession ?? registry?.sessionId;
63
+ const sessionId = envSession ?? registry?.sessionId ?? activity?.sessionId;
60
64
  if (!sessionId || !isValidMailboxId(sessionId))
61
65
  return undefined;
62
66
  const mailboxFromEnv = mailboxIdFromEnv(env);
@@ -66,17 +70,18 @@ export function resolvePostIdentity(input) {
66
70
  return {
67
71
  sessionId,
68
72
  mailboxId,
69
- host: machineIdFromEnv(env),
70
- runtime: env.AGENTS_RUNTIME?.trim() || 'headless',
73
+ host: activity?.host ?? machineIdFromEnv(env),
74
+ runtime: env.AGENTS_RUNTIME?.trim() || activity?.runtime || 'headless',
71
75
  agent: env.AGENTS_AGENT_NAME?.trim()
72
76
  || registry?.agent
77
+ || activity?.agent
73
78
  || detectAgentKind(env),
74
79
  cwd: input.cwd
75
- ?? (env.AGENTS_CWD?.trim() || registry?.cwd || process.cwd()),
76
- pid: registry?.pid,
77
- launchId: launchId || registry?.launchId,
78
- terminalId: env.AGENT_TERMINAL_ID?.trim() || registry?.terminalId,
79
- tmuxPane: env.TMUX_PANE?.trim() || registry?.tmuxPane,
80
+ ?? (env.AGENTS_CWD?.trim() || registry?.cwd || activity?.cwd || process.cwd()),
81
+ pid: registry?.pid ?? activity?.pid,
82
+ launchId: launchId || registry?.launchId || activity?.launchId,
83
+ terminalId: env.AGENT_TERMINAL_ID?.trim() || registry?.terminalId || activity?.terminalId,
84
+ tmuxPane: env.TMUX_PANE?.trim() || registry?.tmuxPane || activity?.tmuxPane,
80
85
  };
81
86
  }
82
87
  function firstValidId(candidates) {
@@ -118,6 +118,14 @@ export const RUN_OPTION_FORWARDING = {
118
118
  bare: 'local-only', // skips the local setup-copy push; lease-only concern
119
119
  tailscale: 'local-only', // --tailscale/--no-tailscale gate the lease net mode; never forwarded
120
120
  copyCreds: 'local-only', // copies creds TO the host before dispatch — local concern only
121
+ // Cloud placement: chosen and dispatched from THIS machine via the provider
122
+ // registry; mutually exclusive with --host (placement conflict dies before
123
+ // dispatch), so these never have a remote argv to ride.
124
+ cloud: 'local-only',
125
+ provider: 'local-only',
126
+ repo: 'local-only',
127
+ branch: 'local-only',
128
+ cloudEnv: 'local-only',
121
129
  authCheck: 'local-only', // --no-auth-check gates the local interactive login preflight; --host runs skip that preflight entirely
122
130
  // The notification must land on the box the PERSON is at — the one that
123
131
  // dispatched — not on a headless worker with no desktop to post to. The local
@@ -12,8 +12,9 @@ export declare function readHumans(): HumansConfig | null;
12
12
  */
13
13
  export declare function writeHumans(config: HumansConfig): void;
14
14
  /**
15
- * Return the effective owner notification config from humans.yaml if
16
- * available, otherwise return null (caller falls back to agents.yaml).
15
+ * Return the effective owner notification destination from humans.yaml.
16
+ * The owner's normal-severity policy selects the channel; when no normal
17
+ * policy is declared, the first addressable channel is the default.
17
18
  */
18
19
  export declare function getOwnerNotifyFromHumans(): {
19
20
  channel: string;
@@ -5,8 +5,7 @@
5
5
  * Schema version: 1
6
6
  *
7
7
  * This module is the single read/write seam for humans.yaml. All channel/
8
- * notify consumers should read owner config from here (with a fallback to
9
- * the legacy notify.owner in agents.yaml during the migration window).
8
+ * notify consumers should read owner config from here.
10
9
  */
11
10
  import * as fs from 'fs';
12
11
  import * as yaml from 'yaml';
@@ -47,14 +46,23 @@ export function writeHumans(config) {
47
46
  fs.writeFileSync(filePath, HUMANS_HEADER + body, { encoding: 'utf-8', mode: 0o600 });
48
47
  }
49
48
  /**
50
- * Return the effective owner notification config from humans.yaml if
51
- * available, otherwise return null (caller falls back to agents.yaml).
49
+ * Return the effective owner notification destination from humans.yaml.
50
+ * The owner's normal-severity policy selects the channel; when no normal
51
+ * policy is declared, the first addressable channel is the default.
52
52
  */
53
53
  export function getOwnerNotifyFromHumans() {
54
- const humans = readHumans();
55
- const notify = humans?.owner?.notify;
56
- if (notify?.channel && notify?.to)
57
- return notify;
54
+ const owner = readHumans()?.owner;
55
+ const channels = owner?.channels ?? [];
56
+ const preferredIds = owner?.policy?.normal ?? [];
57
+ const preferred = preferredIds
58
+ .map((id) => channels.find((entry) => entry.id === id))
59
+ .find((entry) => entry?.to);
60
+ const selected = preferred ?? channels.find((entry) => entry.to);
61
+ if (selected?.id && selected.to)
62
+ return { channel: selected.id, to: selected.to };
63
+ const migrated = owner?.notify;
64
+ if (migrated?.channel && migrated.to)
65
+ return migrated;
58
66
  return null;
59
67
  }
60
68
  /**
@@ -4,7 +4,7 @@
4
4
  * Every human-facing owner notification (feed urgent-block dispatch, monitor
5
5
  * `notify` action, `agents notify`) funnels through the single channel seam:
6
6
  * `lookupTransport(channel, meta).provider.send(text, opts)`. The recipient comes
7
- * from `notify.owner` in agents.yaml — never a hardcoded chat id — so changing the
7
+ * from `humans.yaml` — never a hardcoded chat id — so changing the
8
8
  * owner is honoured by every path at once. `notify.transports` picks the actual
9
9
  * provider per host (rush telegram on zion, openclaw-telegram on mac-mini).
10
10
  * Best-effort: a delivery failure is returned to the caller, never thrown, so a
@@ -19,9 +19,9 @@ import type { SendResult } from './channels/registry.js';
19
19
  export interface OwnerNotifyOptions {
20
20
  /** Config source (defaults to `readMeta()`); lets callers/tests inject it. */
21
21
  meta?: Meta;
22
- /** Override the owner channel from `notify.owner.channel`. */
22
+ /** Override the owner channel resolved from humans.yaml. */
23
23
  channel?: string;
24
- /** Override the owner target from `notify.owner.to`. */
24
+ /** Override the owner target resolved from humans.yaml. */
25
25
  target?: string;
26
26
  /** Resolve + build the delivery but do not actually send. */
27
27
  dryRun?: boolean;
@@ -44,7 +44,7 @@ export declare function buildOpenClawNotifyArgs(text: string, opts: {
44
44
  }): string[];
45
45
  /**
46
46
  * Deliver a message to the configured owner through the one channel seam.
47
- * `channel`/`target` default to `notify.owner.{channel,to}`; `notify.transports`
47
+ * `channel`/`target` default to the normal owner channel in humans.yaml; `notify.transports`
48
48
  * selects the provider per host. A missing owner config or a delivery failure
49
49
  * (e.g. openclaw not on PATH) returns a clean `SendResult` error — never a raw
50
50
  * ENOENT — so callers surface a consistent, best-effort failure.
@@ -34,17 +34,14 @@ export function buildOpenClawNotifyArgs(text, opts) {
34
34
  }
35
35
  /**
36
36
  * Deliver a message to the configured owner through the one channel seam.
37
- * `channel`/`target` default to `notify.owner.{channel,to}`; `notify.transports`
37
+ * `channel`/`target` default to the normal owner channel in humans.yaml; `notify.transports`
38
38
  * selects the provider per host. A missing owner config or a delivery failure
39
39
  * (e.g. openclaw not on PATH) returns a clean `SendResult` error — never a raw
40
40
  * ENOENT — so callers surface a consistent, best-effort failure.
41
41
  */
42
42
  export async function sendToOwner(text, options = {}) {
43
43
  const meta = options.meta ?? readMeta();
44
- // humans.yaml is the primary source; agents.yaml notify.owner is the fallback.
45
- const humansOwner = getOwnerNotifyFromHumans();
46
- const legacyOwner = meta.notify?.owner;
47
- const owner = humansOwner ?? legacyOwner;
44
+ const owner = getOwnerNotifyFromHumans() ?? meta.notify?.owner;
48
45
  const channel = options.channel ?? owner?.channel;
49
46
  const target = options.target ?? owner?.to;
50
47
  if (!channel || !target) {
@@ -52,7 +49,7 @@ export async function sendToOwner(text, options = {}) {
52
49
  ok: false,
53
50
  channel: channel ?? 'unknown',
54
51
  id: target ?? '',
55
- error: 'notify.owner.{channel,to} not set in humans.yaml or agents.yaml',
52
+ error: 'No addressable owner channel configured in humans.yaml or legacy notify.owner',
56
53
  };
57
54
  }
58
55
  registerBuiltinProviders();
@@ -2,7 +2,7 @@
2
2
  * Placement — one model for "where does the body run?"
3
3
  *
4
4
  * The CLI grew several doors that all mean execution target:
5
- * run --host / --device / --lease / --box
5
+ * run --host / --device / --lease / --box / --cloud
6
6
  * routines --placement / --run-on / hostStrategy
7
7
  * monitors --run-on (body) vs --device (owner — NOT placement)
8
8
  * teams --device (teammate pin)
@@ -41,6 +41,10 @@ export interface RunPlacementFlags {
41
41
  computer?: string;
42
42
  lease?: string | boolean;
43
43
  box?: string;
44
+ /** --cloud: vendor cloud placement (the agent's native cloud provider). */
45
+ cloud?: boolean;
46
+ /** --provider: refines the cloud placement; not a placement on its own. */
47
+ provider?: string;
44
48
  }
45
49
  export declare class PlacementError extends Error {
46
50
  constructor(message: string);
@@ -68,10 +72,10 @@ export declare function placementFromRunFlags(flags: RunPlacementFlags): Placeme
68
72
  * Expand a resolved placement into the concrete run option fields the
69
73
  * existing dispatch paths already understand. Pure — does not mutate input.
70
74
  *
71
- * `cloud` and `fleet` are not valid for a bare `agents run` (use `cloud run`
72
- * / routines); they throw so callers fail loud.
75
+ * `fleet` is not valid for a bare `agents run` (it is a routines placement);
76
+ * it throws so callers fail loud.
73
77
  */
74
- export declare function expandPlacementToRunFlags(placement: Placement): Pick<RunPlacementFlags, 'host' | 'device' | 'lease' | 'box'>;
78
+ export declare function expandPlacementToRunFlags(placement: Placement): Pick<RunPlacementFlags, 'host' | 'device' | 'lease' | 'box' | 'cloud' | 'provider'>;
75
79
  /** Map routines hostStrategy (+ optional host) onto the shared Placement. */
76
80
  export declare function placementFromHostStrategy(strategy: 'local' | 'host' | 'fleet' | 'cloud', host?: string): Placement;
77
81
  /** One-line human form for logs / help. */
@@ -2,7 +2,7 @@
2
2
  * Placement — one model for "where does the body run?"
3
3
  *
4
4
  * The CLI grew several doors that all mean execution target:
5
- * run --host / --device / --lease / --box
5
+ * run --host / --device / --lease / --box / --cloud
6
6
  * routines --placement / --run-on / hostStrategy
7
7
  * monitors --run-on (body) vs --device (owner — NOT placement)
8
8
  * teams --device (teammate pin)
@@ -93,6 +93,7 @@ export function placementFromRunFlags(flags) {
93
93
  const hostT = hostFamilyTarget(flags);
94
94
  const hasLease = flags.lease !== undefined && flags.lease !== false;
95
95
  const hasBox = !!flags.box;
96
+ const hasCloud = flags.cloud === true;
96
97
  const placementFlags = [];
97
98
  if (where)
98
99
  placementFlags.push('--where');
@@ -102,12 +103,16 @@ export function placementFromRunFlags(flags) {
102
103
  placementFlags.push('--lease');
103
104
  if (hasBox)
104
105
  placementFlags.push('--box');
106
+ if (hasCloud)
107
+ placementFlags.push('--cloud');
105
108
  if (placementFlags.length > 1) {
106
109
  throw new PlacementError(`Conflicting placement flags: ${placementFlags.join(' + ')}. ` +
107
- `Use one door — prefer --where (device:<name> | auto | lease | local).`);
110
+ `Use one door — prefer --where (device:<name> | auto | lease | cloud | local).`);
108
111
  }
109
112
  if (where)
110
113
  return parseWhereSpec(where, '--where');
114
+ if (hasCloud)
115
+ return { kind: 'cloud', target: flags.provider, source: '--cloud' };
111
116
  if (hasBox)
112
117
  return { kind: 'lease', target: flags.box, source: '--box' };
113
118
  if (hasLease) {
@@ -122,8 +127,8 @@ export function placementFromRunFlags(flags) {
122
127
  * Expand a resolved placement into the concrete run option fields the
123
128
  * existing dispatch paths already understand. Pure — does not mutate input.
124
129
  *
125
- * `cloud` and `fleet` are not valid for a bare `agents run` (use `cloud run`
126
- * / routines); they throw so callers fail loud.
130
+ * `fleet` is not valid for a bare `agents run` (it is a routines placement);
131
+ * it throws so callers fail loud.
127
132
  */
128
133
  export function expandPlacementToRunFlags(placement) {
129
134
  switch (placement.kind) {
@@ -140,12 +145,13 @@ export function expandPlacementToRunFlags(placement) {
140
145
  if (placement.source === '--box')
141
146
  return { box: placement.target };
142
147
  return placement.target ? { lease: placement.target } : { lease: true };
148
+ case 'cloud':
149
+ // Vendor cloud placement — `--where cloud[:provider]` expands to the
150
+ // --cloud flag (+ --provider refinement) the run action dispatches on.
151
+ return placement.target ? { cloud: true, provider: placement.target } : { cloud: true };
143
152
  case 'fleet':
144
153
  throw new PlacementError(`fleet placement is for routines (agents routines add … --placement fleet), not agents run. ` +
145
154
  `Use --where device:auto for an affinity pick, or --where device:<name>.`);
146
- case 'cloud':
147
- throw new PlacementError(`cloud placement is agents cloud run (vendor cloud), not agents run. ` +
148
- `For a disposable box use --where lease; for your fleet use --where device:<name>.`);
149
155
  }
150
156
  }
151
157
  /** Map routines hostStrategy (+ optional host) onto the shared Placement. */
@@ -180,9 +186,9 @@ export const PLACEMENT_MATRIX = `
180
186
  Affinity pick (14d usage) --where auto (= --device auto)
181
187
  Disposable cloud box --where lease (= --lease)
182
188
  Reuse warm crabbox --box <slug>
189
+ Vendor cloud task --cloud (= --where cloud[:provider])
183
190
  Routines: body on one box --run-on <name> / --placement host
184
191
  Routines: pick any online --placement fleet
185
- Vendor cloud task agents cloud run …
186
192
  Monitors: who evaluates --device <owner> (NOT body placement)
187
193
  Monitors: where action runs --run-on <host>
188
194
  `.trim();
@@ -30,7 +30,13 @@ const SETTINGS_MANIFEST = {
30
30
  strategy: 'toml-merge',
31
31
  stateKeys: ['notice', 'windows_wsl_setup_acknowledged'],
32
32
  },
33
- { rel: '.codex/auth.json', strategy: 'copy-if-absent', restrictMode: true },
33
+ // `.codex/auth.json` is deliberately NOT carried forward. Copying it seeded
34
+ // every new Codex version with the current default's ChatGPT token, so two
35
+ // installed versions always reported the same account and could never sign
36
+ // into separate accounts. Claude omits its credential (`.claude.json`) for
37
+ // the same reason — a version home holds its own login, keeping accounts
38
+ // per-version. A fresh Codex version installs signed-out; run `codex login`
39
+ // (or `agents run codex --version <v>`) inside it to authenticate.
34
40
  { rel: '.codex/instructions.md', strategy: 'copy-if-absent' },
35
41
  { rel: '.codex/hooks.json', strategy: 'copy-if-absent' },
36
42
  { rel: '.codex/prompts', strategy: 'dir-entries' },
@@ -1008,6 +1008,8 @@ export interface HumanChannel {
1008
1008
  id: string;
1009
1009
  /** Provider transport (e.g. "rush", "twilio"). */
1010
1010
  transport: string;
1011
+ /** Provider-specific recipient (phone number, user id, address). */
1012
+ to?: string;
1011
1013
  /** If true the channel is watched for incoming messages. */
1012
1014
  watch?: boolean;
1013
1015
  /** Shell command to invoke (for call channels). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.16",
3
+ "version": "1.22.17",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",