@phnx-labs/agents-cli 1.20.72 → 1.20.74

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 (234) hide show
  1. package/CHANGELOG.md +395 -0
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/doctor.js +21 -5
  11. package/dist/commands/events.d.ts +11 -10
  12. package/dist/commands/events.js +30 -21
  13. package/dist/commands/exec.d.ts +34 -0
  14. package/dist/commands/exec.js +337 -39
  15. package/dist/commands/export.d.ts +11 -0
  16. package/dist/commands/export.js +215 -0
  17. package/dist/commands/feed.d.ts +13 -0
  18. package/dist/commands/feed.js +130 -29
  19. package/dist/commands/hq.d.ts +2 -0
  20. package/dist/commands/hq.js +58 -0
  21. package/dist/commands/import.js +51 -3
  22. package/dist/commands/lease.d.ts +19 -0
  23. package/dist/commands/lease.js +138 -11
  24. package/dist/commands/login.d.ts +2 -0
  25. package/dist/commands/login.js +123 -0
  26. package/dist/commands/logs.js +1 -1
  27. package/dist/commands/mine.d.ts +27 -0
  28. package/dist/commands/mine.js +207 -0
  29. package/dist/commands/monitors.js +79 -49
  30. package/dist/commands/plugins.js +1 -0
  31. package/dist/commands/profiles.js +158 -0
  32. package/dist/commands/repo.js +2 -60
  33. package/dist/commands/resources.d.ts +5 -0
  34. package/dist/commands/resources.js +95 -0
  35. package/dist/commands/routines.d.ts +2 -0
  36. package/dist/commands/routines.js +139 -37
  37. package/dist/commands/secrets.js +234 -47
  38. package/dist/commands/send.d.ts +15 -0
  39. package/dist/commands/send.js +62 -0
  40. package/dist/commands/sessions-tail.d.ts +1 -0
  41. package/dist/commands/sessions-tail.js +20 -9
  42. package/dist/commands/sessions.js +18 -3
  43. package/dist/commands/setup-mine.d.ts +18 -0
  44. package/dist/commands/setup-mine.js +106 -0
  45. package/dist/commands/setup-share.js +2 -2
  46. package/dist/commands/setup.js +9 -2
  47. package/dist/commands/share.d.ts +8 -1
  48. package/dist/commands/share.js +122 -37
  49. package/dist/commands/ssh.d.ts +9 -0
  50. package/dist/commands/ssh.js +196 -26
  51. package/dist/commands/sync.js +1 -1
  52. package/dist/commands/teams.js +11 -2
  53. package/dist/commands/versions.js +36 -5
  54. package/dist/commands/view.js +75 -27
  55. package/dist/index.js +80 -10
  56. package/dist/lib/activity.d.ts +124 -0
  57. package/dist/lib/activity.js +542 -0
  58. package/dist/lib/agents.d.ts +35 -0
  59. package/dist/lib/agents.js +79 -6
  60. package/dist/lib/artifact-actions.d.ts +1 -1
  61. package/dist/lib/artifact-actions.js +1 -1
  62. package/dist/lib/ask-classifier.d.ts +2 -1
  63. package/dist/lib/ask-classifier.js +3 -3
  64. package/dist/lib/auth-health.d.ts +32 -4
  65. package/dist/lib/auth-health.js +40 -1
  66. package/dist/lib/auto-pull.js +8 -1
  67. package/dist/lib/brand.d.ts +40 -0
  68. package/dist/lib/brand.js +122 -0
  69. package/dist/lib/browser/drivers/ssh.js +4 -7
  70. package/dist/lib/browser/service.js +6 -8
  71. package/dist/lib/channels/providers/index.d.ts +2 -0
  72. package/dist/lib/channels/providers/index.js +20 -0
  73. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  74. package/dist/lib/channels/providers/mailbox.js +26 -0
  75. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  76. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  77. package/dist/lib/channels/providers/rush.d.ts +6 -0
  78. package/dist/lib/channels/providers/rush.js +55 -0
  79. package/dist/lib/channels/registry.d.ts +42 -0
  80. package/dist/lib/channels/registry.js +20 -0
  81. package/dist/lib/channels/resolve.d.ts +11 -0
  82. package/dist/lib/channels/resolve.js +12 -0
  83. package/dist/lib/cli-resources.d.ts +18 -0
  84. package/dist/lib/cli-resources.js +53 -4
  85. package/dist/lib/cloud/codex.js +6 -3
  86. package/dist/lib/cloud/factory.d.ts +1 -0
  87. package/dist/lib/cloud/factory.js +10 -4
  88. package/dist/lib/cloud/rush.d.ts +3 -1
  89. package/dist/lib/cloud/rush.js +6 -1
  90. package/dist/lib/cloud/types.d.ts +2 -0
  91. package/dist/lib/codex-home.d.ts +35 -0
  92. package/dist/lib/codex-home.js +136 -0
  93. package/dist/lib/config-transfer.d.ts +16 -0
  94. package/dist/lib/config-transfer.js +56 -0
  95. package/dist/lib/crabbox/cli.d.ts +44 -0
  96. package/dist/lib/crabbox/cli.js +120 -20
  97. package/dist/lib/crabbox/lease.d.ts +52 -1
  98. package/dist/lib/crabbox/lease.js +117 -16
  99. package/dist/lib/crabbox/progress.d.ts +31 -0
  100. package/dist/lib/crabbox/progress.js +76 -0
  101. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  102. package/dist/lib/crabbox/runtimes.js +33 -8
  103. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  104. package/dist/lib/crabbox/setup-copy.js +127 -0
  105. package/dist/lib/daemon.d.ts +5 -5
  106. package/dist/lib/daemon.js +9 -9
  107. package/dist/lib/drive-sync.js +6 -3
  108. package/dist/lib/event-stream.d.ts +36 -0
  109. package/dist/lib/event-stream.js +68 -0
  110. package/dist/lib/events.d.ts +1 -1
  111. package/dist/lib/exec.d.ts +11 -0
  112. package/dist/lib/exec.js +23 -2
  113. package/dist/lib/export.d.ts +72 -0
  114. package/dist/lib/export.js +269 -0
  115. package/dist/lib/feed-policy.d.ts +1 -1
  116. package/dist/lib/feed-policy.js +14 -9
  117. package/dist/lib/feed-ranking.d.ts +32 -0
  118. package/dist/lib/feed-ranking.js +224 -0
  119. package/dist/lib/feed.d.ts +35 -2
  120. package/dist/lib/feed.js +66 -3
  121. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  122. package/dist/lib/fleet/auth-sync.js +92 -0
  123. package/dist/lib/fleet/remote-login.d.ts +187 -0
  124. package/dist/lib/fleet/remote-login.js +556 -0
  125. package/dist/lib/format.d.ts +30 -3
  126. package/dist/lib/format.js +34 -5
  127. package/dist/lib/fs-atomic.d.ts +7 -2
  128. package/dist/lib/fs-atomic.js +8 -12
  129. package/dist/lib/git.d.ts +16 -0
  130. package/dist/lib/git.js +79 -2
  131. package/dist/lib/heal.js +11 -3
  132. package/dist/lib/hosts/progress.d.ts +28 -10
  133. package/dist/lib/hosts/progress.js +79 -22
  134. package/dist/lib/hosts/remote-cmd.js +4 -0
  135. package/dist/lib/hq/floor.d.ts +87 -0
  136. package/dist/lib/hq/floor.js +226 -0
  137. package/dist/lib/import.d.ts +23 -0
  138. package/dist/lib/import.js +74 -2
  139. package/dist/lib/isolation-boundary-report.d.ts +10 -0
  140. package/dist/lib/isolation-boundary-report.js +35 -0
  141. package/dist/lib/menubar/install-menubar.js +14 -20
  142. package/dist/lib/notify.d.ts +1 -0
  143. package/dist/lib/notify.js +3 -3
  144. package/dist/lib/open-url.d.ts +2 -0
  145. package/dist/lib/open-url.js +19 -0
  146. package/dist/lib/openclaw-keychain.d.ts +56 -0
  147. package/dist/lib/openclaw-keychain.js +236 -0
  148. package/dist/lib/overdue.js +10 -0
  149. package/dist/lib/permissions.d.ts +44 -1
  150. package/dist/lib/permissions.js +284 -7
  151. package/dist/lib/project-launch.d.ts +6 -12
  152. package/dist/lib/project-launch.js +13 -228
  153. package/dist/lib/project-resources.d.ts +7 -0
  154. package/dist/lib/project-resources.js +306 -0
  155. package/dist/lib/pty-client.d.ts +27 -0
  156. package/dist/lib/pty-client.js +136 -10
  157. package/dist/lib/refresh.js +14 -10
  158. package/dist/lib/resource-profiles.d.ts +26 -0
  159. package/dist/lib/resource-profiles.js +157 -0
  160. package/dist/lib/resources/permissions.js +7 -1
  161. package/dist/lib/resources/types.d.ts +1 -1
  162. package/dist/lib/resources.d.ts +1 -1
  163. package/dist/lib/resources.js +32 -3
  164. package/dist/lib/routines.d.ts +11 -0
  165. package/dist/lib/routines.js +56 -15
  166. package/dist/lib/runner.d.ts +1 -0
  167. package/dist/lib/runner.js +66 -5
  168. package/dist/lib/secrets/bundles.d.ts +16 -3
  169. package/dist/lib/secrets/bundles.js +206 -37
  170. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  171. package/dist/lib/secrets/icloud-import.js +9 -6
  172. package/dist/lib/secrets/mcp.js +1 -1
  173. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  174. package/dist/lib/secrets/vault-age-helper.js +34 -0
  175. package/dist/lib/secrets/vault.d.ts +49 -0
  176. package/dist/lib/secrets/vault.js +397 -0
  177. package/dist/lib/self-heal/checks/path.js +3 -1
  178. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  179. package/dist/lib/self-heal/checks/shims.js +19 -11
  180. package/dist/lib/session/cloud.d.ts +2 -2
  181. package/dist/lib/session/cloud.js +2 -2
  182. package/dist/lib/session/db.d.ts +4 -0
  183. package/dist/lib/session/db.js +44 -7
  184. package/dist/lib/session/discover.d.ts +2 -0
  185. package/dist/lib/session/discover.js +114 -3
  186. package/dist/lib/session/stream-render.d.ts +3 -0
  187. package/dist/lib/session/stream-render.js +130 -0
  188. package/dist/lib/session/types.d.ts +6 -0
  189. package/dist/lib/share/analytics.d.ts +13 -0
  190. package/dist/lib/share/analytics.js +45 -0
  191. package/dist/lib/share/config.d.ts +19 -5
  192. package/dist/lib/share/config.js +44 -8
  193. package/dist/lib/share/provision.d.ts +58 -6
  194. package/dist/lib/share/provision.js +97 -22
  195. package/dist/lib/share/publish.d.ts +36 -13
  196. package/dist/lib/share/publish.js +55 -8
  197. package/dist/lib/share/worker-template.js +83 -17
  198. package/dist/lib/shims.d.ts +35 -0
  199. package/dist/lib/shims.js +159 -13
  200. package/dist/lib/ssh-exec.d.ts +14 -0
  201. package/dist/lib/ssh-exec.js +57 -0
  202. package/dist/lib/staleness/detectors/hooks.js +25 -1
  203. package/dist/lib/staleness/detectors/permissions.js +66 -0
  204. package/dist/lib/staleness/detectors/workflows.js +20 -0
  205. package/dist/lib/staleness/writers/hooks.js +58 -4
  206. package/dist/lib/staleness/writers/permissions.js +2 -2
  207. package/dist/lib/staleness/writers/skills.js +1 -1
  208. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  209. package/dist/lib/staleness/writers/sources.js +68 -1
  210. package/dist/lib/star-nudge.d.ts +45 -0
  211. package/dist/lib/star-nudge.js +91 -0
  212. package/dist/lib/startup/command-registry.d.ts +7 -1
  213. package/dist/lib/startup/command-registry.js +19 -3
  214. package/dist/lib/state.d.ts +2 -0
  215. package/dist/lib/state.js +17 -4
  216. package/dist/lib/subagents-registry.d.ts +2 -0
  217. package/dist/lib/subagents-registry.js +3 -0
  218. package/dist/lib/teams/parsers.js +214 -6
  219. package/dist/lib/teams/supervisor.d.ts +7 -0
  220. package/dist/lib/teams/supervisor.js +2 -1
  221. package/dist/lib/template.d.ts +1 -1
  222. package/dist/lib/template.js +1 -1
  223. package/dist/lib/triggers/webhook.js +36 -3
  224. package/dist/lib/types.d.ts +84 -0
  225. package/dist/lib/uninstall.js +1 -45
  226. package/dist/lib/version.d.ts +40 -0
  227. package/dist/lib/version.js +94 -16
  228. package/dist/lib/versions.d.ts +24 -0
  229. package/dist/lib/versions.js +269 -78
  230. package/dist/lib/workflows.d.ts +2 -0
  231. package/dist/lib/workflows.js +88 -0
  232. package/package.json +2 -1
  233. package/dist/commands/daemon.d.ts +0 -10
  234. package/dist/commands/daemon.js +0 -121
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Top-level resource profiles.
3
+ *
4
+ * These are distinct from model-provider run profiles in lib/profiles.ts. A
5
+ * resource profile is a global mode switch stored in agents.yaml that filters
6
+ * the resolved DotAgents resource view and secrets bundles.
7
+ */
8
+ import { readMeta, updateMeta } from './state.js';
9
+ import { brandProfileName } from './brand.js';
10
+ const PROFILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-_]{0,48}$/i;
11
+ const PATTERNED_KINDS = [
12
+ 'commands',
13
+ 'skills',
14
+ 'hooks',
15
+ 'subagents',
16
+ 'plugins',
17
+ 'workflows',
18
+ 'permissions',
19
+ 'mcp',
20
+ ];
21
+ export function validateResourceProfileName(name) {
22
+ if (!PROFILE_NAME_PATTERN.test(name)) {
23
+ throw new Error(`Invalid profile name '${name}'. Use letters, digits, dash, underscore (max 48 chars).`);
24
+ }
25
+ }
26
+ export function listResourceProfileNames() {
27
+ return Object.keys(readMeta().profiles?.presets ?? {}).sort((a, b) => a.localeCompare(b));
28
+ }
29
+ export function getResourceProfilePreset(name) {
30
+ validateResourceProfileName(name);
31
+ return readMeta().profiles?.presets?.[name] ?? null;
32
+ }
33
+ export function getActiveResourceProfileName() {
34
+ // A white-label brand pins its own profile; when running under a brand
35
+ // (AGENTS_BRAND set) that preset wins over the global `profiles.active`, so
36
+ // every resource filter that keys off the active profile becomes brand-scoped.
37
+ // See lib/brand.ts.
38
+ const branded = brandProfileName();
39
+ if (branded)
40
+ return branded;
41
+ return readMeta().profiles?.active ?? null;
42
+ }
43
+ export function getActiveResourceProfile() {
44
+ const meta = readMeta();
45
+ const name = getActiveResourceProfileName();
46
+ if (!name)
47
+ return null;
48
+ const preset = meta.profiles?.presets?.[name];
49
+ return preset ? { name, preset } : null;
50
+ }
51
+ export function upsertResourceProfilePreset(name, preset) {
52
+ validateResourceProfileName(name);
53
+ updateMeta((meta) => ({
54
+ ...meta,
55
+ profiles: {
56
+ ...meta.profiles,
57
+ presets: {
58
+ ...(meta.profiles?.presets ?? {}),
59
+ [name]: preset,
60
+ },
61
+ },
62
+ }));
63
+ }
64
+ export function setActiveResourceProfile(name) {
65
+ if (name) {
66
+ validateResourceProfileName(name);
67
+ const preset = getResourceProfilePreset(name);
68
+ if (!preset)
69
+ throw new Error(`Profile '${name}' not found.`);
70
+ }
71
+ updateMeta((meta) => ({
72
+ ...meta,
73
+ profiles: {
74
+ ...meta.profiles,
75
+ active: name || undefined,
76
+ },
77
+ }));
78
+ }
79
+ export function patternedProfileKinds() {
80
+ return [...PATTERNED_KINDS];
81
+ }
82
+ function plainPatternMatches(pattern, name) {
83
+ if (pattern === '*')
84
+ return true;
85
+ if (!pattern.includes('*'))
86
+ return pattern === name;
87
+ const escaped = pattern
88
+ .split('*')
89
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
90
+ .join('.*');
91
+ return new RegExp(`^${escaped}$`).test(name);
92
+ }
93
+ function filterByPlainPatterns(names, patterns) {
94
+ const include = patterns.filter((p) => !p.startsWith('!'));
95
+ const exclude = patterns.filter((p) => p.startsWith('!')).map((p) => p.slice(1));
96
+ return names.filter((name) => include.some((p) => plainPatternMatches(p, name)) &&
97
+ !exclude.some((p) => plainPatternMatches(p, name)));
98
+ }
99
+ function sourcePatternMatches(pattern, name, sourceMap) {
100
+ const colon = pattern.indexOf(':');
101
+ if (colon === -1)
102
+ return false;
103
+ const source = pattern.slice(0, colon);
104
+ const rawName = pattern.slice(colon + 1);
105
+ const actualSource = sourceMap.get(name);
106
+ if (actualSource !== source)
107
+ return false;
108
+ const patternNames = rawName === '*' ? ['*'] : rawName.split(',').map((n) => n.trim()).filter(Boolean);
109
+ return patternNames.some((patternName) => plainPatternMatches(patternName, name));
110
+ }
111
+ function expandProfilePatterns(names, patterns, sourceMap) {
112
+ if (sourceMap) {
113
+ const included = new Set();
114
+ const excluded = new Set();
115
+ for (const pattern of patterns) {
116
+ const negate = pattern.startsWith('!');
117
+ const raw = negate ? pattern.slice(1) : pattern;
118
+ const target = negate ? excluded : included;
119
+ const matches = raw.includes(':')
120
+ ? names.filter((name) => sourcePatternMatches(raw, name, sourceMap))
121
+ : names.filter((name) => plainPatternMatches(raw, name));
122
+ for (const name of matches)
123
+ target.add(name);
124
+ }
125
+ return names.filter((name) => included.has(name) && !excluded.has(name));
126
+ }
127
+ return filterByPlainPatterns(names, patterns);
128
+ }
129
+ export function activeRulesPreset() {
130
+ const preset = getActiveResourceProfile()?.preset;
131
+ return preset?.rules ?? preset?.rulesPreset ?? null;
132
+ }
133
+ export function filterNamesForActiveResourceProfile(kind, names, sourceMap) {
134
+ const profile = getActiveResourceProfile();
135
+ if (!profile)
136
+ return names;
137
+ if (kind === 'memory') {
138
+ const rules = activeRulesPreset();
139
+ return rules ? names.filter((name) => name === rules) : names;
140
+ }
141
+ const patterns = profile.preset[kind];
142
+ if (!patterns)
143
+ return names;
144
+ return expandProfilePatterns(names, patterns, sourceMap);
145
+ }
146
+ export function isNameActiveInResourceProfile(kind, name, source) {
147
+ const sourceMap = source ? new Map([[name, source]]) : undefined;
148
+ return filterNamesForActiveResourceProfile(kind, [name], sourceMap).includes(name);
149
+ }
150
+ export function assertNameActiveInResourceProfile(kind, name) {
151
+ const active = getActiveResourceProfile();
152
+ if (!active)
153
+ return;
154
+ if (isNameActiveInResourceProfile(kind, name))
155
+ return;
156
+ throw new Error(`${kind === 'secrets' ? 'Secrets bundle' : 'Resource'} '${name}' is not active in profile '${active.name}'.`);
157
+ }
@@ -77,6 +77,12 @@ function getAgentConfigPath(agent, versionHome) {
77
77
  return path.join(versionHome, '.kiro', 'settings', 'permissions.yaml');
78
78
  case 'openclaw':
79
79
  return path.join(versionHome, '.openclaw', 'openclaw.json');
80
+ case 'copilot':
81
+ return path.join(versionHome, '.copilot', 'permissions-config.json');
82
+ case 'forge':
83
+ return path.join(versionHome, '.forge', 'permissions.yaml');
84
+ case 'hermes':
85
+ return path.join(versionHome, '.hermes', 'config.yaml');
80
86
  case 'goose':
81
87
  return path.join(versionHome, '.config', 'goose', 'permission.yaml');
82
88
  default:
@@ -173,7 +179,7 @@ export const PermissionsHandler = {
173
179
  merged = mergePermissionSets(merged, r.item);
174
180
  }
175
181
  // Apply to the agent's config file
176
- applyPermissionsToVersion(agent, merged, versionHome, true);
182
+ applyPermissionsToVersion(agent, merged, versionHome, true, cwd);
177
183
  },
