@phnx-labs/agents-cli 1.20.52 → 1.20.53

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 (107) hide show
  1. package/CHANGELOG.md +57 -2
  2. package/README.md +12 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/cloud.d.ts +3 -0
  5. package/dist/commands/cloud.js +2 -1
  6. package/dist/commands/exec.js +65 -1
  7. package/dist/commands/feed.d.ts +29 -0
  8. package/dist/commands/feed.js +237 -32
  9. package/dist/commands/memory.d.ts +9 -0
  10. package/dist/commands/memory.js +164 -0
  11. package/dist/commands/message.d.ts +11 -6
  12. package/dist/commands/message.js +140 -5
  13. package/dist/commands/routines.js +12 -0
  14. package/dist/commands/secrets-migrate.d.ts +2 -1
  15. package/dist/commands/secrets-migrate.js +88 -13
  16. package/dist/commands/secrets.js +4 -1
  17. package/dist/commands/sessions.js +10 -1
  18. package/dist/commands/worktree.js +4 -2
  19. package/dist/index.js +16 -21
  20. package/dist/lib/agents.js +249 -17
  21. package/dist/lib/answer-router.d.ts +75 -0
  22. package/dist/lib/answer-router.js +149 -0
  23. package/dist/lib/ask-classifier.d.ts +71 -0
  24. package/dist/lib/ask-classifier.js +197 -0
  25. package/dist/lib/cloud/antigravity.d.ts +0 -2
  26. package/dist/lib/cloud/antigravity.js +2 -17
  27. package/dist/lib/cloud/codex.js +3 -18
  28. package/dist/lib/cloud/rush.js +3 -15
  29. package/dist/lib/cloud/stream.js +2 -0
  30. package/dist/lib/cloud/types.d.ts +21 -0
  31. package/dist/lib/cloud/types.js +81 -0
  32. package/dist/lib/crabbox/cli.d.ts +1 -1
  33. package/dist/lib/crabbox/cli.js +12 -2
  34. package/dist/lib/crabbox/lease.d.ts +13 -0
  35. package/dist/lib/crabbox/lease.js +11 -2
  36. package/dist/lib/crabbox/progress.d.ts +62 -0
  37. package/dist/lib/crabbox/progress.js +129 -0
  38. package/dist/lib/events.js +4 -1
  39. package/dist/lib/exec.js +19 -1
  40. package/dist/lib/feed-outcome.d.ts +101 -0
  41. package/dist/lib/feed-outcome.js +244 -0
  42. package/dist/lib/feed-policy.d.ts +30 -0
  43. package/dist/lib/feed-policy.js +133 -0
  44. package/dist/lib/feed.d.ts +127 -3
  45. package/dist/lib/feed.js +416 -40
  46. package/dist/lib/git.d.ts +17 -1
  47. package/dist/lib/git.js +20 -1
  48. package/dist/lib/hooks.js +522 -12
  49. package/dist/lib/hosts/passthrough.d.ts +3 -3
  50. package/dist/lib/hosts/passthrough.js +3 -4
  51. package/dist/lib/mailbox-gc.d.ts +22 -0
  52. package/dist/lib/mailbox-gc.js +161 -0
  53. package/dist/lib/mailbox.d.ts +26 -2
  54. package/dist/lib/mailbox.js +80 -5
  55. package/dist/lib/mcp.js +82 -0
  56. package/dist/lib/memory.d.ts +55 -0
  57. package/dist/lib/memory.js +274 -0
  58. package/dist/lib/notify.d.ts +16 -0
  59. package/dist/lib/notify.js +61 -0
  60. package/dist/lib/operator.d.ts +26 -0
  61. package/dist/lib/operator.js +107 -0
  62. package/dist/lib/plugins.d.ts +35 -0
  63. package/dist/lib/plugins.js +217 -0
  64. package/dist/lib/remote-agents-json.d.ts +14 -0
  65. package/dist/lib/remote-agents-json.js +94 -0
  66. package/dist/lib/resources/mcp.js +44 -0
  67. package/dist/lib/resources/memory.d.ts +15 -0
  68. package/dist/lib/resources/memory.js +46 -0
  69. package/dist/lib/resources/types.d.ts +2 -2
  70. package/dist/lib/runner.d.ts +43 -0
  71. package/dist/lib/runner.js +323 -74
  72. package/dist/lib/sandbox.js +6 -0
  73. package/dist/lib/secrets/bundles.js +38 -13
  74. package/dist/lib/secrets/icloud-import.d.ts +12 -3
  75. package/dist/lib/secrets/icloud-import.js +37 -7
  76. package/dist/lib/secrets/index.d.ts +106 -2
  77. package/dist/lib/secrets/index.js +603 -28
  78. package/dist/lib/session/active.d.ts +18 -0
  79. package/dist/lib/session/active.js +47 -17
  80. package/dist/lib/session/db.d.ts +9 -1
  81. package/dist/lib/session/db.js +18 -3
  82. package/dist/lib/session/discover.d.ts +13 -0
  83. package/dist/lib/session/discover.js +31 -0
  84. package/dist/lib/session/parse.d.ts +8 -0
  85. package/dist/lib/session/parse.js +42 -21
  86. package/dist/lib/session/remote-active.js +8 -89
  87. package/dist/lib/session/state.d.ts +11 -0
  88. package/dist/lib/session/state.js +37 -0
  89. package/dist/lib/session/tail.d.ts +23 -4
  90. package/dist/lib/session/tail.js +34 -16
  91. package/dist/lib/session/throughput.d.ts +30 -0
  92. package/dist/lib/session/throughput.js +86 -0
  93. package/dist/lib/shim-heal.d.ts +12 -3
  94. package/dist/lib/shim-heal.js +12 -6
  95. package/dist/lib/staleness/detectors/subagents.js +57 -3
  96. package/dist/lib/staleness/writers/hooks.js +7 -3
  97. package/dist/lib/staleness/writers/subagents.js +37 -6
  98. package/dist/lib/startup/command-registry.d.ts +1 -0
  99. package/dist/lib/startup/command-registry.js +2 -0
  100. package/dist/lib/subagents.d.ts +52 -0
  101. package/dist/lib/subagents.js +315 -12
  102. package/dist/lib/teams/worktree.d.ts +8 -0
  103. package/dist/lib/teams/worktree.js +8 -0
  104. package/dist/lib/types.d.ts +10 -2
  105. package/dist/lib/versions.js +38 -48
  106. package/package.json +4 -3
  107. package/scripts/postinstall.js +61 -1
