@phnx-labs/agents-cli 1.22.16 → 1.22.18

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +10 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/cloud.d.ts +0 -1
  5. package/dist/commands/cloud.js +19 -185
  6. package/dist/commands/doctor.js +16 -14
  7. package/dist/commands/exec.js +57 -5
  8. package/dist/commands/feed.d.ts +1 -0
  9. package/dist/commands/feed.js +36 -32
  10. package/dist/commands/run-cloud.d.ts +26 -0
  11. package/dist/commands/run-cloud.js +162 -0
  12. package/dist/commands/versions.js +4 -2
  13. package/dist/commands/view.js +1 -54
  14. package/dist/lib/agents.js +4 -2
  15. package/dist/lib/channels/providers/rush.d.ts +2 -0
  16. package/dist/lib/channels/providers/rush.js +20 -5
  17. package/dist/lib/channels/registry.d.ts +2 -0
  18. package/dist/lib/channels/send.d.ts +4 -3
  19. package/dist/lib/channels/send.js +15 -18
  20. package/dist/lib/cloud/dispatch.d.ts +27 -0
  21. package/dist/lib/cloud/dispatch.js +214 -0
  22. package/dist/lib/feed-post.d.ts +2 -2
  23. package/dist/lib/feed-post.js +15 -10
  24. package/dist/lib/hosts/remote-cmd.js +8 -0
  25. package/dist/lib/humans.d.ts +3 -2
  26. package/dist/lib/humans.js +16 -8
  27. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  28. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  29. package/dist/lib/notify.d.ts +4 -4
  30. package/dist/lib/notify.js +8 -7
  31. package/dist/lib/placement.d.ts +8 -4
  32. package/dist/lib/placement.js +14 -8
  33. package/dist/lib/resources.js +124 -0
  34. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  35. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  36. package/dist/lib/session/active.js +7 -3
  37. package/dist/lib/settings-manifest.js +7 -1
  38. package/dist/lib/types.d.ts +2 -0
  39. package/package.json +1 -1