178
184
  /**
179
185
  * Permissions use YAML format.
@@ -5,7 +5,7 @@
5
5
  * - Union: All resources from all layers are combined
6
6
  * - Override on name conflict: Higher layer wins (project > user > system)
7
7
  */
8
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'forge';
8
+ export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'forge';
9
9
  export type Layer = 'system' | 'user' | 'project';
10
10
  export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent' | 'workflow' | 'memory';
11
11
  /** A resolved resource with its origin layer. */
@@ -5,7 +5,7 @@
5
5
  import type { AgentId } from './types.js';
6
6
  import { type SkillParseError } from './skills.js';
7
7
  /** Resource kind — matches the subdirectory name under each repo root. */
8
- export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'cli' | 'permissions' | 'subagents' | 'profiles' | 'secrets';
8
+ export type ResourceKind = 'commands' | 'skills' | 'hooks' | 'rules' | 'mcp' | 'cli' | 'permissions' | 'subagents' | 'workflows' | 'profiles' | 'secrets';
9
9
  /** A resource resolved with its origin. */
10
10
  export interface ResolvedResource {
11
11
  name: string;
@@ -14,6 +14,27 @@ import { listMcpServerConfigs } from './mcp.js';
14
14
  import { WorkflowsHandler } from './resources/workflows.js';
15
15
  import { isCapable } from './capabilities.js';
16
16
  import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, } from './state.js';