@@ -16,7 +16,7 @@
16
16
  * the bundle/key boundary even for dotted bundle names like `hetzner.com`.
17
17
  */
18
18
  import { deleteSyncedKeychainItem, getSyncedKeychainTokens, listSyncedKeychainItems, parseBundleValue, secretsKeychainItem, serializeRef, SECRETS_ITEM_PREFIX, } from './index.js';
19
- import { BUNDLE_META_PREFIX, BUNDLE_NAME_PATTERN, ENV_KEY_PATTERN, bundleExists, bundleItemStore, bundlePolicy, keychainRef, readBundle, writeBundle, } from './bundles.js';
19
+ import { BUNDLE_META_PREFIX, BUNDLE_NAME_PATTERN, ENV_KEY_PATTERN, bundleExists, bundleItemStore, bundlePolicy, isLoaderOrInterpreterEnv, isReservedEnvName, keychainRef, readBundle, writeBundle, } from './bundles.js';
20
20
  /**
21
21
  * Group raw synced service names into per-bundle candidates. Pure — separated
22
22
  * from discovery so the parsing rules are unit-testable without a keychain.
@@ -72,9 +72,12 @@ export function discoverSyncedBundles() {
72
72
  * Values come from the synced secret items; the synced metadata item, when
73
73
  * present, contributes the description, literal vars, and non-keychain refs
74
74
  * (env:/file:/exec: refs carry no stored secret, so copying the ref preserves
75
- * them exactly). Existing local keys are skipped unless `force`. With `purge`,
76
- * only services whose value was successfully read are deleted from iCloud
77
- * an unreadable item is never destroyed.
75
+ * them exactly). Existing local keys are skipped unless `force`. Keys the
76
+ * modern store refuses by policy (reserved / loader env names the pre-cutover
77
+ * store accepted) are reported as `unimportable` instead of aborting the whole
78
+ * bundle. With `purge`, only services whose value provably lives locally
79
+ * (imported now, or skipped-because-present) are deleted from iCloud — an
80
+ * unreadable or unimportable item is never destroyed.
78
81
  */
