@joenandez/academy 0.4.0 → 0.5.13
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/.claude-plugin/marketplace.json +5 -0
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +415 -0
- package/README.md +67 -122
- package/hooks/backstory/.claude-plugin/plugin.json +11 -0
- package/hooks/backstory/LICENSE +21 -0
- package/hooks/backstory/SOURCE +2 -0
- package/hooks/backstory/bin/backstory +9 -0
- package/hooks/backstory/hooks/hooks.json +45 -0
- package/hooks/backstory/scripts/knowledge.mjs +87 -0
- package/hooks/backstory/scripts/memory.mjs +685 -0
- package/hooks/backstory/scripts/tool-tick.mjs +1 -0
- package/hooks/backstory/skills/capture/SKILL.md +71 -0
- package/hooks/backstory/skills/capture/references/knowledge-capture-input.json +16 -0
- package/hooks/backstory/skills/capture/references/tagging-policy.md +29 -0
- package/hooks/backstory/skills/memory/SKILL.md +70 -0
- package/hooks/backstory/skills/setup/SKILL.md +85 -0
- package/hooks/context_metrics.mjs +67 -0
- package/hooks/hook_runtime.mjs +43 -2
- package/hooks/memory_bridge.mjs +286 -52
- package/hooks/memory_store.mjs +78 -26
- package/hooks/register_session.mjs +76 -14
- package/hooks/roque/.claude-plugin/plugin.json +6 -0
- package/hooks/roque/LICENSE +21 -0
- package/hooks/roque/SOURCE +1 -0
- package/hooks/roque/hooks.json +38 -0
- package/hooks/roque/package.json +3 -0
- package/hooks/roque/roque.js +1690 -0
- package/hooks/sync_memory.mjs +11 -7
- package/package.json +10 -3
- package/scripts/cli/agent-gate.mjs +70 -0
- package/scripts/cli/agent-id.mjs +37 -0
- package/scripts/cli/archive.mjs +39 -38
- package/scripts/cli/archived.mjs +4 -2
- package/scripts/cli/args-kinds.mjs +33 -0
- package/scripts/cli/args-notes.mjs +42 -0
- package/scripts/cli/args-owner.mjs +37 -0
- package/scripts/cli/args-run.mjs +38 -0
- package/scripts/cli/args.mjs +47 -104
- package/scripts/cli/beads.mjs +57 -0
- package/scripts/cli/codex.mjs +66 -12
- package/scripts/cli/core.mjs +117 -41
- package/scripts/cli/create.mjs +224 -76
- package/scripts/cli/curator-feed-codex.mjs +81 -0
- package/scripts/cli/curator-feed-observations.mjs +97 -0
- package/scripts/cli/curator-feed-scope.mjs +124 -0
- package/scripts/cli/curator-feed-sources.mjs +172 -0
- package/scripts/cli/curator-feed.mjs +318 -0
- package/scripts/cli/default-name.mjs +73 -0
- package/scripts/cli/doctor.mjs +67 -2
- package/scripts/cli/eventlog.mjs +6 -2
- package/scripts/cli/exit-code.mjs +9 -0
- package/scripts/cli/first-run.mjs +115 -0
- package/scripts/cli/heartbeat.mjs +135 -0
- package/scripts/cli/helm-skip.mjs +20 -0
- package/scripts/cli/hire-headless.mjs +37 -22
- package/scripts/cli/hire.mjs +8 -2
- package/scripts/cli/inspect.mjs +36 -18
- package/scripts/cli/lifecycle.mjs +95 -49
- package/scripts/cli/main.mjs +32 -2
- package/scripts/cli/memory-env.mjs +124 -0
- package/scripts/cli/migrate.mjs +13 -7
- package/scripts/cli/next-steps.mjs +13 -0
- package/scripts/cli/nightly-launch.mjs +118 -0
- package/scripts/cli/nightly.mjs +228 -0
- package/scripts/cli/notes.mjs +58 -8
- package/scripts/cli/owner-config.mjs +181 -0
- package/scripts/cli/owner-jobs.mjs +201 -0
- package/scripts/cli/prompt-append.mjs +135 -0
- package/scripts/cli/rename.mjs +90 -109
- package/scripts/cli/run-context.mjs +50 -0
- package/scripts/cli/run.mjs +236 -79
- package/scripts/cli/scaffold.mjs +119 -126
- package/scripts/cli/schedule-errors.mjs +70 -0
- package/scripts/cli/sessions.mjs +10 -3
- package/scripts/cli/skills-check.mjs +147 -0
- package/scripts/cli/skills.mjs +170 -0
- package/scripts/cli/start.mjs +201 -0
- package/scripts/cli/templates.mjs +119 -45
- package/scripts/cli/tldr.mjs +184 -0
- package/scripts/cli/toml-string.mjs +76 -0
- package/scripts/cli/user-work-view.mjs +48 -0
- package/scripts/cli/writable-dirs.mjs +23 -0
- package/scripts/cli/yaml.mjs +1 -1
- package/skills/hire/SKILL.md +112 -100
- package/skills/hire/references/owner.md +68 -0
- package/templates/agents/claude-code/knowledge-curator.md +5 -2
- package/templates/agents/codex/knowledge-curator.toml +2 -1
- package/templates/operating-model/base.md +78 -0
- package/templates/operating-model/owner.md +11 -0
- package/templates/operating-model/principal.md +9 -0
- package/templates/operating-model/specialist.md +9 -0
- package/templates/packs/owner/craft.md +32 -0
- package/templates/packs/owner/identity.md +13 -0
- package/templates/packs/owner/mandate.md +26 -0
- package/templates/packs/owner/owner/SKILL.md +118 -0
- package/templates/packs/owner/owner-feedback/SKILL.md +49 -0
- package/templates/packs/owner/owner-heartbeat/SKILL.md +35 -0
- package/templates/packs/owner/owner-nightly/SKILL.md +114 -0
- package/templates/packs/owner/owner-review/SKILL.md +70 -0
- package/templates/packs/owner/owner-work/SKILL.md +53 -0
- package/templates/packs/principal/craft.md +27 -0
- package/templates/packs/principal/identity.md +13 -0
- package/templates/packs/principal/mandate.md +23 -0
- package/templates/packs/principal/principal-brief/SKILL.md +44 -0
- package/templates/packs/principal/principal-intro/SKILL.md +95 -0
- package/templates/packs/principal/principal-nightly/SKILL.md +152 -0
- package/templates/skills/check-in/SKILL.md +34 -24
- package/templates/skills/knowledge-curation/SKILL.md +89 -48
- package/templates/skills/local-rules/SKILL.md +86 -0
- package/templates/skills/nightly-consolidation/SKILL.md +131 -100
- package/templates/skills/skill-method/SKILL.md +79 -0
- package/templates/skills/user-nightly/SKILL.md +143 -0
- package/templates/skills/work-graph/SKILL.md +75 -0
- package/conformance/README.md +0 -60
- package/conformance/discovery.test.mjs +0 -140
- package/conformance/envelope.test.mjs +0 -185
- package/conformance/error-codes.test.mjs +0 -125
- package/conformance/harness.mjs +0 -180
- package/conformance/identity.test.mjs +0 -125
- package/docs/integration-guide.md +0 -1042
- package/templates/skills/self-update/SKILL.md +0 -121
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ACADEMY_ROOT, RuntimeUnavailableError } from './core.mjs';
|
|
5
|
+
|
|
6
|
+
const PRODUCT = 'tldr-agents';
|
|
7
|
+
const ACTIVATION_MANIFEST = path.join('release', 'activation-manifest.json');
|
|
8
|
+
const ORPHAN_MARKER = '.orphaned_at';
|
|
9
|
+
const REGISTRY_PLUGIN_NAME = 'tldr';
|
|
10
|
+
const FALLBACK_RELEASE = path.join('.tldr-agents', 'agent', 'install', 'tldr-agent');
|
|
11
|
+
|
|
12
|
+
function isAbsoluteExecutable(file) {
|
|
13
|
+
if (!path.isAbsolute(file)) return false;
|
|
14
|
+
try {
|
|
15
|
+
const stat = fs.statSync(file);
|
|
16
|
+
return stat.isFile() && (stat.mode & 0o111) !== 0;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function manifestNamesTldrAgents(release) {
|
|
23
|
+
try {
|
|
24
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(release, ACTIVATION_MANIFEST), 'utf8'));
|
|
25
|
+
return manifest?.product === PRODUCT;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function verifyRelease(release, source) {
|
|
32
|
+
const bin = path.join(release, 'bin', PRODUCT);
|
|
33
|
+
if (!isAbsoluteExecutable(bin)) {
|
|
34
|
+
throw new RuntimeUnavailableError(
|
|
35
|
+
`Failed to resolve executable "${PRODUCT}" from ${source}: ${bin} is not executable.`,
|
|
36
|
+
{ executable: PRODUCT },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
if (!manifestNamesTldrAgents(release)) {
|
|
40
|
+
throw new RuntimeUnavailableError(
|
|
41
|
+
`Failed to resolve executable "${PRODUCT}" from ${source}: ${release} is not a tldr-agents release.`,
|
|
42
|
+
{ executable: PRODUCT },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return bin;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function verifyOverride(bin) {
|
|
49
|
+
const binDir = path.dirname(bin);
|
|
50
|
+
if (!path.isAbsolute(bin) || path.basename(bin) !== PRODUCT || path.basename(binDir) !== 'bin') {
|
|
51
|
+
throw new RuntimeUnavailableError(
|
|
52
|
+
`Failed to resolve executable "${PRODUCT}" from SUBSPACE_TLDR_AGENTS_BIN: ${bin} is not an absolute tldr-agents executable.`,
|
|
53
|
+
{ executable: PRODUCT },
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return verifyRelease(path.dirname(binDir), 'SUBSPACE_TLDR_AGENTS_BIN');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function registryInstalls(registryPath) {
|
|
60
|
+
let registry;
|
|
61
|
+
try {
|
|
62
|
+
registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return Object.entries(registry?.plugins ?? {})
|
|
67
|
+
.filter(
|
|
68
|
+
([key, installs]) => key.split('@')[0] === REGISTRY_PLUGIN_NAME && Array.isArray(installs),
|
|
69
|
+
)
|
|
70
|
+
.flatMap(([, installs]) => installs)
|
|
71
|
+
.filter((entry) => typeof entry?.installPath === 'string')
|
|
72
|
+
.filter((entry) => !fs.existsSync(path.join(entry.installPath, ORPHAN_MARKER)))
|
|
73
|
+
.map((entry) => ({ release: entry.installPath, lastUpdated: Date.parse(entry.lastUpdated) }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function newestFirst(left, right) {
|
|
77
|
+
const a = Number.isNaN(left.lastUpdated) ? -Infinity : left.lastUpdated;
|
|
78
|
+
const b = Number.isNaN(right.lastUpdated) ? -Infinity : right.lastUpdated;
|
|
79
|
+
return b - a;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveFromRegistry(homeDir) {
|
|
83
|
+
const registryPath = path.join(homeDir, '.claude', 'plugins', 'installed_plugins.json');
|
|
84
|
+
const installs = registryInstalls(registryPath).sort(newestFirst);
|
|
85
|
+
if (installs.length === 0) return null;
|
|
86
|
+
let newestRejection = null;
|
|
87
|
+
for (const install of installs) {
|
|
88
|
+
try {
|
|
89
|
+
return verifyRelease(install.release, 'plugin registry');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
newestRejection ??= error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw newestRejection;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The tldr package Academy depends on, for a machine with no plugin install
|
|
98
|
+
// (WSL, Linux). Walks up like Node resolution: npm nests it under Academy, or
|
|
99
|
+
// dedupes it to a sibling global package. Not require.resolve: tldr's empty
|
|
100
|
+
// `exports` map blocks it. Its package root is also its Claude plugin root.
|
|
101
|
+
function bundledTldrRoot(academyRoot) {
|
|
102
|
+
for (let dir = academyRoot; ; dir = path.dirname(dir)) {
|
|
103
|
+
try {
|
|
104
|
+
return fs.realpathSync(path.join(dir, 'node_modules', '@joenandez', 'tldr'));
|
|
105
|
+
} catch {
|
|
106
|
+
if (path.dirname(dir) === dir) return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveTldrAgents(env, academyRoot) {
|
|
112
|
+
const override = env.SUBSPACE_TLDR_AGENTS_BIN;
|
|
113
|
+
if (override) return { bin: verifyOverride(override) };
|
|
114
|
+
|
|
115
|
+
const homeDir = os.homedir();
|
|
116
|
+
if (!path.isAbsolute(homeDir)) {
|
|
117
|
+
throw new RuntimeUnavailableError(`Failed to resolve executable "${PRODUCT}".`, {
|
|
118
|
+
executable: PRODUCT,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const registryBin = resolveFromRegistry(homeDir);
|
|
123
|
+
if (registryBin) return { bin: registryBin };
|
|
124
|
+
|
|
125
|
+
const fallback = path.join(homeDir, FALLBACK_RELEASE);
|
|
126
|
+
if (fs.existsSync(path.join(fallback, 'bin', PRODUCT))) {
|
|
127
|
+
return { bin: verifyRelease(fallback, 'fallback install') };
|
|
128
|
+
}
|
|
129
|
+
const bundled = bundledTldrRoot(academyRoot);
|
|
130
|
+
if (!bundled) {
|
|
131
|
+
throw new RuntimeUnavailableError(
|
|
132
|
+
`Failed to resolve executable "${PRODUCT}" from plugin registry, fallback install, or bundled package.`,
|
|
133
|
+
{ executable: PRODUCT },
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return { bin: verifyRelease(bundled, 'bundled package'), root: bundled };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveTldrAgentsBinary(env = process.env, academyRoot = ACADEMY_ROOT) {
|
|
140
|
+
return resolveTldrAgents(env, academyRoot).bin;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The bundled package's plugin root, or null when tldr resolves from anywhere
|
|
144
|
+
// else (or nowhere): only then must a launch load the bundled plugin itself.
|
|
145
|
+
export function bundledTldrPluginRoot(env = process.env, academyRoot = ACADEMY_ROOT) {
|
|
146
|
+
try {
|
|
147
|
+
return resolveTldrAgents(env, academyRoot).root ?? null;
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// A hung tldr must not hold an agent's lifecycle lock (others wait 5 s for it)
|
|
154
|
+
// without bound. Generous because the machine can run at very high load.
|
|
155
|
+
// ACADEMY_TLDR_TIMEOUT_MS is the test override, as ACADEMY_HIRE_TIMEOUT_MS is for hire.
|
|
156
|
+
const DEFAULT_TLDR_TIMEOUT_MS = 60_000;
|
|
157
|
+
|
|
158
|
+
export function tldrTimeoutMs(env = process.env) {
|
|
159
|
+
const configured = Number.parseInt(env.ACADEMY_TLDR_TIMEOUT_MS ?? '', 10);
|
|
160
|
+
return Number.isInteger(configured) && configured > 0 ? configured : DEFAULT_TLDR_TIMEOUT_MS;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// The spawnSync options every tldr-agents call shares, timeout included.
|
|
164
|
+
export const tldrSpawnOptions = (cwd, extra = {}) => ({
|
|
165
|
+
cwd,
|
|
166
|
+
encoding: 'utf8',
|
|
167
|
+
env: process.env,
|
|
168
|
+
timeout: tldrTimeoutMs(),
|
|
169
|
+
...extra,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export function spawnTldrTasksArgs(args) {
|
|
173
|
+
return ['tasks', ...args];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// tldr starts a job's run with its own context env, built by its executor
|
|
177
|
+
// (`buildHelmContextEnv` for an adapter run, the runtime env for a command
|
|
178
|
+
// job): HELM_JOB_ID, HELM_RUN_ID, HELM_REPORT_CMD, HELM_SCOPE_ID,
|
|
179
|
+
// HELM_SCOPE_CWD. An adapter (`--provider`) run gets no job env: tldr refuses
|
|
180
|
+
// `--env-json` on a provider job, and would not pass it on. The job id is the
|
|
181
|
+
// one fact about the run that always arrives, so `academy run` reads it here.
|
|
182
|
+
export function schedulerJobId(env = process.env) {
|
|
183
|
+
return env.HELM_JOB_ID || null;
|
|
184
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The string forms of a TOML value, as Codex reads a `-c key=value` override.
|
|
2
|
+
// Anything that is not one complete, valid TOML string is the raw text, which
|
|
3
|
+
// is what Codex does with a value that does not parse.
|
|
4
|
+
|
|
5
|
+
const SIMPLE_ESCAPES = { b: '\b', t: '\t', n: '\n', f: '\f', r: '\r', '"': '"', '\\': '\\' };
|
|
6
|
+
const HEX_WIDTH = { u: 4, U: 8 };
|
|
7
|
+
|
|
8
|
+
function codePointText(hex) {
|
|
9
|
+
const point = Number.parseInt(hex, 16);
|
|
10
|
+
const valid =
|
|
11
|
+
/^[0-9a-fA-F]+$/.test(hex) && point <= 0x10ffff && !(point >= 0xd800 && point <= 0xdfff);
|
|
12
|
+
return valid ? String.fromCodePoint(point) : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Reads the escape that starts at `raw[at]` (the backslash). Returns the text it
|
|
16
|
+
// stands for and where reading resumes, or undefined for an invalid escape.
|
|
17
|
+
function readEscape(raw, at, multiline) {
|
|
18
|
+
const kind = raw[at + 1];
|
|
19
|
+
if (kind in SIMPLE_ESCAPES) return { text: SIMPLE_ESCAPES[kind], next: at + 2 };
|
|
20
|
+
if (kind in HEX_WIDTH) {
|
|
21
|
+
const end = at + 2 + HEX_WIDTH[kind];
|
|
22
|
+
const text = codePointText(raw.slice(at + 2, end));
|
|
23
|
+
return text === undefined ? undefined : { text, next: end };
|
|
24
|
+
}
|
|
25
|
+
// A line-ending backslash drops the newline and the whitespace after it.
|
|
26
|
+
const trimmed = /^[ \t]*\r?\n\s*/.exec(raw.slice(at + 1));
|
|
27
|
+
return multiline && trimmed ? { text: '', next: at + 1 + trimmed[0].length } : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A quote either closes the string (`closed` is the content it adds) or is
|
|
31
|
+
// content itself.
|
|
32
|
+
function readQuote(raw, at, quote, multiline) {
|
|
33
|
+
let run = 1;
|
|
34
|
+
while (raw[at + run] === quote) run++;
|
|
35
|
+
if (!multiline) return at + 1 === raw.length ? { closed: '' } : undefined;
|
|
36
|
+
if (run < 3) return { text: quote.repeat(run), next: at + run };
|
|
37
|
+
// At most two quotes may sit against the closing three, as content.
|
|
38
|
+
return run <= 5 && at + run === raw.length ? { closed: quote.repeat(run - 3) } : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readStep(raw, at, quote, multiline) {
|
|
42
|
+
const ch = raw[at];
|
|
43
|
+
if (ch === quote) return readQuote(raw, at, quote, multiline);
|
|
44
|
+
if (ch === '\\' && quote === '"') return readEscape(raw, at, multiline);
|
|
45
|
+
return ch === '\n' && !multiline ? undefined : { text: ch, next: at + 1 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function scanString(raw, quote, multiline) {
|
|
49
|
+
const open = quote.repeat(multiline ? 3 : 1);
|
|
50
|
+
if (!raw.startsWith(open)) return undefined;
|
|
51
|
+
let at = open.length;
|
|
52
|
+
if (multiline) at += /^\r?\n/.exec(raw.slice(at))?.[0].length ?? 0;
|
|
53
|
+
let out = '';
|
|
54
|
+
while (at < raw.length) {
|
|
55
|
+
const step = readStep(raw, at, quote, multiline);
|
|
56
|
+
if (!step) return undefined;
|
|
57
|
+
if (step.closed !== undefined) return out + step.closed;
|
|
58
|
+
out += step.text;
|
|
59
|
+
at = step.next;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function decodeTomlString(raw) {
|
|
65
|
+
const forms = [
|
|
66
|
+
['"', true],
|
|
67
|
+
['"', false],
|
|
68
|
+
["'", true],
|
|
69
|
+
["'", false],
|
|
70
|
+
];
|
|
71
|
+
for (const [quote, multiline] of forms) {
|
|
72
|
+
const text = scanString(raw, quote, multiline);
|
|
73
|
+
if (text !== undefined) return text;
|
|
74
|
+
}
|
|
75
|
+
return raw;
|
|
76
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const AGENTS_LINE = /^agents:/;
|
|
2
|
+
|
|
3
|
+
// A section is `## ` through the line before the next `## `; the preamble is
|
|
4
|
+
// everything before the first. Only the curator writes the `agents:` line, and
|
|
5
|
+
// it never reaches a prompt. `names` are the agent's current name and fixed id:
|
|
6
|
+
// a rename changes only the name, and the curator may still list the id.
|
|
7
|
+
export function filterUserWork(text, ...names) {
|
|
8
|
+
const [preamble, ...sections] = text.split('\n').reduce(
|
|
9
|
+
(blocks, line) => {
|
|
10
|
+
if (line.startsWith('## ')) blocks.push([]);
|
|
11
|
+
blocks.at(-1).push(line);
|
|
12
|
+
return blocks;
|
|
13
|
+
},
|
|
14
|
+
[[]],
|
|
15
|
+
);
|
|
16
|
+
const kept = sections.filter((lines) => {
|
|
17
|
+
const named = lines.find((line) => AGENTS_LINE.test(line));
|
|
18
|
+
return (
|
|
19
|
+
named !== undefined &&
|
|
20
|
+
named
|
|
21
|
+
.slice('agents:'.length)
|
|
22
|
+
.split(',')
|
|
23
|
+
.some((agent) => names.includes(agent.trim()))
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
const view = [preamble, ...kept.map((lines) => lines.filter((line) => !AGENTS_LINE.test(line)))]
|
|
27
|
+
.flat()
|
|
28
|
+
.join('\n')
|
|
29
|
+
.trimEnd();
|
|
30
|
+
return { view, sections: sections.length, kept: kept.length };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The part of the user's user-work.md that names `name`: preamble plus its sections. */
|
|
34
|
+
export function userWorkView(text, name) {
|
|
35
|
+
return filterUserWork(text, name).view;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function logUserWorkView(agent, surfaces) {
|
|
39
|
+
const { sections, kept } = surfaces.find(({ name }) => name === 'user-work');
|
|
40
|
+
console.error(
|
|
41
|
+
JSON.stringify({
|
|
42
|
+
event: 'run.user_work_view',
|
|
43
|
+
params: { agent },
|
|
44
|
+
result: { sections, kept },
|
|
45
|
+
status: 'filtered',
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ACADEMY_HOME, kindOf } from './core.mjs';
|
|
4
|
+
|
|
5
|
+
/** The Lane 2 proposal queue a kind's skills write to. */
|
|
6
|
+
export const proposalsDir = (kind) => join(ACADEMY_HOME, 'proposals', kind);
|
|
7
|
+
|
|
8
|
+
// Every kind is granted ACADEMY_HOME: its proposal queue, user.md and the other
|
|
9
|
+
// shared files live there, outside its home and outside any project. The grant
|
|
10
|
+
// is a directory because a file path is not a directory grant. It reaches more
|
|
11
|
+
// than the queue, so the one-writer rule for shared files is the operating
|
|
12
|
+
// model's to keep, not the sandbox's. The queue is created first, which also
|
|
13
|
+
// creates ACADEMY_HOME: a provider may refuse an --add-dir that does not exist.
|
|
14
|
+
export function sharedWritableDirArgs(dir) {
|
|
15
|
+
mkdirSync(proposalsDir(kindOf(dir)), { recursive: true });
|
|
16
|
+
return ['--add-dir', ACADEMY_HOME];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Codex sandboxes writes to its -C root, so a project run must also be granted
|
|
20
|
+
// the agent home.
|
|
21
|
+
export function codexWritableDirArgs(dir) {
|
|
22
|
+
return ['--add-dir', dir, ...sharedWritableDirArgs(dir)];
|
|
23
|
+
}
|
package/scripts/cli/yaml.mjs
CHANGED
|
@@ -44,7 +44,7 @@ export function writeAgentYamlScalar(dir, key, value) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// The same write for a caller that already holds the agent's lifecycle lock.
|
|
47
|
-
// `rename`
|
|
47
|
+
// `rename` rewrites `name:` inside the agent's lifecycle lock, so
|
|
48
48
|
// taking the lock a second time here would block against its own holder until
|
|
49
49
|
// the timeout and report `lock_timeout` for work that was never contended.
|
|
50
50
|
export function rewriteAgentYamlScalar(dir, key, value) {
|