@phnx-labs/agents-cli 1.14.2 → 1.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -7
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +7 -0
- package/dist/commands/browser.d.ts +3 -0
- package/dist/commands/browser.js +392 -0
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +16 -9
- package/dist/commands/doctor.js +248 -12
- package/dist/commands/prune.js +9 -3
- package/dist/commands/refresh-rules.d.ts +15 -0
- package/dist/commands/{refresh-memory.js → refresh-rules.js} +14 -14
- package/dist/commands/routines.js +1 -1
- package/dist/commands/rules.js +100 -4
- package/dist/commands/secrets.js +198 -11
- package/dist/commands/sync.js +19 -0
- package/dist/commands/teams.js +184 -22
- package/dist/commands/trash.d.ts +10 -0
- package/dist/commands/trash.js +187 -0
- package/dist/commands/view.js +47 -14
- package/dist/index.js +62 -4
- package/dist/lib/agents.js +2 -2
- package/dist/lib/browser/cdp.d.ts +24 -0
- package/dist/lib/browser/cdp.js +94 -0
- package/dist/lib/browser/chrome.d.ts +16 -0
- package/dist/lib/browser/chrome.js +157 -0
- package/dist/lib/browser/drivers/local.d.ts +8 -0
- package/dist/lib/browser/drivers/local.js +22 -0
- package/dist/lib/browser/drivers/ssh.d.ts +9 -0
- package/dist/lib/browser/drivers/ssh.js +129 -0
- package/dist/lib/browser/index.d.ts +5 -0
- package/dist/lib/browser/index.js +5 -0
- package/dist/lib/browser/input.d.ts +6 -0
- package/dist/lib/browser/input.js +52 -0
- package/dist/lib/browser/ipc.d.ts +12 -0
- package/dist/lib/browser/ipc.js +223 -0
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +61 -0
- package/dist/lib/browser/refs.d.ts +21 -0
- package/dist/lib/browser/refs.js +88 -0
- package/dist/lib/browser/service.d.ts +45 -0
- package/dist/lib/browser/service.js +404 -0
- package/dist/lib/browser/types.d.ts +73 -0
- package/dist/lib/browser/types.js +7 -0
- package/dist/lib/cloud/codex.js +1 -1
- package/dist/lib/cloud/registry.js +2 -2
- package/dist/lib/cloud/rush.js +2 -2
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/daemon.d.ts +1 -1
- package/dist/lib/daemon.js +47 -11
- package/dist/lib/diff-text.d.ts +25 -0
- package/dist/lib/diff-text.js +47 -0
- package/dist/lib/doctor-diff.d.ts +64 -0
- package/dist/lib/doctor-diff.js +497 -0
- package/dist/lib/git.js +3 -3
- package/dist/lib/hooks.d.ts +6 -0
- package/dist/lib/hooks.js +6 -1
- package/dist/lib/migrate.js +123 -0
- package/dist/lib/pty-client.js +3 -3
- package/dist/lib/pty-server.js +36 -7
- package/dist/lib/resources/commands.d.ts +46 -0
- package/dist/lib/resources/commands.js +208 -0
- package/dist/lib/resources/hooks.d.ts +12 -0
- package/dist/lib/resources/hooks.js +136 -0
- package/dist/lib/resources/index.d.ts +36 -0
- package/dist/lib/resources/index.js +69 -0
- package/dist/lib/resources/mcp.d.ts +34 -0
- package/dist/lib/resources/mcp.js +483 -0
- package/dist/lib/resources/permissions.d.ts +13 -0
- package/dist/lib/resources/permissions.js +184 -0
- package/dist/lib/resources/rules.d.ts +43 -0
- package/dist/lib/resources/rules.js +146 -0
- package/dist/lib/resources/skills.d.ts +37 -0
- package/dist/lib/resources/skills.js +238 -0
- package/dist/lib/resources/subagents.d.ts +46 -0
- package/dist/lib/resources/subagents.js +198 -0
- package/dist/lib/resources/types.d.ts +82 -0
- package/dist/lib/resources/types.js +8 -0
- package/dist/lib/resources.js +1 -1
- package/dist/lib/rotate.d.ts +8 -1
- package/dist/lib/rotate.js +17 -4
- package/dist/lib/rules/compile.d.ts +104 -0
- package/dist/lib/{memory-compile.js → rules/compile.js} +160 -21
- package/dist/lib/rules/compose.d.ts +78 -0
- package/dist/lib/rules/compose.js +170 -0
- package/dist/lib/{memory.d.ts → rules/rules.d.ts} +5 -5
- package/dist/lib/{memory.js → rules/rules.js} +10 -10
- package/dist/lib/secrets/AgentsKeychain.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
- package/dist/lib/secrets/bundles.d.ts +61 -4
- package/dist/lib/secrets/bundles.js +222 -54
- package/dist/lib/secrets/index.d.ts +24 -5
- package/dist/lib/secrets/index.js +70 -41
- package/dist/lib/session/active.js +5 -5
- package/dist/lib/session/db.js +4 -4
- package/dist/lib/session/discover.js +2 -2
- package/dist/lib/session/render.js +21 -7
- package/dist/lib/shims.d.ts +28 -4
- package/dist/lib/shims.js +72 -14
- package/dist/lib/state.d.ts +22 -28
- package/dist/lib/state.js +83 -78
- package/dist/lib/sync-manifest.d.ts +2 -2
- package/dist/lib/sync-manifest.js +5 -5
- package/dist/lib/teams/agents.d.ts +4 -2
- package/dist/lib/teams/agents.js +11 -4
- package/dist/lib/teams/api.d.ts +1 -1
- package/dist/lib/teams/api.js +2 -2
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/persistence.js +3 -3
- package/dist/lib/teams/registry.d.ts +12 -1
- package/dist/lib/teams/registry.js +12 -2
- package/dist/lib/teams/worktree.d.ts +30 -0
- package/dist/lib/teams/worktree.js +96 -0
- package/dist/lib/types.d.ts +12 -6
- package/dist/lib/types.js +3 -3
- package/dist/lib/versions.d.ts +32 -3
- package/dist/lib/versions.js +147 -119
- package/package.json +3 -2
- package/scripts/postinstall.js +29 -0
- package/dist/commands/refresh-memory.d.ts +0 -15
- package/dist/lib/memory-compile.d.ts +0 -66
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagents resource handler.
|
|
3
|
+
*
|
|
4
|
+
* Subagents are YAML files stored in subagents/ directories across layers.
|
|
5
|
+
* Format is the same for all agents. Resolution order: project > user > system.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as yaml from 'yaml';
|
|
10
|
+
import { getProjectAgentsDir, getUserSubagentsDir, getSystemSubagentsDir, getEnabledExtraRepos, } from '../state.js';
|
|
11
|
+
/** Get layer directories for subagent resolution. */
|
|
12
|
+
function getLayerDirs(cwd) {
|
|
13
|
+
const projectDir = getProjectAgentsDir(cwd);
|
|
14
|
+
const extraRepos = getEnabledExtraRepos();
|
|
15
|
+
return {
|
|
16
|
+
system: path.join(getSystemSubagentsDir()),
|
|
17
|
+
user: getUserSubagentsDir(),
|
|
18
|
+
project: projectDir ? path.join(projectDir, 'subagents') : null,
|
|
19
|
+
extra: extraRepos.map((e) => path.join(e.dir, 'subagents')),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Map source directory to layer name. */
|
|
23
|
+
function dirToLayer(dir, dirs) {
|
|
24
|
+
if (dirs.project && dir.startsWith(dirs.project))
|
|
25
|
+
return 'project';
|
|
26
|
+
if (dir.startsWith(dirs.user))
|
|
27
|
+
return 'user';
|
|
28
|
+
return 'system';
|
|
29
|
+
}
|
|
30
|
+
/** Parse a subagent YAML file and return parsed item. */
|
|
31
|
+
function parseSubagentYaml(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
37
|
+
const parsed = yaml.parse(content);
|
|
38
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
name: parsed.name || '',
|
|
43
|
+
description: parsed.description || '',
|
|
44
|
+
model: parsed.model,
|
|
45
|
+
color: parsed.color,
|
|
46
|
+
config: parsed.config,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Extract name from filename (removes .yaml/.yml extension). */
|
|
54
|
+
function nameFromFile(filename) {
|
|
55
|
+
return filename.replace(/\.ya?ml$/, '');
|
|
56
|
+
}
|
|
57
|
+
export class SubagentsHandler {
|
|
58
|
+
kind = 'subagent';
|
|
59
|
+
/**
|
|
60
|
+
* List all subagents across layers, with higher layer winning on name conflict.
|
|
61
|
+
* Returns a union of all subagents, deduplicated by name.
|
|
62
|
+
*/
|
|
63
|
+
listAll(_agent, cwd) {
|
|
64
|
+
const dirs = getLayerDirs(cwd);
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const results = [];
|
|
67
|
+
// Order: project > user > system > extra (extra comes last after system)
|
|
68
|
+
const layerDirs = [];
|
|
69
|
+
if (dirs.project && fs.existsSync(dirs.project)) {
|
|
70
|
+
layerDirs.push({ dir: dirs.project, layer: 'project' });
|
|
71
|
+
}
|
|
72
|
+
if (fs.existsSync(dirs.user)) {
|
|
73
|
+
layerDirs.push({ dir: dirs.user, layer: 'user' });
|
|
74
|
+
}
|
|
75
|
+
if (fs.existsSync(dirs.system)) {
|
|
76
|
+
layerDirs.push({ dir: dirs.system, layer: 'system' });
|
|
77
|
+
}
|
|
78
|
+
for (const extraDir of dirs.extra) {
|
|
79
|
+
if (fs.existsSync(extraDir)) {
|
|
80
|
+
layerDirs.push({ dir: extraDir, layer: 'system' });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const { dir, layer } of layerDirs) {
|
|
84
|
+
let entries;
|
|
85
|
+
try {
|
|
86
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
if (!entry.isFile())
|
|
93
|
+
continue;
|
|
94
|
+
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.yml'))
|
|
95
|
+
continue;
|
|
96
|
+
if (entry.name.startsWith('.'))
|
|
97
|
+
continue;
|
|
98
|
+
const name = nameFromFile(entry.name);
|
|
99
|
+
if (seen.has(name))
|
|
100
|
+
continue;
|
|
101
|
+
const filePath = path.join(dir, entry.name);
|
|
102
|
+
const item = parseSubagentYaml(filePath);
|
|
103
|
+
if (!item)
|
|
104
|
+
continue;
|
|
105
|
+
seen.add(name);
|
|
106
|
+
results.push({
|
|
107
|
+
name,
|
|
108
|
+
item,
|
|
109
|
+
layer,
|
|
110
|
+
path: filePath,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a single subagent by name.
|
|
118
|
+
* Returns the winning layer's version, or null if not found.
|
|
119
|
+
*/
|
|
120
|
+
resolve(_agent, name, cwd) {
|
|
121
|
+
const dirs = getLayerDirs(cwd);
|
|
122
|
+
// Order: project > user > system > extra
|
|
123
|
+
const searchDirs = [];
|
|
124
|
+
if (dirs.project) {
|
|
125
|
+
searchDirs.push({ dir: dirs.project, layer: 'project' });
|
|
126
|
+
}
|
|
127
|
+
searchDirs.push({ dir: dirs.user, layer: 'user' });
|
|
128
|
+
searchDirs.push({ dir: dirs.system, layer: 'system' });
|
|
129
|
+
for (const extraDir of dirs.extra) {
|
|
130
|
+
searchDirs.push({ dir: extraDir, layer: 'system' });
|
|
131
|
+
}
|
|
132
|
+
for (const { dir, layer } of searchDirs) {
|
|
133
|
+
if (!fs.existsSync(dir))
|
|
134
|
+
continue;
|
|
135
|
+
// Try .yaml first, then .yml
|
|
136
|
+
for (const ext of ['.yaml', '.yml']) {
|
|
137
|
+
const filePath = path.join(dir, name + ext);
|
|
138
|
+
if (fs.existsSync(filePath)) {
|
|
139
|
+
const item = parseSubagentYaml(filePath);
|
|
140
|
+
if (item) {
|
|
141
|
+
return {
|
|
142
|
+
name,
|
|
143
|
+
item,
|
|
144
|
+
layer,
|
|
145
|
+
path: filePath,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Sync resolved subagents to the agent's version home directory.
|
|
155
|
+
* Copies YAML files to the target directory.
|
|
156
|
+
*/
|
|
157
|
+
sync(agent, versionHome, cwd) {
|
|
158
|
+
const targetDir = path.join(versionHome, this.targetDir(agent));
|
|
159
|
+
// Ensure target directory exists
|
|
160
|
+
if (!fs.existsSync(targetDir)) {
|
|
161
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
// Clear existing subagents in target
|
|
164
|
+
try {
|
|
165
|
+
const existing = fs.readdirSync(targetDir);
|
|
166
|
+
for (const file of existing) {
|
|
167
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
168
|
+
fs.unlinkSync(path.join(targetDir, file));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore errors during cleanup
|
|
174
|
+
}
|
|
175
|
+
// Copy all resolved subagents
|
|
176
|
+
const resolved = this.listAll(agent, cwd);
|
|
177
|
+
for (const { name, path: sourcePath } of resolved) {
|
|
178
|
+
const ext = sourcePath.endsWith('.yml') ? '.yml' : '.yaml';
|
|
179
|
+
const targetPath = path.join(targetDir, name + ext);
|
|
180
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get the file format this resource uses for a given agent.
|
|
185
|
+
* Subagents always use YAML format.
|
|
186
|
+
*/
|
|
187
|
+
format(_agent) {
|
|
188
|
+
return 'yaml';
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get the target directory name in the agent's version home.
|
|
192
|
+
*/
|
|
193
|
+
targetDir(_agent) {
|
|
194
|
+
return 'subagents';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Singleton instance of the SubagentsHandler. */
|
|
198
|
+
export const subagentsHandler = new SubagentsHandler();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified resource system types.
|
|
3
|
+
*
|
|
4
|
+
* Resources merge from three layers: system → user → project
|
|
5
|
+
* - Union: All resources from all layers are combined
|
|
6
|
+
* - Override on name conflict: Higher layer wins (project > user > system)
|
|
7
|
+
*/
|
|
8
|
+
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw';
|
|
9
|
+
export type Layer = 'system' | 'user' | 'project';
|
|
10
|
+
export type ResourceKind = 'command' | 'hook' | 'skill' | 'rule' | 'mcp' | 'permission' | 'subagent';
|
|
11
|
+
/** A resolved resource with its origin layer. */
|
|
12
|
+
export interface ResolvedItem<T> {
|
|
13
|
+
name: string;
|
|
14
|
+
item: T;
|
|
15
|
+
layer: Layer;
|
|
16
|
+
path: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resource handler interface.
|
|
20
|
+
*
|
|
21
|
+
* Each resource type (commands, hooks, skills, etc.) implements this interface
|
|
22
|
+
* to provide consistent list/resolve/sync behavior across all agent types.
|
|
23
|
+
*/
|
|
24
|
+
export interface ResourceHandler<T> {
|
|
25
|
+
readonly kind: ResourceKind;
|
|
26
|
+
/**
|
|
27
|
+
* List all resources across layers, with higher layer winning on name conflict.
|
|
28
|
+
* Returns a union of all resources, deduplicated by name.
|
|
29
|
+
*/
|
|
30
|
+
listAll(agent: AgentId, cwd?: string): ResolvedItem<T>[];
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a single resource by name.
|
|
33
|
+
* Returns the winning layer's version, or null if not found.
|
|
34
|
+
*/
|
|
35
|
+
resolve(agent: AgentId, name: string, cwd?: string): ResolvedItem<T> | null;
|
|
36
|
+
/**
|
|
37
|
+
* Sync resolved resources to the agent's version home directory.
|
|
38
|
+
* Copies/transforms resources as needed for the agent's expected format.
|
|
39
|
+
*/
|
|
40
|
+
sync(agent: AgentId, versionHome: string, cwd?: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get the file format this resource uses for a given agent.
|
|
43
|
+
*/
|
|
44
|
+
format(agent: AgentId): 'md' | 'toml' | 'json' | 'yaml';
|
|
45
|
+
/**
|
|
46
|
+
* Get the target directory name in the agent's version home.
|
|
47
|
+
*/
|
|
48
|
+
targetDir(agent: AgentId): string;
|
|
49
|
+
/**
|
|
50
|
+
* For resources that modify config files (MCP, permissions),
|
|
51
|
+
* return the config file path. Returns null if not applicable.
|
|
52
|
+
*/
|
|
53
|
+
configPath?(agent: AgentId, versionHome: string): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Compute content hash for a resource item (for change detection).
|
|
56
|
+
* Used by diff() to detect modifications without full content comparison.
|
|
57
|
+
* Optional — handlers that don't implement this fall back to full sync.
|
|
58
|
+
*/
|
|
59
|
+
hash?(item: T): string;
|
|
60
|
+
/**
|
|
61
|
+
* Compare source layers vs synced target to detect drift.
|
|
62
|
+
* Returns list of resources that differ (added, modified, removed).
|
|
63
|
+
* Enables incremental sync and "X resources out of sync" status.
|
|
64
|
+
* Optional — handlers that don't implement this always report "unknown".
|
|
65
|
+
*/
|
|
66
|
+
diff?(agent: AgentId, versionHome: string, cwd?: string): ResourceDiff[];
|
|
67
|
+
}
|
|
68
|
+
/** Result of comparing source vs target for a single resource. */
|
|
69
|
+
export interface ResourceDiff {
|
|
70
|
+
name: string;
|
|
71
|
+
status: 'added' | 'modified' | 'removed';
|
|
72
|
+
sourceLayer: Layer | null;
|
|
73
|
+
sourceHash: string | null;
|
|
74
|
+
targetHash: string | null;
|
|
75
|
+
}
|
|
76
|
+
/** Helper to get layer directories for resource resolution. */
|
|
77
|
+
export interface LayerDirs {
|
|
78
|
+
system: string;
|
|
79
|
+
user: string;
|
|
80
|
+
project: string | null;
|
|
81
|
+
extra: string[];
|
|
82
|
+
}
|
package/dist/lib/resources.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AGENTS, listInstalledMcpsWithScope } from './agents.js';
|
|
|
8
8
|
import { listInstalledCommandsWithScope } from './commands.js';
|
|
9
9
|
import { listInstalledSkillsWithScope } from './skills.js';
|
|
10
10
|
import { listInstalledHooksWithScope } from './hooks.js';
|
|
11
|
-
import { listInstalledInstructionsWithScope } from './
|
|
11
|
+
import { listInstalledInstructionsWithScope } from './rules/rules.js';
|
|
12
12
|
import { getEffectiveHome } from './versions.js';
|
|
13
13
|
import { listMcpServerConfigs } from './mcp.js';
|
|
14
14
|
import { getProjectAgentsDir, getUserAgentsDir, getSystemAgentsDir, getEnabledExtraRepos, } from './state.js';
|
package/dist/lib/rotate.d.ts
CHANGED
|
@@ -35,7 +35,14 @@ export declare const RUN_STRATEGIES: RunStrategy[];
|
|
|
35
35
|
export declare function normalizeRunStrategy(value: unknown): RunStrategy | null;
|
|
36
36
|
/** Read project-local run strategy from the nearest agents.yaml, if present. */
|
|
37
37
|
export declare function getProjectRunStrategy(agent: AgentId, startPath: string): RunStrategy | null;
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the configured strategy. Lookup order:
|
|
40
|
+
* 1. project-local agents.yaml (nearest to `startPath`)
|
|
41
|
+
* 2. ~/.agents-system/agents.yaml
|
|
42
|
+
* 3. default: `available` (use the pinned default version when healthy,
|
|
43
|
+
* otherwise fall through to a healthy account so a single rate-limited
|
|
44
|
+
* account doesn't block the run).
|
|
45
|
+
*/
|
|
39
46
|
export declare function getConfiguredRunStrategy(agent: AgentId, startPath?: string): RunStrategy;
|
|
40
47
|
/** Persist the global run strategy used by bare `agents run <agent>`. */
|
|
41
48
|
export declare function setGlobalRunStrategy(agent: AgentId, strategy: RunStrategy): void;
|
package/dist/lib/rotate.js
CHANGED
|
@@ -11,6 +11,12 @@ import { getAccountInfo } from './agents.js';
|
|
|
11
11
|
import { readMeta, writeMeta, getAgentsDir } from './state.js';
|
|
12
12
|
import { listInstalledVersions, getVersionHomePath, resolveVersion } from './versions.js';
|
|
13
13
|
import { getUsageInfoByIdentity, getUsageLookupKey, isClaudeAuthValid, } from './usage.js';
|
|
14
|
+
const ROTATE_DIR = 'helpers/rotate';
|
|
15
|
+
function getRotateDir() {
|
|
16
|
+
const dir = path.join(getAgentsDir(), ROTATE_DIR);
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
14
20
|
export const RUN_STRATEGIES = ['pinned', 'available', 'balanced'];
|
|
15
21
|
/**
|
|
16
22
|
* Return a run strategy when the input is valid, otherwise null.
|
|
@@ -47,11 +53,18 @@ export function getProjectRunStrategy(agent, startPath) {
|
|
|
47
53
|
}
|
|
48
54
|
return null;
|
|
49
55
|
}
|
|
50
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the configured strategy. Lookup order:
|
|
58
|
+
* 1. project-local agents.yaml (nearest to `startPath`)
|
|
59
|
+
* 2. ~/.agents-system/agents.yaml
|
|
60
|
+
* 3. default: `available` (use the pinned default version when healthy,
|
|
61
|
+
* otherwise fall through to a healthy account so a single rate-limited
|
|
62
|
+
* account doesn't block the run).
|
|
63
|
+
*/
|
|
51
64
|
export function getConfiguredRunStrategy(agent, startPath = process.cwd()) {
|
|
52
65
|
return getProjectRunStrategy(agent, startPath)
|
|
53
66
|
?? normalizeRunStrategy(readMeta().run?.[agent]?.strategy)
|
|
54
|
-
?? '
|
|
67
|
+
?? 'available';
|
|
55
68
|
}
|
|
56
69
|
/** Persist the global run strategy used by bare `agents run <agent>`. */
|
|
57
70
|
export function setGlobalRunStrategy(agent, strategy) {
|
|
@@ -279,7 +292,7 @@ export async function selectAvailableVersion(agent, preferredVersion) {
|
|
|
279
292
|
* a torn write just means the next reader sees a stale timestamp (harmless).
|
|
280
293
|
*/
|
|
281
294
|
function recordRotationPick(agent, version) {
|
|
282
|
-
const stampPath = path.join(
|
|
295
|
+
const stampPath = path.join(getRotateDir(), `stamp-${agent}.json`);
|
|
283
296
|
try {
|
|
284
297
|
fs.writeFileSync(stampPath, JSON.stringify({ version, ts: Date.now() }), 'utf-8');
|
|
285
298
|
}
|
|
@@ -290,7 +303,7 @@ function recordRotationPick(agent, version) {
|
|
|
290
303
|
* or stamp is older than 60 seconds (stale).
|
|
291
304
|
*/
|
|
292
305
|
function readRotationStamp(agent) {
|
|
293
|
-
const stampPath = path.join(
|
|
306
|
+
const stampPath = path.join(getRotateDir(), `stamp-${agent}.json`);
|
|
294
307
|
try {
|
|
295
308
|
const raw = JSON.parse(fs.readFileSync(stampPath, 'utf-8'));
|
|
296
309
|
if (Date.now() - raw.ts < 60_000)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules file compilation -- resolving @-imports into a single flat file.
|
|
3
|
+
*
|
|
4
|
+
* Agents that do not natively resolve `@path/to/file` imports (Codex, Cursor)
|
|
5
|
+
* need a pre-compiled rules file with all imports inlined. This module
|
|
6
|
+
* handles that expansion for both user-scope (writes into version home) and
|
|
7
|
+
* project-scope (writes into the workspace).
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentId } from '../types.js';
|
|
10
|
+
import { type RulesLayer } from './compose.js';
|
|
11
|
+
/** Sidecar manifest recording source file hashes for staleness detection. */
|
|
12
|
+
export interface CompileManifest {
|
|
13
|
+
compiledAt: string;
|
|
14
|
+
sources: {
|
|
15
|
+
path: string;
|
|
16
|
+
sha256: string;
|
|
17
|
+
mtime?: number;
|
|
18
|
+
size?: number;
|
|
19
|
+
}[];
|
|
20
|
+
}
|
|
21
|
+
/** Result of resolving @-imports in a rules file. */
|
|
22
|
+
export interface ResolveResult {
|
|
23
|
+
/** Fully-inlined content. */
|
|
24
|
+
content: string;
|
|
25
|
+
/** Absolute paths of every file read during resolution (including the root). */
|
|
26
|
+
sources: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Expand all `@path/to/file` imports in `content`, recursively up to
|
|
30
|
+
* MAX_DEPTH. Imports inside fenced code blocks and inline code spans are
|
|
31
|
+
* left alone, matching Claude Code's parser. Missing files are left as-is
|
|
32
|
+
* (silent skip), matching the documented behavior.
|
|
33
|
+
*
|
|
34
|
+
* Relative paths resolve against `baseDir`; absolute and tilde-prefixed
|
|
35
|
+
* paths resolve against the filesystem root / home directory.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveImports(content: string, baseDir: string): ResolveResult;
|
|
38
|
+
/** True if the agent's native runtime resolves `@path` imports in its rules file. */
|
|
39
|
+
export declare function supportsRulesImports(agentId: AgentId): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Fast staleness check. Returns true when:
|
|
42
|
+
* - the compiled file or its manifest is missing
|
|
43
|
+
* - any recorded source file is missing
|
|
44
|
+
* - any recorded source's sha256 no longer matches
|
|
45
|
+
*
|
|
46
|
+
* For agents that support @-imports natively, always returns false — there's
|
|
47
|
+
* nothing to compile.
|
|
48
|
+
*/
|
|
49
|
+
export declare function isRulesStale(agentId: AgentId, version: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the source `rules/AGENTS.md` (with all @-imports expanded) and
|
|
52
|
+
* write the result into the version home, alongside a sidecar manifest that
|
|
53
|
+
* records source file hashes for staleness detection.
|
|
54
|
+
*
|
|
55
|
+
* Agents that natively resolve @-imports are skipped (no-op) — their sync
|
|
56
|
+
* uses the standard copyFileSync path in `syncResourcesToVersion`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function compileRulesForAgent(agentId: AgentId, version: string): {
|
|
59
|
+
compiled: boolean;
|
|
60
|
+
compiledPath: string;
|
|
61
|
+
sources: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Recompile rules if stale. Safe to call on every agent invocation — the
|
|
65
|
+
* staleness check is fast (sha256 of 8-10 small files, ~10-20ms). Returns
|
|
66
|
+
* true if a recompile happened, false otherwise.
|
|
67
|
+
*/
|
|
68
|
+
export declare function ensureRulesFresh(agentId: AgentId, version: string): boolean;
|
|
69
|
+
export interface ProjectCompileResult {
|
|
70
|
+
/** True when cwd/AGENTS.md was newly written or rewritten. */
|
|
71
|
+
compiled: boolean;
|
|
72
|
+
/** Absolute path to cwd/AGENTS.md. Empty when no project rules dir was present. */
|
|
73
|
+
agentsPath: string;
|
|
74
|
+
/** Per-agent instruction filenames symlinked (or copied) to AGENTS.md. */
|
|
75
|
+
symlinks: string[];
|
|
76
|
+
/** Number of source files inlined (root + recursive @-imports). */
|
|
77
|
+
sources: number;
|
|
78
|
+
/** Per-agent files we left alone because the user wrote/owns them. */
|
|
79
|
+
skippedClobber: string[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compile project-scope rules into a workspace's root memory files so each
|
|
83
|
+
* agent's native loader picks them up.
|
|
84
|
+
*
|
|
85
|
+
* Composes rules from all available layers (project > user > extras > system)
|
|
86
|
+
* with project highest priority — so a project's `subrules/` and `rules.yaml`
|
|
87
|
+
* shadow user/system fragments and presets. Writes `cwd/AGENTS.md` with
|
|
88
|
+
* COMPILED_HEADER_PROJECT and creates symlinks (CLAUDE.md, GEMINI.md,
|
|
89
|
+
* .cursorrules, etc.) → AGENTS.md so every agent finds its expected file at
|
|
90
|
+
* cwd. The agent's own loader merges this project-level file with its
|
|
91
|
+
* user-level rules (in version home) at runtime.
|
|
92
|
+
*
|
|
93
|
+
* Don't-clobber guard: if `cwd/AGENTS.md` exists without our header, the user
|
|
94
|
+
* authored it — leave it alone and report via `skippedClobber`. Same for any
|
|
95
|
+
* pre-existing per-agent file or symlink that doesn't already point at
|
|
96
|
+
* AGENTS.md.
|
|
97
|
+
*
|
|
98
|
+
* No-op when `cwd/.agents/rules/` does not exist. Idempotent on repeated
|
|
99
|
+
* calls — content equality short-circuits the write.
|
|
100
|
+
*/
|
|
101
|
+
export declare function compileRulesForProject(cwd: string, opts?: {
|
|
102
|
+
preset?: string;
|
|
103
|
+
layers?: RulesLayer[];
|
|
104
|
+
}): ProjectCompileResult;
|