79
82
  export function importSyncedBundle(candidate, opts = {}) {
80
83
  const values = getSyncedKeychainTokens(candidate.services);
@@ -112,8 +115,21 @@ export function importSyncedBundle(candidate, opts = {}) {
112
115
  let added = 0;
113
116
  let skipped = 0;
114
117
  const missing = [];
118
+ const unimportable = [];
119
+ // Services whose value now provably lives in the local store — imported this
120
+ // run, or skipped because the local bundle already carries the key. Only
121
+ // these may be purged; a missing or unimportable key's iCloud item is its
122
+ // only copy and must survive.
123
+ const purgeable = new Set();
124
+ const rejectedByPolicy = (key) => isLoaderOrInterpreterEnv(key) || isReservedEnvName(key);
115
125
  // Keys with a synced secret item: re-store the value device-locally.
116
126
  for (const key of candidate.keys) {
127
+ // The pre-cutover store accepted keys the modern one refuses (writeBundle
128
+ // throws) — skip them instead of aborting the whole bundle.
129
+ if (rejectedByPolicy(key)) {
130
+ unimportable.push(key);
131
+ continue;
132
+ }
117
133
  const value = values.get(secretsKeychainItem(candidate.name, key));
118
134
  if (value === undefined) {
119
135
  missing.push(key);
@@ -121,6 +137,7 @@ export function importSyncedBundle(candidate, opts = {}) {
121
137
  }
122
138
  if (!opts.force && key in bundle.vars) {
123
139
  skipped++;
140
+ purgeable.add(secretsKeychainItem(candidate.name, key));
124
141
  continue;
125
142
  }
126
143
  if (opts.allPlaintext) {
@@ -130,6 +147,7 @@ export function importSyncedBundle(candidate, opts = {}) {
130
147
  store.set(secretsKeychainItem(candidate.name, key), value);
131
148
  bundle.vars[key] = keychainRef(key);
132
149
  }
150
+ purgeable.add(secretsKeychainItem(candidate.name, key));
133
151
  added++;
134
152
  }
135
153
  // Vars declared only in the synced metadata: literals and non-keychain refs
@@ -140,6 +158,11 @@ export function importSyncedBundle(candidate, opts = {}) {
140
158
  continue;
141
159
  if (candidate.keys.includes(key))
142
160
  continue; // the secret item already covered it
161
+ if (rejectedByPolicy(key)) {
162
+ if (!unimportable.includes(key))
163
+ unimportable.push(key);
164
+ continue;
165
+ }
143
166
  let parsed;
144
167
  try {
145
168
  parsed = parseBundleValue(raw);
@@ -160,14 +183,21 @@ export function importSyncedBundle(candidate, opts = {}) {
160
183
  added++;
161
184
  }
162
185
  writeBundle(bundle);
186
+ // The metadata item is only a projection of what was just written locally —
187
+ // safe to purge once writeBundle has succeeded, UNLESS it still names keys
188
+ // that never made it over (missing/unimportable): then it stays as the only
189
+ // record of them.
190
+ if (metaJson !== undefined && missing.length === 0 && unimportable.length === 0) {
191
+ purgeable.add(metaService);
192
+ }
163
193
  let purged = 0;
164
194
  if (opts.purge) {
165
195
  for (const svc of candidate.services) {
166
- if (!values.has(svc))
167
- continue; // never destroy an item we couldn't read
196
+ if (!purgeable.has(svc))
197
+ continue;
168
198
  if (deleteSyncedKeychainItem(svc))
169
199
  purged++;
170
200
  }
171
201
  }
172
- return { name: candidate.name, added, skipped, missing, purged };
202
+ return { name: candidate.name, added, skipped, missing, unimportable, purged };
173
203
  }
@@ -74,7 +74,11 @@ export declare function secretsKeychainItem(bundle: string, key: string): string
74
74
  export interface KeychainBackend {
75
75
  has(item: string): boolean;
76
76
  get(item: string): string;
77
- set(item: string, value: string): void;
77
+ /** `opts.noAcl` mirrors setKeychainToken's no-ACL write path so tests can
78
+ * assert which path a write took; in-memory backends may ignore it. */
79
+ set(item: string, value: string, opts?: {
80
+ noAcl?: boolean;
81
+ }): void;
78
82
  delete(item: string): boolean;
79
83
  list(prefix: string): string[];
80
84
  }
@@ -85,6 +89,103 @@ export declare function setKeychainBackendForTest(b: KeychainBackend | null): Ke
85
89
  * with an in-memory backend there is no real keychain to dedup, so the broker
86
90
  * fast-path must not engage. Always false in production (`backend` is null). */
87
91
  export declare function isKeychainBackendOverridden(): boolean;
92
+ export declare const HMAC_KEY_ITEM = "agents-cli.hmackey";
93
+ /** Force hashed service names on with a fixed key (test only). Pass null to
94
+ * restore lazy production resolution. Composes with setKeychainBackendForTest
95
+ * so unit tests exercise the exact transform production uses. */
96
+ export declare function setKeychainServiceHashingForTest(key: Buffer | null): void;
97
+ /**
98
+ * Run `fn` with service-name hashing suspended: every primitive uses the
99
+ * literal names it is given. For migration flows ONLY — they enumerate raw
100
+ * names from the helper (which may be pre-re-key cleartext leftovers) and must
101
+ * read/delete those exact items, not their hashed transforms.
102
+ */
103
+ export declare function withRawKeychainServiceNames<T>(fn: () => T): T;
104
+ /** The hashed (storage) service name for a cleartext item name. Exported for
105
+ * the re-key migration and tests; runtime callers go through the primitives,
106
+ * which apply this transparently. */
107
+ export declare function hashedServiceName(item: string, key: Buffer): string;
108
+ /**
109
+ * The storage-layer service name for `item`: hashed when hashing is active,
110
+ * the item itself otherwise. For callers that mix helper-enumerated
111
+ * (already-hashed) names with computed cleartext names in one lookup map —
112
+ * see readAndResolveBundleEnv.
113
+ */
114
+ export declare function keychainServiceAlias(item: string): string;
115
+ /** One re-keyed (or failed) item, by its old cleartext service name. */
116
+ export interface RekeyPlanItem {
117
+ oldService: string;
118
+ newService: string;
119
+ /** Preserve the no-ACL write path for `never`-policy bundle items. */
120
+ noAcl: boolean;
121
+ /** Replacement payload (bundle metadata gets `name` injected); absent = copy verbatim. */
122
+ payload?: string;
123
+ }
124
+ /**
125
+ * Build the old→new mapping for a set of cleartext services. Bundle metadata
126
+ * is parsed first to (a) recover each bundle's prompt policy — the persisted
127
+ * `tier` token, where `none`/`never` means the item must be re-written through
128
+ * the no-ACL path — and (b) inject the cleartext `name` into the JSON, because
129
+ * after hashing the service name can no longer carry it (listBundles reads it
130
+ * back from the payload). A value item's tier is resolved from its bundle's
131
+ * metadata PAYLOAD in `values` — under the cleartext metadata name or its
132
+ * hashed transform — never from the metadata item being part of the same
133
+ * `services` batch: a --prefix run can scope a bundle's value items alone, and
134
+ * rekeyServiceNames supplies the out-of-scope metadata reads (see the
135
+ * supplemental batch there). Exported for unit tests.
136
+ */
137
+ export declare function computeRekeyPlan(services: string[], values: Map<string, string>, key: Buffer): {
138
+ items: RekeyPlanItem[];
139
+ unreadable: string[];
140
+ };
141
+ /** Outcome of one rekeyServiceNames run. */
142
+ export interface RekeyReport {
143
+ /** Old cleartext services whose items now live under hashed names. */
144
+ migrated: string[];
145
+ failed: Array<{
146
+ item: string;
147
+ detail: string;
148
+ }>;
149
+ /** True when hashed naming is on after this run. */
150
+ activated: boolean;
151
+ nothingToDo: boolean;
152
+ }
153
+ /**
154
+ * The one-time re-key: move every cleartext-named `agents-cli.*` item to its
155
+ * hashed service name. Composed entirely from the existing helper primitives —
156
+ * no new Swift command:
157
+ *
158
+ * 1. Enumerate cleartext services (silent) and batch-read every value behind
159
+ * ONE Touch ID (`get-batch`; readItem also sweeps legacy/orphaned copies).
160
+ * 2. Write each hashed copy (`set`/`set-no-acl` never prompt), preserving
161
+ * the no-ACL tier for `never`-policy bundles.
162
+ * 3. Batch-verify every copy round-trips (second Touch ID).
163
+ * 4. Only then activate hashing (sentinel + pendingDeletes) and delete the
164
+ * old items (silent).
165
+ *
166
+ * Add-before-delete throughout: a cancel/crash/failure anywhere before step 4
167
+ * leaves every old item intact and hashing OFF — same rationale as the
168
+ * helper's migrate-orphans, which is also why no pre-write backup is taken.
169
+ * On ANY per-item failure nothing is deleted and the sentinel stays off
170
+ * (all-or-nothing activation); the report names every failed item. A crash
171
+ * between the sentinel write and the deletes is resumed silently by the next
172
+ * process (pendingDeletes). Idempotent: re-running converges.
173
+ */
174
+ export declare function rekeyServiceNames(opts?: {
175
+ prefixes?: string[];
176
+ announce?: boolean;
177
+ log?: (line: string) => void;
178
+ }): RekeyReport;
179
+ /** Re-key state snapshot for `agents secrets rekey --status`. */
180
+ export declare function rekeyStatus(): {
181
+ migrated: boolean;
182
+ hasKey: boolean;
183
+ pendingDeletes: number;
184
+ cleartext: string[];
185
+ /** False when the enumeration probe fails (locked keybag) — the cleartext
186
+ * count is then meaningless. Only probeable once the key record exists. */
187
+ enumerationOk: boolean;
188
+ };
88
189
  /** Check if a keychain/keyring item exists. Never prompts for biometry. */
89
190
  export declare function hasKeychainToken(item: string): boolean;
90
191
  /**
@@ -129,7 +230,10 @@ export declare function deleteKeychainToken(item: string): boolean;
129
230
  * and the direct file enumeration return the same items.
130
231
  */
131
232
  export declare function keychainUsesFileFallback(): boolean;
132
- /** Enumerate keychain/keyring item names starting with the given prefix. */
233
+ /** Enumerate keychain/keyring item names starting with the given prefix.
234
+ * With hashed service names active, the two sub-namespace prefixes callers
235
+ * use (bundle metadata; one bundle's value items) are mapped to their hashed
236
+ * shapes — the returned names are then storage (opaque) names. */
133
237
  export declare function listKeychainItems(prefix: string): string[];
134
238
  /**
135
239
  * Enumerate ONLY legacy file-based-keychain item names with the given prefix —