@@ -0,0 +1,26 @@
1
+ import type { Command } from 'commander';
2
+ import type { CloudProvider } from '../lib/cloud/types.js';
3
+ /** Error type for run --cloud validation failures (exit 1, red message). */
4
+ export declare class RunCloudError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ /** Flags the user passed that cannot ride a cloud placement. */
8
+ export declare function runCloudConflicts(options: Record<string, unknown>): string[];
9
+ /** Cloud-only flags passed without the --cloud placement. */
10
+ export declare function cloudFlagsWithoutCloud(options: Record<string, unknown>): string[];
11
+ /** Agents that route to a native cloud provider (the registry's truth). */
12
+ export declare function cloudCapableAgentIds(): string[];
13
+ /**
14
+ * Resolve the cloud provider for a run: explicit --provider wins, else the
15
+ * agent's native cloud. An agent with no native cloud fails loud with the
16
+ * capable list — never a silent ride onto the configured default.
17
+ */
18
+ export declare function resolveRunCloudProvider(agentId: string, explicitProvider?: string): CloudProvider;
19
+ /** Validate the agent half of `agents run <agent> --cloud` and return the registry id. */
20
+ export declare function resolveRunCloudAgent(agentSpec: string): string;
21
+ /**
22
+ * Handle `agents run <agent> [prompt] --cloud`. Validation lives in the
23
+ * exported helpers above (unit-tested); this wires them to the shared
24
+ * dispatch core and dies on any validation failure.
25
+ */
26
+ export declare function handleRunCloud(agentSpec: string, prompt: string | undefined, options: Record<string, unknown>, command: Command): Promise<void>;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `agents run <agent> [prompt] --cloud` — the vendor-cloud placement for a run.
3
+ *
4
+ * One of three run placements: local (default), machine (--host/--device,
5
+ * --lease), cloud (--cloud). Routing goes through the cloud provider registry
6
+ * exactly like `agents cloud run --agent <agent>`: the agent's native
7
+ * `cloudProvider` wins, `--provider` overrides. The dispatch itself is the
8
+ * shared core in lib/cloud/dispatch.ts — this module only validates the run
9
+ * surface and translates run flags into a DispatchOptions.
10
+ */
11
+ import chalk from 'chalk';
12
+ import { AGENTS, resolveAgentName, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
13
+ import { RUN_AUTO_KEYWORD } from '../lib/types.js';
14
+ import { resolveProvider, nativeProviderForAgent } from '../lib/cloud/registry.js';
15
+ import { resolveCloudPrompt, executeCloudDispatch } from '../lib/cloud/dispatch.js';
16
+ /** Error type for run --cloud validation failures (exit 1, red message). */
17
+ export class RunCloudError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'RunCloudError';
21
+ }
22
+ }
23
+ /**
24
+ * Run flags that are meaningless on a cloud placement. Each entry maps the
25
+ * option field to the flag the user typed. Cloud tasks run in the provider's
26
+ * workspace on the provider's accounts, so local-run knobs (account strategy,
27
+ * secrets injection, loop guards, resume, terminal handoff, cwd) have nothing
28
+ * to act on — passing one is an error, never a silent ignore.
29
+ */
30
+ const RUN_CLOUD_CONFLICTS = [
31
+ { field: 'terminal', flag: '--terminal', set: (v) => v !== undefined && v !== false },
32
+ { field: 'interactive', flag: '--interactive', set: (v) => v === true },
33
+ { field: 'acp', flag: '--acp', set: (v) => v === true },
34
+ { field: 'loop', flag: '--loop', set: (v) => v === true },
35
+ { field: 'resumeCheckpoint', flag: '--resume-checkpoint', set: (v) => v !== undefined },
36
+ { field: 'maxIterations', flag: '--max-iterations', set: (v) => v !== undefined },
37
+ { field: 'budget', flag: '--budget', set: (v) => v !== undefined },
38
+ { field: 'until', flag: '--until', set: (v) => v !== undefined },
39
+ { field: 'interval', flag: '--interval', set: (v) => v !== undefined },
40
+ { field: 'resume', flag: '--resume', set: (v) => v !== undefined && v !== false },
41
+ { field: 'sessionId', flag: '--session-id', set: (v) => v !== undefined },
42
+ { field: 'secrets', flag: '--secrets', set: (v) => Array.isArray(v) && v.length > 0 },
43
+ { field: 'secretsKeys', flag: '--secrets-keys', set: (v) => v !== undefined },
44
+ { field: 'allowExpired', flag: '--allow-expired', set: (v) => v === true },
45
+ { field: 'autoSecrets', flag: '--no-auto-secrets', set: (v) => v === false },
46
+ { field: 'copyCreds', flag: '--copy-creds', set: (v) => v === true },
47
+ { field: 'fallback', flag: '--fallback', set: (v) => v !== undefined },
48
+ { field: 'strategy', flag: '--strategy', set: (v) => v !== undefined },
49
+ { field: 'balanced', flag: '--balanced', set: (v) => v === true },
50
+ { field: 'cwd', flag: '--cwd', set: (v) => v !== undefined },
51
+ { field: 'project', flag: '--project', set: (v) => v !== undefined },
52
+ { field: 'addDir', flag: '--add-dir', set: (v) => Array.isArray(v) && v.length > 0 },
53
+ { field: 'remoteCwd', flag: '--remote-cwd', set: (v) => v !== undefined },
54
+ { field: 'env', flag: '--env', set: (v) => Array.isArray(v) && v.length > 0 },
55
+ { field: 'notify', flag: '--notify', set: (v) => v === true },
56
+ // effort defaults to 'auto' on run; only an explicit non-default value is a
57
+ // conflict (no provider consumes an effort option — it would be dropped).
58
+ { field: 'effort', flag: '--effort', set: (v) => v !== undefined && v !== 'auto' },
59
+ // --name seeds the LOCAL session label; a cloud task has no local session.
60
+ { field: 'name', flag: '--name', set: (v) => v !== undefined },
61
+ ];
62
+ /** Flags that refine a cloud dispatch; meaningless without the placement. */
63
+ const CLOUD_ONLY_FLAGS = [
64
+ { field: 'provider', flag: '--provider', set: (v) => v !== undefined },
65
+ { field: 'repo', flag: '--repo', set: (v) => Array.isArray(v) && v.length > 0 },
66
+ { field: 'branch', flag: '--branch', set: (v) => v !== undefined },
67
+ { field: 'cloudEnv', flag: '--cloud-env', set: (v) => v !== undefined },
68
+ ];
69
+ /** Flags the user passed that cannot ride a cloud placement. */
70
+ export function runCloudConflicts(options) {
71
+ return RUN_CLOUD_CONFLICTS.filter((c) => c.set(options[c.field])).map((c) => c.flag);
72
+ }
73
+ /** Cloud-only flags passed without the --cloud placement. */
74
+ export function cloudFlagsWithoutCloud(options) {
75
+ return CLOUD_ONLY_FLAGS.filter((c) => c.set(options[c.field])).map((c) => c.flag);
76
+ }
77
+ /** Agents that route to a native cloud provider (the registry's truth). */
78
+ export function cloudCapableAgentIds() {
79
+ return Object.values(AGENTS)
80
+ .filter((a) => a.cloudProvider)
81
+ .map((a) => a.id)
82
+ .sort();
83
+ }
84
+ /**
85
+ * Resolve the cloud provider for a run: explicit --provider wins, else the
86
+ * agent's native cloud. An agent with no native cloud fails loud with the
87
+ * capable list — never a silent ride onto the configured default.
88
+ */
89
+ export function resolveRunCloudProvider(agentId, explicitProvider) {
90
+ if (explicitProvider)
91
+ return resolveProvider(explicitProvider);
92
+ if (!nativeProviderForAgent(agentId)) {
93
+ throw new RunCloudError(`${agentId} has no native cloud. Cloud-capable agents: ${cloudCapableAgentIds().join(', ')}. ` +
94
+ `Override with --provider <id> (rush | codex | factory | antigravity | host).`);
95
+ }
96
+ return resolveProvider(undefined, agentId);
97
+ }
98
+ /** Validate the agent half of `agents run <agent> --cloud` and return the registry id. */
99
+ export function resolveRunCloudAgent(agentSpec) {
100
+ if (agentSpec === RUN_AUTO_KEYWORD || agentSpec.startsWith(`${RUN_AUTO_KEYWORD}@`)) {
101
+ throw new RunCloudError(`agents run auto --cloud: auto harness-pick is a local-run feature. ` +
102
+ `Name a cloud-capable agent: ${cloudCapableAgentIds().join(', ')}.`);
103
+ }
104
+ if (agentSpec.includes('@')) {
105
+ throw new RunCloudError(`Version pins (<agent>@<version>) do not apply to --cloud — the provider runs its own agent version. ` +
106
+ `Drop the pin: agents run ${agentSpec.split('@')[0]} "<task>" --cloud.`);
107
+ }
108
+ const agentId = resolveAgentName(agentSpec);
109
+ if (!agentId) {
110
+ throw new RunCloudError(`Unknown agent: ${agentSpec}. Cloud-capable agents: ${cloudCapableAgentIds().join(', ')}.`);
111
+ }
112
+ if (isAgentHardDeprecated(agentId)) {
113
+ throw new RunCloudError(hardDeprecationError(agentId));
114
+ }
115
+ return agentId;
116
+ }
117
+ /**
118
+ * Handle `agents run <agent> [prompt] --cloud`. Validation lives in the
119
+ * exported helpers above (unit-tested); this wires them to the shared
120
+ * dispatch core and dies on any validation failure.
121
+ */
122
+ export async function handleRunCloud(agentSpec, prompt, options, command) {
123
+ const json = options.json === true;
124
+ try {
125
+ const agentId = resolveRunCloudAgent(agentSpec);
126
+ const provider = resolveRunCloudProvider(agentId, options.provider);
127
+ const resolvedPrompt = resolveCloudPrompt(prompt, {
128
+ json,
129
+ hint: `agents run ${agentId} "<task>" --cloud${agentId === 'claude' ? ' --repo <owner/repo>' : ''}`,
130
+ });
131
+ const repoValues = Array.isArray(options.repo) ? options.repo : [];
132
+ const dispatchOptions = {
133
+ prompt: resolvedPrompt,
134
+ agent: agentId,
135
+ repo: repoValues[0],
136
+ repos: repoValues.length > 0 ? repoValues : undefined,
137
+ branch: options.branch,
138
+ timeout: options.timeout,
139
+ model: options.model,
140
+ providerOptions: {},
141
+ };
142
+ if (options.cloudEnv)
143
+ dispatchOptions.providerOptions.env = options.cloudEnv;
144
+ // --mode defaults to 'plan' on run; forward it only when the user set it.
145
+ if (command.getOptionValueSource('mode') === 'cli') {
146
+ dispatchOptions.providerOptions.mode = options.mode;
147
+ }
148
+ await executeCloudDispatch({
149
+ provider,
150
+ dispatchOptions,
151
+ follow: options.follow !== false,
152
+ json,
153
+ });
154
+ }
155
+ catch (err) {
156
+ if (err instanceof RunCloudError) {
157
+ console.error(chalk.red(err.message));
158
+ process.exit(1);
159
+ }
160
+ throw err;
161
+ }
162
+ }
@@ -438,8 +438,10 @@ export function registerVersionsCommands(program) {
438
438
  console.log(chalk.gray(` Created shim: ${getShimsDir()}/${agentConfig.cliCommand}`));
439
439
  }