17
+ import { isNameActiveInResourceProfile } from './resource-profiles.js';
18
+ function profiledKind(kind) {
19
+ switch (kind) {
20
+ case 'commands':
21
+ case 'skills':
22
+ case 'hooks':
23
+ case 'mcp':
24
+ case 'permissions':
25
+ case 'subagents':
26
+ case 'secrets':
27
+ return kind;
28
+ case 'rules':
29
+ return 'memory';
30
+ default:
31
+ return null;
32
+ }
33
+ }
34
+ function resourceIsActive(kind, name, source) {
35
+ const activeKind = profiledKind(kind);
36
+ return activeKind ? isNameActiveInResourceProfile(activeKind, name, source) : true;
37
+ }
17
38
  /**
18
39
  * Resolve a single resource by kind + name using project > user > system precedence.
19
40
  * For file-based resources the path ends in `.md`, `.yaml`, or `.yml` as appropriate.
@@ -36,13 +57,19 @@ export function resolveResource(kind, name, cwd) {
36
57
  // Try exact name (for directories like skills/subagents)
37
58
  const exactPath = path.join(dir, name);
38
59
  if (fs.existsSync(exactPath)) {
39
- return { name, path: exactPath, source };
60
+ if (resourceIsActive(kind, name, source)) {
61
+ return { name, path: exactPath, source };
62
+ }
63
+ continue;
40
64
  }
41
65
  // Try with common file extensions
42
66
  for (const ext of ['.md', '.yaml', '.yml']) {
43
67
  const withExt = exactPath + ext;
44
68
  if (fs.existsSync(withExt)) {
45
- return { name, path: withExt, source };
69
+ if (resourceIsActive(kind, name, source)) {
70
+ return { name, path: withExt, source };
71
+ }
72
+ continue;
46
73
  }
47
74
  }
48
75
  }
@@ -80,6 +107,8 @@ export function listResources(kind, cwd) {
80
107
  const rawName = entry.name.replace(/\.(md|yaml|yml)$/, '');
81
108
  if (seen.has(rawName))
82
109
  continue;
110
+ if (!resourceIsActive(kind, rawName, source))
111
+ continue;
83
112
  seen.add(rawName);
84
113
  results.push({
85
114
  name: rawName,
@@ -162,7 +191,7 @@ export function getAgentResources(agentId, options = {}) {
162
191
  hooks.push({ name: hook.name, path: hook.path, scope: hook.scope });
163
192
  }
164
193
  }
165
- // Workflows (claude only)
194
+ // Workflows
166
195
  const workflows = [];
167
196
  if (isCapable(agentId, 'workflows')) {
168
197
  for (const w of WorkflowsHandler.listAll(agentId, cwd)) {
@@ -41,6 +41,10 @@ export interface GithubJobTrigger {
41
41
  repo?: string;
42
42
  /** git branch (ref short name) — when set, only payloads for this branch match. */