440
440
  // Seed the fresh version home with user settings from the current
441
- // default version (settings.json, keybindings, codex config/auth).
442
- // Gap-filling only never overwrites what the new home has.
441
+ // default version (settings.json, keybindings, codex config).
442
+ // Credentials are deliberately excluded so each version keeps its own
443
+ // login (see SETTINGS_MANIFEST). Gap-filling only — never overwrites
444
+ // what the new home has.
443
445
  const carrySource = getGlobalDefault(agent);
444
446
  if (carrySource && carrySource !== installedVersion) {
445
447
  const carried = carryForwardSettings(agent, getVersionHomePath(agent, carrySource), getVersionHomePath(agent, installedVersion));
@@ -10,8 +10,7 @@ import { machineId } from '../lib/machine-id.js';
10
10
  import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth-health.js';
11
11
  import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
12
12
  import { readManifest } from '../lib/manifest.js';
13
- import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
14
- import { formatKeptProjectResources } from '../lib/project-resources.js';
13
+ import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
15
14
  import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
16
15
  import { getAgentResources } from '../lib/resources.js';
17
16
  import { renderMergedResources } from '../lib/merged-resources.js';
@@ -29,7 +28,6 @@ import { resolveConfiguredModel } from '../lib/models.js';
29
28
  import { listProfiles, profileExists, profileSummary, readProfile } from '../lib/profiles.js';
30
29
  import { getByokUsageForHarness, hasByokProvider, renderByokBar } from '../lib/byok-usage.js';
31
30
  import { renderHarnessDetail } from './harness.js';
32
- import { loadManifest, isStale } from '../lib/staleness/index.js';
33
31
  import { confirm } from '@inquirer/prompts';
34
32
  import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
35
33
  import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
@@ -781,57 +779,6 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
781
779
  if (!filterAgentId) {
782
780
  renderHostClisSection(process.cwd());
783
781
  }
784
- // Check for new resources when viewing a specific agent
785
- if (filterAgentId && versionManaged.length > 0) {
786
- const defaultVersion = getGlobalDefault(filterAgentId);
787
- if (defaultVersion) {
788
- const manifest = loadManifest(filterAgentId, defaultVersion);
789
- const cwd = process.cwd();
790
- if (manifest && !isStale(manifest, filterAgentId, defaultVersion, cwd)) {
791
- return;
792
- }
793
- const available = getAvailableResources();
794
- const synced = getActuallySyncedResources(filterAgentId, defaultVersion);
795
- const projectOnly = getProjectOnlyResources();
796
- const newResources = getNewResources(available, synced, projectOnly);
797
- if (hasNewResources(newResources, filterAgentId, defaultVersion)) {
798
- try {
799
- const selection = await promptNewResourceSelection(filterAgentId, newResources, defaultVersion);
800
- if (selection && Object.keys(selection).length > 0) {
801
- const result = syncResourcesToVersion(filterAgentId, defaultVersion, selection);
802
- const synced = [];
803
- if (result.commands)
804
- synced.push('commands');
805
- if (result.skills)
806
- synced.push('skills');
807
- if (result.hooks)
808
- synced.push('hooks');
809
- if (result.memory.length > 0)
810
- synced.push('memory');
811
- if (result.permissions)
812
- synced.push('permissions');
813
- if (result.mcp.length > 0)
814
- synced.push('mcp');
815
- if (result.plugins.length > 0)
816
- synced.push('plugins');
817
- if (result.workflows.length > 0)
818
- synced.push('workflows');
819
- if (synced.length > 0) {
820
- console.log(chalk.green(`\nSynced to ${agentLabel(filterAgentId)}@${defaultVersion}: ${synced.join(', ')}`));
821
- }
822
- const kept = formatKeptProjectResources(result.projectSkipped);
823
- if (kept)
824
- console.log(chalk.gray(kept));
825
- }
826
- }
827
- catch (err) {
828
- if (isPromptCancelled(err))
829
- return;
830
- throw err;
831
- }
832
- }
833
- }
834
- }
835
782
  }
836
783
  /**
837
784
  * Show detailed resources for a specific agent version.
@@ -212,8 +212,10 @@ export const AGENTS = {
212
212
  format: 'markdown',
213
213
  variableSyntax: '$ARGUMENTS',
214
214
  supportsHooks: true,
215
- // Claude Code has no headless Anthropic-hosted dispatch CLI (only
216
- // --remote-control, which bridges a *local* session). Its cloud is Rush.
215
+ // Claude Code grew a native `claude --cloud "<prompt>"` (Anthropic-managed
216
+ // infra, claude.ai/code; requires claude.ai subscription auth). Routing
217
+ // still goes to Rush Cloud deliberately — it keeps cloud tasks in one
218
+ // tracked fleet (agents cloud list/status/logs) regardless of harness.
217
219
  cloudProvider: 'rush',
218
220
  capabilities: { hooks: true, mcp: true, mcpHttp: true, mcpHeaders: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'CLAUDE.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true, interactiveRepl: true },
219
221
  },
@@ -3,4 +3,6 @@ export type RushChannel = 'telegram' | 'imessage' | 'slack' | 'discord';
3
3
  export declare const RUSH_CHANNELS: RushChannel[];
4
4
  /** Build the `rush send` argv (exported for tests). */
5
5
  export declare function buildRushSendArgs(channel: RushChannel, text: string, opts: SendOptions): string[];