43
43
  branch?: string;
44
+ /** GitHub webhook action, e.g. `opened`, `synchronize`, or `labeled`. */
45
+ action?: string;
46
+ /** Required GitHub label name. For `pull_request.labeled`, this is the added label. */
47
+ label?: string;
44
48
  }
45
49
  export interface LinearJobTrigger {
46
50
  type: 'linear_event';
@@ -184,6 +188,10 @@ export declare function checkJobDeviceEligibility(config: Pick<JobConfig, 'name'
184
188
  * Higher layers shadow lower ones of the same name (first-seen wins): a project
185
189
  * routine shadows a user routine, and a user routine shadows a built-in system
186
190
  * routine (`~/.agents/.system/routines/`, shipped via gh:phnx-labs/.agents-system).
191
+ * When a same-name project routine wins for inspection, the user-layer
192
+ * `devices` allowlist is overlaid only if the project routine does not declare
193
+ * its own allowlist, so CWD project discovery cannot hide an operational fleet
194
+ * pin or erase a project-authored one.
187
195
  * Project discovery is opt-in via `cwd`; the daemon (which calls `listJobs()`
188
196
  * with no argument) sees user + system routines, so a built-in routine fires for
189
197
  * every install unless the user overrides or disables it by name.
@@ -191,6 +199,9 @@ export declare function checkJobDeviceEligibility(config: Pick<JobConfig, 'name'
191
199
  export declare function listJobs(cwd?: string): JobConfig[];
192
200
  /**
193
201
  * Read a single job config by name, checking project > user > system.
202
+ * Same-name project routines keep the user-layer `devices` allowlist only when
203
+ * the project routine does not declare its own allowlist, for the same reason
204
+ * as listJobs().
194
205
  * Project discovery is opt-in via `cwd`; daemon callers pass no argument and
195
206
  * resolve user + system routines (a user routine of the same name shadows a
196
207
  * built-in system routine).
@@ -107,11 +107,29 @@ const JOB_DEFAULTS = {
107
107
  timeout: '10m',
108
108
  enabled: true,
109
109
  };
110
+ function overlayUserRoutineDevices(job, userJob) {
111
+ if (job.devices !== undefined)
112
+ return job;
113
+ if (!userJob)
114
+ return job;
115
+ const merged = { ...job };
116
+ if (userJob.devices && userJob.devices.length > 0) {
117
+ merged.devices = userJob.devices;
118
+ }
119
+ else {
120
+ delete merged.devices;
121
+ }
122
+ return merged;
123
+ }
110
124
  /**
111
125
  * List all job configs, scanning project > user > system routine dirs.
112
126
  * Higher layers shadow lower ones of the same name (first-seen wins): a project
113
127
  * routine shadows a user routine, and a user routine shadows a built-in system
114
128
  * routine (`~/.agents/.system/routines/`, shipped via gh:phnx-labs/.agents-system).
129
+ * When a same-name project routine wins for inspection, the user-layer
130
+ * `devices` allowlist is overlaid only if the project routine does not declare
131
+ * its own allowlist, so CWD project discovery cannot hide an operational fleet
132
+ * pin or erase a project-authored one.
115
133
  * Project discovery is opt-in via `cwd`; the daemon (which calls `listJobs()`
116
134
  * with no argument) sees user + system routines, so a built-in routine fires for
117
135
  * every install unless the user overrides or disables it by name.
@@ -120,22 +138,26 @@ export function listJobs(cwd) {
120
138
  ensureAgentsDir();
121
139
  const seen = new Set();
122
140
  const jobs = [];
141
+ const userDir = getRoutinesDir();
123
142
  const dirs = [];
124
143
  if (cwd) {
125
144
  const projectDir = getProjectRoutinesDir(cwd);
126
145
  if (projectDir)
127
- dirs.push(projectDir);
146
+ dirs.push({ scope: 'project', path: projectDir });
128
147
  }
129
- dirs.push(getRoutinesDir());
130
- dirs.push(getSystemRoutinesDir());
131
- for (const dir of dirs) {
148
+ dirs.push({ scope: 'user', path: userDir });
149
+ dirs.push({ scope: 'system', path: getSystemRoutinesDir() });
150
+ for (const { scope, path: dir } of dirs) {
132
151
  if (!fs.existsSync(dir))
133
152
  continue;
134
153
  const files = fs.readdirSync(dir).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
135
154
  for (const file of files) {
136
- const job = readJobFile(path.join(dir, file));
155
+ let job = readJobFile(path.join(dir, file));
137
156
  if (!job)
138
157
  continue;
158
+ if (scope === 'project') {
159
+ job = overlayUserRoutineDevices(job, readJobFromDir(userDir, job.name));
160
+ }
139
161
  if (seen.has(job.name))
140
162
  continue;
141
163
  seen.add(job.name);
@@ -146,26 +168,39 @@ export function listJobs(cwd) {
146
168
  }
147
169
  /**
148
170
  * Read a single job config by name, checking project > user > system.
171
+ * Same-name project routines keep the user-layer `devices` allowlist only when
172
+ * the project routine does not declare its own allowlist, for the same reason
173
+ * as listJobs().
149
174
  * Project discovery is opt-in via `cwd`; daemon callers pass no argument and
150
175
  * resolve user + system routines (a user routine of the same name shadows a
151
176
  * built-in system routine).
152
177
  */
153
178
  export function readJob(name, cwd) {
154
179
  ensureAgentsDir();
180
+ const userDir = getRoutinesDir();
155
181
  const dirs = [];
156
182
  if (cwd) {
157
183
  const projectDir = getProjectRoutinesDir(cwd);
158
184
  if (projectDir)
159
- dirs.push(projectDir);
160
- }
161
- dirs.push(getRoutinesDir());
162
- dirs.push(getSystemRoutinesDir());
163
- for (const dir of dirs) {
164
- for (const ext of ['.yml', '.yaml']) {
165
- const filePath = safeJoin(dir, name + ext);
166
- if (fs.existsSync(filePath)) {
167
- return readJobFile(filePath);
168
- }
185
+ dirs.push({ scope: 'project', path: projectDir });
186
+ }
187
+ dirs.push({ scope: 'user', path: userDir });
188
+ dirs.push({ scope: 'system', path: getSystemRoutinesDir() });
189
+ for (const { scope, path: dir } of dirs) {
190
+ const job = readJobFromDir(dir, name);
191
+ if (job) {
192
+ if (scope === 'project')
193
+ return overlayUserRoutineDevices(job, readJobFromDir(userDir, name));
194
+ return job;
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ function readJobFromDir(dir, name) {
200
+ for (const ext of ['.yml', '.yaml']) {
201
+ const filePath = safeJoin(dir, name + ext);
202
+ if (fs.existsSync(filePath)) {
203
+ return readJobFile(filePath);
169
204
  }
170
205
  }
171
206
  return null;
@@ -440,6 +475,12 @@ export function validateTrigger(trigger) {
440
475
  if (github.branch !== undefined && typeof github.branch !== 'string') {
441
476
  errors.push('trigger.branch must be a string');
442
477
  }
478
+ if (github.action !== undefined && typeof github.action !== 'string') {
479
+ errors.push('trigger.action must be a string');
480
+ }
481
+ if (github.label !== undefined && typeof github.label !== 'string') {
482
+ errors.push('trigger.label must be a string');
483
+ }
443
484
  return errors;
444
485
  }
445
486
  const linear = t;
@@ -25,6 +25,7 @@ export interface RunResult {
25
25
  }
26
26
  /** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
27
27
  export declare function buildJobCommand(config: JobConfig, resolvedPrompt: string): string[];
28
+ export declare function archiveRoutineTranscripts(meta: Pick<RunMeta, 'jobName' | 'runId' | 'agent'>, runDir: string, overlayHome?: string): void;
28
29
  /** Pre-flight version/account selection for a routine job. */
29
30
  export interface RoutineLaunchPlan {
30
31
  /** Ordered attempts: primary first, then same-agent failover accounts. */
@@ -19,13 +19,14 @@ import * as path from 'path';
19
19
  import * as os from 'os';
20
20
  import { resolveJobPrompt, parseTimeout, writeRunMeta, getRunDir, checkJobDeviceEligibility, finalizeRunMeta, } from './routines.js';
21
21
  import { getRunsDir } from './state.js';
22
- import { prepareJobHome, buildSpawnEnv } from './sandbox.js';
22
+ import { prepareJobHome, buildSpawnEnv, getJobHomePath } from './sandbox.js';
23
23
  import { resolveModel, buildReasoningFlags } from './models.js';
24
24
  import { createTimer, maybeRotate, redactPrompt } from './events.js';
25
25
  import { normalizeMode, resolveHeadlessMode, buildExecEnv, detectRateLimit, } from './exec.js';
26
26
  import { loadTask as loadHostTask } from './hosts/tasks.js';
27
27
  import { reconcileTask as reconcileHostTask } from './hosts/reconcile.js';
28
28
  import { backgroundSpawnOptions } from './platform/process.js';
29
+ import { walkForFiles } from './fs-walk.js';
29
30
  import { getBinaryPath, isVersionInstalled, resolveVersion } from './versions.js';
30
31
  import { getConfiguredRunStrategy, resolveRunVersion, rotationFailoverChain, readinessFromCandidate, } from './rotate.js';
31
32
  /** CLI command templates per agent, with {prompt} as a placeholder. */
@@ -36,6 +37,10 @@ const AGENT_COMMANDS = {
36
37
  kimi: ['kimi', '--prompt', '{prompt}', '--output-format', 'stream-json'],
37
38
  droid: ['droid', 'exec', '{prompt}', '-o', 'stream-json'],
38
39
  };
40
+ const ROUTINE_TRANSCRIPT_SPECS = {
41
+ claude: [{ root: ['.claude', 'projects'], ext: '.jsonl' }],
42
+ codex: [{ root: ['.codex', 'sessions'], ext: '.jsonl' }],
43
+ };
39
44
  /** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
40
45
  export function buildJobCommand(config, resolvedPrompt) {
41
46
  // Workflow branch: delegate to `agents run <workflow>` which handles subagent
@@ -184,6 +189,35 @@ function appendModelAndReasoning(cmd, config) {
184
189
  function generateRunId() {
185
190
  return new Date().toISOString().replace(/[:.]/g, '-');
186
191
  }
192
+ export function archiveRoutineTranscripts(meta, runDir, overlayHome) {
193
+ if (!meta.agent)
194
+ return;
195
+ const specs = ROUTINE_TRANSCRIPT_SPECS[meta.agent];
196
+ if (!specs)
197
+ return;
198
+ const home = overlayHome ?? getJobHomePath(meta.jobName);
199
+ for (const spec of specs) {
200
+ const sourceRoot = path.join(home, ...spec.root);
201
+ if (!fs.existsSync(sourceRoot))
202
+ continue;
203
+ const destRoot = path.join(runDir, 'sessions', meta.agent, spec.root[spec.root.length - 1]);
204
+ for (const sourcePath of walkForFiles(sourceRoot, spec.ext, 100_000)) {
205
+ const rel = path.relative(sourceRoot, sourcePath);
206
+ if (rel.startsWith('..') || path.isAbsolute(rel))
207
+ continue;
208
+ const destPath = path.join(destRoot, rel);
209
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
210
+ try {
211
+ fs.copyFileSync(sourcePath, destPath);
212
+ fs.chmodSync(destPath, 0o600);
213
+ }
214
+ catch {
215
+ // The process already reached a terminal state; a concurrently removed
216
+ // transcript should not rewrite that outcome.
217
+ }
218
+ }
219
+ }
220
+ }
187
221
  /**
188
222
  * Build the argv for a command-mode routine: run the shell string directly
189
223
  * through the platform shell. No agent binary, no rotation, no sandbox.
@@ -533,6 +567,7 @@ export async function executeJob(config, deps) {
533
567
  finalizeRunMeta(meta, loopFailed ? 'failed' : 'completed', loopFailed ? 1 : 0, loopFailed ? { errorMessage: `loop stopped: ${loopResult.stoppedBy}` } : undefined);
534
568
  writeRunMeta(meta);
535
569
  timer.end({ status: meta.status, exitCode: meta.exitCode ?? undefined, runId });
570
+ archiveRoutineTranscripts(meta, runDir, overlayHome);
536
571
  return { meta, reportPath: null };
537
572
  }
538
573
  // Single-shot path: build the command once, then walk the launch chain on
@@ -577,6 +612,7 @@ export async function executeJob(config, deps) {
577
612
  writeRunMeta(meta);
578
613
  timer.end({ status: 'timeout', runId });
579
614
  const reportPath = extractAndSaveReport(stdoutPath, effectiveAgent, runDir);
615
+ archiveRoutineTranscripts(meta, runDir, overlayHome);
580
616
  return { meta, reportPath };
581
617
  }
582
618
  if (attempt.status === 'completed') {
@@ -584,6 +620,7 @@ export async function executeJob(config, deps) {
584
620
  writeRunMeta(meta);
585
621
  timer.end({ status: 'completed', exitCode: 0, runId });
586
622
  const reportPath = extractAndSaveReport(stdoutPath, effectiveAgent, runDir);
623
+ archiveRoutineTranscripts(meta, runDir, overlayHome);
587
624
  return { meta, reportPath };
588
625
  }
589
626
  // Failed — cascade only on rate/usage limit when more chain entries remain.
@@ -608,6 +645,7 @@ export async function executeJob(config, deps) {
608
645
  ...(attempt.error ? { error: attempt.error } : {}),
609
646
  });
610
647
  const reportPath = extractAndSaveReport(stdoutPath, effectiveAgent, runDir);
648
+ archiveRoutineTranscripts(meta, runDir, overlayHome);
611
649
  return { meta, reportPath };
612
650
  }
613
651
  // Unreachable: chain is always non-empty, but keep a safe fallback.
@@ -830,13 +868,32 @@ export async function executeJobDetached(config) {
830
868
  ...backgroundSpawnOptions({ fdStdio: true }),
831
869
  env: spawnEnv,
832
870
  });
871
+ let settled = false;
872
+ const settle = (status, exitCode, errorMessage) => {
873
+ if (settled)
874
+ return;
875
+ settled = true;
876
+ finalizeRunMeta(meta, status, exitCode, errorMessage ? { errorMessage } : undefined);
877
+ writeRunMeta(meta);
878
+ archiveRoutineTranscripts(meta, runDir, overlayHome);
879
+ if (status !== 'timeout')
880
+ extractAndSaveReport(stdoutPath, effectiveAgent, runDir);
881
+ };
882
+ child.on('exit', (code) => {
883
+ const inferred = inferFinalStatusFromLog(stdoutPath, effectiveAgent);
884
+ if (inferred) {
885
+ settle(inferred.status, inferred.exitCode);
886
+ }
887
+ else {
888
+ settle(code === 0 ? 'completed' : 'failed', code ?? 1);
889
+ }
890
+ });
833
891
  child.on('error', (err) => {
834
892
  try {
835
893
  fs.closeSync(stdoutFd);
836
894
  }
837
895
  catch { /* fd already closed */ }
838
- finalizeRunMeta(meta, 'failed', 1, { errorMessage: err.message });
839
- writeRunMeta(meta);
896
+ settle('failed', 1, err.message);
840
897
  process.stderr.write(`[agents] daemon: spawn failed for job "${config.name}": ${err.message}\n`);
841
898
  });
842
899
  child.unref();
@@ -1098,8 +1155,10 @@ export function monitorRunningJobs() {
1098
1155
  if (Number.isFinite(wallClockMs) && wallClockMs > MAX_WALL_CLOCK_MS) {
1099
1156
  finalizeRunMeta(meta, 'timeout', null, { errorMessage: 'exceeded max wall clock' });
1100
1157
  writeRunMeta(meta);
1101
- if (!isCommandRun)
1158
+ if (!isCommandRun) {
1102
1159
  extractAndSaveReport(stdoutPath, meta.agent, runDirPath);
1160
+ archiveRoutineTranscripts(meta, runDirPath);
1161
+ }
1103
1162
  continue;
1104
1163
  }
1105
1164
  if (!isPidOurs(meta.pid, meta.spawnedAt)) {
@@ -1122,8 +1181,10 @@ export function monitorRunningJobs() {
1122
1181
  }
1123
1182
  }
1124
1183
  writeRunMeta(meta);
1125
- if (!isCommandRun)
1184
+ if (!isCommandRun) {
1126
1185
  extractAndSaveReport(stdoutPath, meta.agent, runDirPath);
1186
+ archiveRoutineTranscripts(meta, runDirPath);
1187
+ }
1127
1188
  }
1128
1189
  }
1129
1190
  catch { /* corrupt or unreadable meta.json */ }
@@ -13,13 +13,16 @@
13
13
  * SSH). The item-name scheme is identical, so the only difference is where
14
14
  * bytes land. A file-backed bundle is discovered by the presence of its
15
15
  * metadata item in the file store.
16
+ * - `vault`: a single age-encrypted ~/.agents/vault.age file unlocked by
17
+ * `agents login`; intended for user-managed cross-machine file sync.
16
18
  *
17
- * Cross-machine sync is handled by src/lib/secrets/sync.ts via an explicit
18
- * encrypted export/import flow; the bundle layer is sync-agnostic.
19
+ * Server-backed cross-machine sync is handled by src/lib/secrets/sync.ts via
20
+ * an explicit encrypted export/import flow; the bundle layer also supports the
21
+ * local vault backend for user-managed file sync.
19
22
  */
20
23
  import { type BundleValue, type SecretRef } from './index.js';
21
24
  /** Which store carries a bundle's items. */
22
- export type SecretsBackend = 'keychain' | 'file';
25
+ export type SecretsBackend = 'keychain' | 'file' | 'vault';
23
26
  /**
24
27
  * Discover a bundle's backend by location: a file-backed bundle's metadata
25
28
  * item exists in the encrypted-file store. This is a plain file-existence
@@ -91,10 +94,12 @@ export interface LegacyBundleCandidate {
91
94
  }
92
95
  export declare const BUNDLE_NAME_PATTERN: RegExp;
93
96
  export declare const ENV_KEY_PATTERN: RegExp;
97
+ export declare const BUNDLE_KEY_PATTERN: RegExp;
94
98
  export declare const BUNDLE_META_PREFIX = "agents-cli.bundles.";
95
99
  export declare const RESERVED_ENV_NAMES: Set<string>;
96
100
  export declare function bundleToEnvPrefix(name: string): string;
97
101
  export declare function isReservedEnvName(key: string): boolean;
102
+ export declare function bundleKeyToEnvKey(key: string): string;
98
103
  export declare function isLoaderOrInterpreterEnv(name: string): boolean;
99
104
  export declare function sanitizeProcessEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
100
105
  /** Validate a bundle name against the allowed pattern. Throws on invalid input. */
@@ -140,6 +145,7 @@ export interface WriteBundleOptions {
140
145
  */
141
146
  export declare function shouldEvictAfterBundleWrite(skipRequested: boolean, noAgentEnv: string | undefined, backendOverridden: boolean): boolean;
142
147
  export declare function writeBundle(bundle: SecretsBundle, opts?: WriteBundleOptions): void;
148
+ export declare function writeBundleWithItems(bundle: SecretsBundle, items: Map<string, string>, opts?: WriteBundleOptions): void;
143
149
  export declare function deleteBundle(name: string): boolean;
144
150
  export declare function listBundles(): SecretsBundle[];
145
151
  export interface BundleEntryInfo {
@@ -185,7 +191,14 @@ export interface ResolveBundleOptions {
185
191
  * bundle-level expiry has passed) aborts the run before Touch ID is popped.
186
192
  */
187
193
  allowExpired?: boolean;
194
+ /**
195
+ * `process` projects dotted account keys like `GITHUB_USERNAME.personal` to
196
+ * the shell-safe base env name (`GITHUB_USERNAME`). Direct value lookups and
197
+ * backup/export flows use `storage` to preserve the exact bundle key names.
198
+ */
199
+ keyMode?: 'process' | 'storage';
188
200
  }
201
+ export declare function canCacheResolvedEnv(bundle: SecretsBundle, selectedKeys: Set<string>, keyMode: ResolveBundleOptions['keyMode']): boolean;
189
202
  /**
190
203
  * Apply the --keys subset + expiry gate to an already-resolved snapshot from
191
204
  * the secrets-agent fast-path. The agent stores the FULL bundle env, so a