6
+ /** Build the owner-scoped iMessage argv. */
7
+ export declare function buildRushOwnerMessageArgs(text: string): string[];
6
8
  export declare const rushProviders: ChannelProvider[];
@@ -1,10 +1,9 @@
1
1
  /**
2
- * Rush-daemon channel providers — telegram / imessage / slack / discord.
2
+ * Rush channel providers — telegram / imessage / slack / discord.
3
3
  *
4
- * These shell out to the already-built `rush send` CLI, which routes through the
5
- * rush daemon's live channel gateways over ~/.rush/daemon.sock. We do NOT import
6
- * rush's Go internals (different repo, internal package) the CLI boundary is
7
- * the contract. `rush send --json` prints {"ok":true,"channel":..,"id":..}.
4
+ * Addressable channels use `rush send`, which routes through the daemon's live
5
+ * gateways. Owner-scoped iMessage uses `rush message send`; it is backed by the
6
+ * verified Rush owner account and does not require a daemon channel registration.
8
7
  */
9
8
  import { execFile } from 'child_process';
10
9
  import { promisify } from 'util';
@@ -19,6 +18,10 @@ export function buildRushSendArgs(channel, text, opts) {
19
18
  args.push('--attachment', a);
20
19
  return args;
21
20
  }
21
+ /** Build the owner-scoped iMessage argv. */
22
+ export function buildRushOwnerMessageArgs(text) {
23
+ return ['message', 'send', '--text', text];
24
+ }
22
25
  function rushProvider(channel) {
23
26
  return {
24
27
  name: channel,
@@ -34,6 +37,18 @@ function rushProvider(channel) {
34
37
  return { ok: false, channel, id: opts.target, error: 'rush CLI not found on PATH' };
35
38
  }
36
39
  try {
40
+ if (channel === 'imessage' && opts.ownerScoped) {
41
+ if ((opts.attachments?.length ?? 0) > 0) {
42
+ return {
43
+ ok: false,
44
+ channel,
45
+ id: opts.target,
46
+ error: 'owner-scoped iMessage does not support attachments',
47
+ };
48
+ }
49
+ await execFileAsync('rush', buildRushOwnerMessageArgs(text));
50
+ return { ok: true, channel, id: opts.target };
51
+ }
37
52
  const { stdout } = await execFileAsync('rush', buildRushSendArgs(channel, text, opts));
38
53
  const parsed = JSON.parse(stdout);
39
54
  return {
@@ -17,6 +17,8 @@ export interface SendOptions {
17
17
  attachments?: string[];
18
18
  /** Sender label (used by the mailbox provider). */
19
19
  from?: string;
20
+ /** Destination was resolved through the verified owner alias. */
21
+ ownerScoped?: boolean;
20
22
  /** Resolve + build the delivery but do not actually send. */
21
23
  dryRun?: boolean;
22
24
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * One primitive: resolve a destination (channel + target), compose text + urls +
5
5
  * attachments, hand off to a channel provider. `notify` is the same path with
6
- * destination defaulted to `notify.owner` in agents.yaml — owner is an address
6
+ * destination defaulted from `humans.yaml` — owner is an address
7
7
  * alias (`--to owner`), not a separate stack.
8
8
  *
9
9
  * Agent control (`agents message`, `sessions inject`) stays outside this module.
@@ -18,6 +18,7 @@ export interface SendEnvelope {
18
18
  thread?: string;
19
19
  attachments?: string[];
20
20
  from?: string;
21
+ ownerScoped?: boolean;
21
22
  dryRun?: boolean;
22
23
  }
23
24
  export interface ResolveSendInput {
@@ -60,8 +61,8 @@ export declare function isOwnerAlias(to: string | undefined): boolean;
60
61
  /** Compose body + optional URL lines (skip urls already present in the body). */
61
62
  export declare function composeSendText(text: string, urls?: string[]): string;
62
63
  /**
63
- * Read the owner destination; humans.yaml is the primary source, agents.yaml
64
- * notify.owner is the fallback. Returns null when neither is set.
64
+ * Read the owner destination. humans.yaml is canonical; notify.owner remains a
65
+ * migration fallback for installations that have not run the schema migration.
65
66
  */
66
67
  export declare function readOwnerDest(meta: Meta): {
67
68
  channel: string;
@@ -18,19 +18,16 @@ export function composeSendText(text, urls) {
18
18
  return body ? `${body}\n${extra.join('\n')}` : extra.join('\n');
19
19
  }
20
20
  /**
21
- * Read the owner destination; humans.yaml is the primary source, agents.yaml
22
- * notify.owner is the fallback. Returns null when neither is set.
21
+ * Read the owner destination. humans.yaml is canonical; notify.owner remains a
22
+ * migration fallback for installations that have not run the schema migration.
23
23
  */
24
24
  export function readOwnerDest(meta) {
25
- const humansOwner = getOwnerNotifyFromHumans();
26
- if (humansOwner)
27
- return humansOwner;
28
- const owner = meta.notify?.owner;
29
- const channel = owner?.channel?.trim();
30
- const to = owner?.to?.trim();
31
- if (!channel || !to)
32
- return null;
33
- return { channel, to };
25
+ const canonical = getOwnerNotifyFromHumans();
26
+ if (canonical)
27
+ return canonical;
28
+ const channel = meta.notify?.owner?.channel?.trim();
29
+ const to = meta.notify?.owner?.to?.trim();
30
+ return channel && to ? { channel, to } : null;
34
31
  }
35
32
  /**
36
33
  * Resolve CLI/config into a send envelope. Pure except for reading `meta` —
@@ -55,16 +52,14 @@ export function resolveSendEnvelope(input, meta) {
55
52
  };
56
53
  }
57
54
  // Owner defaults fill only missing fields (and expand the bare "owner" alias).
58
- // humans.yaml is the primary source; notify.owner in agents.yaml is the
59
- // fallback for the migration window. Explicit --channel/--to always win.
55
+ // humans.yaml is the canonical source. Explicit --channel/--to always win.
60
56
  let channel = (input.channel ?? '').trim();
61
57
  let to = (input.to ?? '').trim();
62
58
  const usedOwnerAlias = isOwnerAlias(to);
63
59
  if (input.ownerMode || usedOwnerAlias) {
64
- const humansOwner = getOwnerNotifyFromHumans();
65
- const fallbackOwner = meta.notify?.owner;
66
- const ownerChannel = humansOwner?.channel ?? fallbackOwner?.channel?.trim() ?? '';
67
- const ownerTo = humansOwner?.to ?? fallbackOwner?.to?.trim() ?? '';
60
+ const owner = getOwnerNotifyFromHumans() ?? meta.notify?.owner;
61
+ const ownerChannel = owner?.channel ?? '';
62
+ const ownerTo = owner?.to ?? '';
68
63
  if (!channel)
69
64
  channel = ownerChannel;
70
65
  if (!to || usedOwnerAlias)
@@ -72,7 +67,7 @@ export function resolveSendEnvelope(input, meta) {
72
67
  }
73
68
  if (!channel || !to) {
74
69
  const hint = input.ownerMode || usedOwnerAlias
75
- ? 'Set notify.{channel,to} in humans.yaml (or notify.owner in agents.yaml), or pass --channel and --to explicitly.'
70
+ ? 'Set owner.channels and owner.policy.normal in humans.yaml, or pass --channel and --to explicitly.'
76
71
  : 'Need --channel and --to (or --to owner with notify.owner configured). ' +
77
72
  'Example: agents send --channel desktop --to local --text "hi"';
78
73
  return { ok: false, error: hint };
@@ -91,6 +86,7 @@ export function resolveSendEnvelope(input, meta) {
91
86
  thread: input.thread?.trim() || undefined,
92
87
  attachments: attachments.length ? attachments : undefined,
93
88
  from: input.from?.trim() || undefined,
89
+ ownerScoped: usedOwnerAlias || (input.ownerMode === true && !input.to?.trim()),
94
90
  dryRun: input.dryRun,
95
91
  },
96
92
  };
@@ -107,6 +103,7 @@ export async function deliverEnvelope(envelope, meta) {
107
103
  thread: envelope.thread,
108
104
  attachments: envelope.attachments,
109
105
  from: envelope.from,
106
+ ownerScoped: envelope.ownerScoped,
110
107
  dryRun: envelope.dryRun,
111
108
  });
112
109
  }
@@ -0,0 +1,27 @@
1
+ import type { CloudProvider, DispatchOptions } from './types.js';
2
+ /**
3
+ * Resolve the prompt for a cloud dispatch: the raw value, or the contents of
4
+ * the file it points at (with a dim note on TTY). Dies when empty.
5
+ */
6
+ export declare function resolveCloudPrompt(raw: string | undefined, opts: {
7
+ json: boolean;
8
+ hint: string;
9
+ }): string;
10
+ export interface ExecuteCloudDispatchParams {
11
+ provider: CloudProvider;
12
+ dispatchOptions: DispatchOptions;
13
+ /** Image file paths for vision dispatch (checked against provider capability). */
14
+ imagePaths?: string[];
15
+ /** Raw skill refs (`id` or `id@version`) for ride-along skills. */
16
+ skillIds?: string[];
17
+ /** Stream the task output after dispatch; false = fire-and-forget. */
18
+ follow: boolean;
19
+ json: boolean;
20
+ }
21
+ /**
22
+ * Dispatch a cloud task and (unless follow=false) stream it to completion.
23
+ * Owns: share-env injection, capability checks, the dispatch spinner +
24
+ * missing-target picker, local persistence, event emission, and the budget
25
+ * kill-switch. Dies on any dispatch failure — callers never see a partial.
26
+ */
27
+ export declare function executeCloudDispatch(params: ExecuteCloudDispatchParams): Promise<void>;