@skrr-ai/cli 0.1.45 → 0.1.46
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/dist/base-command.d.ts +13 -1
- package/dist/base-command.js +37 -3
- package/dist/commands/agents/actions/create.js +11 -4
- package/dist/commands/browser/cloud-agents.js +4 -0
- package/dist/commands/code/handover.d.ts +11 -0
- package/dist/commands/code/handover.js +105 -1
- package/dist/commands/commitments/handover.d.ts +29 -0
- package/dist/commands/commitments/handover.js +99 -0
- package/dist/commands/followups/watch.js +40 -5
- package/dist/commands/tasks/fork.d.ts +32 -0
- package/dist/commands/tasks/fork.js +171 -0
- package/dist/commands/tasks/handover.d.ts +23 -0
- package/dist/commands/tasks/handover.js +205 -0
- package/dist/commands/tasks/promote.d.ts +23 -0
- package/dist/commands/tasks/promote.js +82 -0
- package/dist/commands/tasks/runs.d.ts +16 -0
- package/dist/commands/tasks/runs.js +46 -1
- package/dist/commands/tasks/show.d.ts +23 -0
- package/dist/commands/tasks/show.js +59 -0
- package/dist/lib/api-fetch.d.ts +7 -1
- package/dist/lib/api-fetch.js +9 -2
- package/dist/lib/code-handover.d.ts +60 -0
- package/dist/lib/code-handover.js +120 -0
- package/dist/lib/commitments.d.ts +22 -0
- package/dist/lib/commitments.js +42 -0
- package/dist/lib/dedicated-wait.js +22 -1
- package/dist/lib/followups.d.ts +18 -0
- package/dist/lib/followups.js +44 -0
- package/dist/lib/node-adapter.js +6 -0
- package/dist/lib/task-extras.d.ts +44 -0
- package/dist/lib/task-extras.js +73 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +45 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessSkillLayout.d.ts +65 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessSkillLayout.js +99 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +10 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +7 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +45 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessSkillLayout.d.ts +65 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessSkillLayout.js +94 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +5 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +2242 -2234
- package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
- package/oclif.manifest.json +35697 -35300
- package/package.json +2 -2
package/dist/lib/task-extras.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.taskChatApi = exports.taskSelfScheduleApi = exports.taskLookupApi = exports.taskWorkflowApi = void 0;
|
|
4
4
|
exports.taskCompletePath = taskCompletePath;
|
|
5
|
+
exports.taskRunHandover = taskRunHandover;
|
|
6
|
+
exports.taskRunFork = taskRunFork;
|
|
7
|
+
exports.taskRunPromote = taskRunPromote;
|
|
8
|
+
exports.parseExecutionTargetRef = parseExecutionTargetRef;
|
|
5
9
|
/**
|
|
6
10
|
* task-extras.ts — the `/api/tasks` endpoints that have no `dataService.*`
|
|
7
11
|
* wrapper, gathered so every one of them is reachable from `skrr`.
|
|
@@ -18,6 +22,7 @@ exports.taskCompletePath = taskCompletePath;
|
|
|
18
22
|
*/
|
|
19
23
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
20
24
|
const triggers_1 = require("./triggers");
|
|
25
|
+
const api_fetch_1 = require("./api-fetch");
|
|
21
26
|
const MOUNT = '/api/tasks';
|
|
22
27
|
const base = (id) => `${MOUNT}/${encodeURIComponent(id)}`;
|
|
23
28
|
exports.taskWorkflowApi = {
|
|
@@ -78,6 +83,74 @@ exports.taskSelfScheduleApi = {
|
|
|
78
83
|
function taskCompletePath(taskId) {
|
|
79
84
|
return `${base(taskId)}/complete`;
|
|
80
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* `POST /api/tasks/:taskId/runs/:runId/handover` — the B1 admission path
|
|
88
|
+
* (docs/architecture/execution-handover-impl-contracts.md): mint a new run on a
|
|
89
|
+
* different agent/target, resumed from the source run's Resume Point.
|
|
90
|
+
*
|
|
91
|
+
* `apiFetch` rather than `request.post`: a handover ADMITS a run, so the write
|
|
92
|
+
* needs the shared `X-Idempotency-Key` stamping apiFetch applies — a retried
|
|
93
|
+
* request must not double-admit. Kept in the lib so `domain-api-parity` sees
|
|
94
|
+
* the endpoint — a command that builds its own `/api/tasks/...` string is
|
|
95
|
+
* invisible to the coverage check.
|
|
96
|
+
*/
|
|
97
|
+
function taskRunHandover(taskId, runId, body) {
|
|
98
|
+
return (0, api_fetch_1.apiFetch)(`${base(taskId)}/runs/${encodeURIComponent(runId)}/handover`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `POST /api/tasks/:taskId/runs/:runId/fork` — B8. Admits a SIBLING run from
|
|
105
|
+
* the same Resume Point, so both attempts are live and neither is canonical
|
|
106
|
+
* until `promote`. Distinct from handover, which continues the work once.
|
|
107
|
+
*/
|
|
108
|
+
function taskRunFork(taskId, runId, body) {
|
|
109
|
+
return (0, api_fetch_1.apiFetch)(`${base(taskId)}/runs/${encodeURIComponent(runId)}/fork`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
body,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* `POST /api/tasks/:taskId/runs/:runId/promote` — B8. Fixes one branch of a
|
|
116
|
+
* parallel group as canonical and CANCELS the live losers, which is why it has
|
|
117
|
+
* a surface of its own rather than riding a flag on fork.
|
|
118
|
+
*/
|
|
119
|
+
function taskRunPromote(taskId, runId, body) {
|
|
120
|
+
return (0, api_fetch_1.apiFetch)(`${base(taskId)}/runs/${encodeURIComponent(runId)}/promote`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
body,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Selectors the platform reserves for its own managed targets. */
|
|
126
|
+
const AGENT_WORKER_SELECTORS = new Set(['agent-worker', '__oversky_worker__']);
|
|
127
|
+
const HOSTED_MACHINE_SELECTORS = new Set(['hosted-machine', '__oversky_hosted_machine__']);
|
|
128
|
+
/**
|
|
129
|
+
* Turn a `--harness` value into the `executionTargetRef` the API parses.
|
|
130
|
+
*
|
|
131
|
+
* The route validates an OBJECT (`{kind, ref?}`); passing the flag through as a
|
|
132
|
+
* string failed its zod parse, so every `--harness` invocation returned
|
|
133
|
+
* `400 Invalid request data` — the harness axis, which is half of what handover
|
|
134
|
+
* is for, was unreachable from the CLI and untested.
|
|
135
|
+
*
|
|
136
|
+
* The kind is INFERRED rather than asked for, because an operator naming a
|
|
137
|
+
* machine should not also have to name its category: the two managed selectors
|
|
138
|
+
* are reserved words, a `hosted-machine:<sessionId>` form addresses one
|
|
139
|
+
* session, and everything else is a harness id — which is what the flag is
|
|
140
|
+
* called.
|
|
141
|
+
*/
|
|
142
|
+
function parseExecutionTargetRef(value) {
|
|
143
|
+
const trimmed = value.trim();
|
|
144
|
+
if (AGENT_WORKER_SELECTORS.has(trimmed))
|
|
145
|
+
return { kind: 'agent-worker' };
|
|
146
|
+
if (HOSTED_MACHINE_SELECTORS.has(trimmed))
|
|
147
|
+
return { kind: 'hosted-machine' };
|
|
148
|
+
const hosted = /^hosted-machine:(.+)$/.exec(trimmed);
|
|
149
|
+
if (hosted)
|
|
150
|
+
return { kind: 'hosted-machine', ref: hosted[1] };
|
|
151
|
+
const harness = /^harness:(.+)$/.exec(trimmed);
|
|
152
|
+
return { kind: 'harness', ref: harness ? harness[1] : trimmed };
|
|
153
|
+
}
|
|
81
154
|
exports.taskChatApi = {
|
|
82
155
|
transcriptSources: (id, query = {}) => data_provider_1.request.get((0, triggers_1.withQuery)(`${base(id)}/transcript/sources`, query)),
|
|
83
156
|
reindexTranscript: (id) => data_provider_1.request.post(`${base(id)}/transcript/reindex`, {}),
|
|
@@ -313,6 +313,13 @@ declare const ENGINE_ENV_SUFFIXES: Readonly<{
|
|
|
313
313
|
allowUpstreamEgress: "ALLOW_UPSTREAM_EGRESS";
|
|
314
314
|
compatOpencode: "COMPAT_OPENCODE";
|
|
315
315
|
noCompatPrompts: "NO_COMPAT_PROMPTS";
|
|
316
|
+
/**
|
|
317
|
+
* Advisory input-token budget for a managed session, set by the daemon from
|
|
318
|
+
* the server-issued `requestEnvelope.maxInputTokens` so the engine's
|
|
319
|
+
* auto-compaction can trigger before the relay refuses the request. The
|
|
320
|
+
* engine must treat it as a hint: enforcement stays server-side.
|
|
321
|
+
*/
|
|
322
|
+
maxInputTokens: "MAX_INPUT_TOKENS";
|
|
316
323
|
}>;
|
|
317
324
|
export type FirstPartyHarnessEngineEnvKey = keyof typeof ENGINE_ENV_SUFFIXES;
|
|
318
325
|
/**
|
|
@@ -350,7 +357,7 @@ export declare const FIRST_PARTY_HARNESS_ENGINE: Readonly<{
|
|
|
350
357
|
* `readFirstPartyHarnessEngineEnv` to read one the way the engine does and
|
|
351
358
|
* `firstPartyHarnessEngineEnvEntries` to set one every engine build honours.
|
|
352
359
|
*/
|
|
353
|
-
env: Readonly<Record<"appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts", string>>;
|
|
360
|
+
env: Readonly<Record<"appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts" | "maxInputTokens", string>>;
|
|
354
361
|
}>;
|
|
355
362
|
/**
|
|
356
363
|
* Every name the engine reads for one purpose, current namespace first — the
|
|
@@ -450,6 +450,13 @@ const ENGINE_ENV_SUFFIXES = Object.freeze({
|
|
|
450
450
|
allowUpstreamEgress: 'ALLOW_UPSTREAM_EGRESS',
|
|
451
451
|
compatOpencode: 'COMPAT_OPENCODE',
|
|
452
452
|
noCompatPrompts: 'NO_COMPAT_PROMPTS',
|
|
453
|
+
/**
|
|
454
|
+
* Advisory input-token budget for a managed session, set by the daemon from
|
|
455
|
+
* the server-issued `requestEnvelope.maxInputTokens` so the engine's
|
|
456
|
+
* auto-compaction can trigger before the relay refuses the request. The
|
|
457
|
+
* engine must treat it as a hint: enforcement stays server-side.
|
|
458
|
+
*/
|
|
459
|
+
maxInputTokens: 'MAX_INPUT_TOKENS',
|
|
453
460
|
});
|
|
454
461
|
function engineEnvName(appName, key) {
|
|
455
462
|
return `${appName.toUpperCase().replace(/-/g, '_')}_${ENGINE_ENV_SUFFIXES[key]}`;
|
|
@@ -64,6 +64,13 @@ const firstPartyHarness_js_1 = require("./firstPartyHarness.js");
|
|
|
64
64
|
function olderSpellings() {
|
|
65
65
|
return (0, firstPartyHarness_js_1.firstPartyHarnessArtifactSpellings)().filter((spelling) => spelling !== firstPartyHarness_js_1.FIRST_PARTY_HARNESS.provider);
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Entries rescued from a legacy home when the canonical one already exists as a
|
|
69
|
+
* shell. `bin` is what managed resolution needs; `skills` is the user's own
|
|
70
|
+
* data. Deliberately a short, named list rather than "everything": a merge this
|
|
71
|
+
* module cannot enumerate (there is no readdir here) would be guessing.
|
|
72
|
+
*/
|
|
73
|
+
const STRANDED_HOME_ENTRIES = ['bin', 'skills'];
|
|
67
74
|
/** Suffix of the rollback copy the installer keeps beside the binary. */
|
|
68
75
|
exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = '.previous';
|
|
69
76
|
/**
|
|
@@ -186,6 +193,44 @@ function migrateFirstPartyHarnessHome(options = {}) {
|
|
|
186
193
|
break;
|
|
187
194
|
}
|
|
188
195
|
}
|
|
196
|
+
else if (isRealDirectory(fs.lstat(canonicalHome))) {
|
|
197
|
+
// 1b. The canonical home EXISTS, so the rename above is impossible — but it
|
|
198
|
+
// may be a shell rather than the real home. A managed write that reaches
|
|
199
|
+
// `<root>/<appName>` before this migration runs creates the directory, and
|
|
200
|
+
// from then on step 1 skips forever and the legacy home keeps the binary.
|
|
201
|
+
// Observed on a real machine: `<root>/<appName>` holding a 2-byte `{}`
|
|
202
|
+
// config written hours after `<root>/<legacy appName>/bin` received the
|
|
203
|
+
// signed 107MB engine, which left managed resolution pointing at a bin
|
|
204
|
+
// directory that did not exist and every managed session with no engine to
|
|
205
|
+
// spawn.
|
|
206
|
+
//
|
|
207
|
+
// A rename cannot merge two directories, and this module has no readdir, so
|
|
208
|
+
// this moves the two entries that carry weight and nothing else: `bin`,
|
|
209
|
+
// which is what resolution needs, and `skills`, which is user data. An
|
|
210
|
+
// entry already present at the canonical home is never touched — the shell
|
|
211
|
+
// may be empty, but it is not automatically wrong.
|
|
212
|
+
for (const legacyAppName of firstPartyHarness_js_1.FIRST_PARTY_HARNESS_ENGINE.legacyAppNames) {
|
|
213
|
+
const aliasHome = node_path_1.default.join(root, legacyAppName);
|
|
214
|
+
if (!isRealDirectory(fs.lstat(aliasHome)))
|
|
215
|
+
continue;
|
|
216
|
+
for (const entry of STRANDED_HOME_ENTRIES) {
|
|
217
|
+
const from = node_path_1.default.join(aliasHome, entry);
|
|
218
|
+
const to = node_path_1.default.join(canonicalHome, entry);
|
|
219
|
+
if (!isRealDirectory(fs.lstat(from)))
|
|
220
|
+
continue;
|
|
221
|
+
if (fs.lstat(to) !== null)
|
|
222
|
+
continue;
|
|
223
|
+
try {
|
|
224
|
+
fs.rename(from, to);
|
|
225
|
+
moved.push(`${from} -> ${to}`);
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
errors.push(`could not move ${from} to ${to}: ${describe(err)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
189
234
|
// 2. The binary and its companions — follow the provider — inside the home.
|
|
190
235
|
const binDir = node_path_1.default.join(canonicalHome, 'bin');
|
|
191
236
|
if (!isRealDirectory(fs.lstat(binDir))) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OSK-10409 — which harnesses resolve the `.claude/skills/<name>/SKILL.md`
|
|
3
|
+
* layout, shared by the server and the daemon.
|
|
4
|
+
*
|
|
5
|
+
* ── Why this lives in auth-core ────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The same reason `harnessTrust.ts` does: it is a DECLARED per-harness fact
|
|
8
|
+
* that several processes have to agree on, and the cost of them disagreeing is
|
|
9
|
+
* silent. It is not auth, and it does not pretend to be — auth-core is where
|
|
10
|
+
* this repo already keeps the small tables that a harness's identity answers.
|
|
11
|
+
*
|
|
12
|
+
* ── What the fact IS ───────────────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The daemon materializes a session's skills at
|
|
15
|
+
* `<root>/.claude/skills/<name>/SKILL.md` and hands `<root>` to the harness —
|
|
16
|
+
* `--add-dir` for the Claude CLI, ACP `additionalDirectories` for Devin. A
|
|
17
|
+
* harness on this list READS that layout: it enumerates the tree, surfaces the
|
|
18
|
+
* manifest line, and resolves the body from disk. A harness that is not on it
|
|
19
|
+
* ignores the tree entirely, so nothing laid there ever reaches its model.
|
|
20
|
+
*
|
|
21
|
+
* It is a capability with EVIDENCE, never a brand check. The gate this replaced
|
|
22
|
+
* read `provider !== 'claude'` on the stated premise that every other harness
|
|
23
|
+
* ignores the layout, and the premise was false for Devin (OSK-10264): the
|
|
24
|
+
* daemon lays the same tree, passes it on `additionalDirectories`, and Devin
|
|
25
|
+
* loads it — measured live on 3000.10.31. Add a harness here only with the same
|
|
26
|
+
* kind of evidence, that its sessions actually surface a skill laid out this
|
|
27
|
+
* way. A harness that ignores the layout gains nothing and pays for the wire.
|
|
28
|
+
*
|
|
29
|
+
* ── Three readers, in three directions ─────────────────────────────────────
|
|
30
|
+
*
|
|
31
|
+
* 1. The server's PUSH gate (`buildDaemonServerSkills`): a harness on this
|
|
32
|
+
* list is one a pushed skill should be sent to.
|
|
33
|
+
* 2. The server's INVOKED-BODY gate (`buildInvokedSkillsPrompt`, OSK-10361):
|
|
34
|
+
* a harness on this list must NOT also have the body pasted into its
|
|
35
|
+
* prompt — it already has it from the tree, and on ACP the paste lands in
|
|
36
|
+
* the transcript at full skill size on every invoked turn.
|
|
37
|
+
* 3. The daemon's SHADOWING protection (`skill-shadowing.ts`, OSK-10044): a
|
|
38
|
+
* harness on this list resolves `/<name>` from its own `.claude/skills`
|
|
39
|
+
* roots, so a picked skill can be shadowed there by a same-named one — and
|
|
40
|
+
* the daemon is the only place that collision can be seen. That gate spelled
|
|
41
|
+
* `claude` and so never reached Devin, which the server-side paste was
|
|
42
|
+
* accidentally masking until (2) stopped pasting.
|
|
43
|
+
*
|
|
44
|
+
* Reader 3 is why the table moved out of the API: the daemon cannot require an
|
|
45
|
+
* api-side module, and a second copy of a table whose whole value is that every
|
|
46
|
+
* reader agrees is worse than no table at all.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Harness provider → the evidence that its sessions load the layout.
|
|
50
|
+
*
|
|
51
|
+
* The reason string is part of the entry, not decoration: an entry added
|
|
52
|
+
* without one is an entry nobody can audit later.
|
|
53
|
+
*/
|
|
54
|
+
export declare const HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT: ReadonlyMap<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Whether this harness resolves a skill from the `.claude/skills` tree the
|
|
57
|
+
* daemon materializes, so the body reaches it without being pasted anywhere.
|
|
58
|
+
*
|
|
59
|
+
* An absent provider is NOT a native resolver. "We do not know what this is"
|
|
60
|
+
* must never mean "it reads the layout" — that direction withholds the body
|
|
61
|
+
* from a harness that needed it, which is a turn that looks like the skill ran.
|
|
62
|
+
*/
|
|
63
|
+
export declare function harnessLoadsClaudeSkillsLayout(provider: string | null | undefined): boolean;
|
|
64
|
+
/** The stated evidence for a harness on the list, or `null` when it is not. */
|
|
65
|
+
export declare function claudeSkillsLayoutEvidence(provider: string | null | undefined): string | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OSK-10409 — which harnesses resolve the `.claude/skills/<name>/SKILL.md`
|
|
4
|
+
* layout, shared by the server and the daemon.
|
|
5
|
+
*
|
|
6
|
+
* ── Why this lives in auth-core ────────────────────────────────────────────
|
|
7
|
+
*
|
|
8
|
+
* The same reason `harnessTrust.ts` does: it is a DECLARED per-harness fact
|
|
9
|
+
* that several processes have to agree on, and the cost of them disagreeing is
|
|
10
|
+
* silent. It is not auth, and it does not pretend to be — auth-core is where
|
|
11
|
+
* this repo already keeps the small tables that a harness's identity answers.
|
|
12
|
+
*
|
|
13
|
+
* ── What the fact IS ───────────────────────────────────────────────────────
|
|
14
|
+
*
|
|
15
|
+
* The daemon materializes a session's skills at
|
|
16
|
+
* `<root>/.claude/skills/<name>/SKILL.md` and hands `<root>` to the harness —
|
|
17
|
+
* `--add-dir` for the Claude CLI, ACP `additionalDirectories` for Devin. A
|
|
18
|
+
* harness on this list READS that layout: it enumerates the tree, surfaces the
|
|
19
|
+
* manifest line, and resolves the body from disk. A harness that is not on it
|
|
20
|
+
* ignores the tree entirely, so nothing laid there ever reaches its model.
|
|
21
|
+
*
|
|
22
|
+
* It is a capability with EVIDENCE, never a brand check. The gate this replaced
|
|
23
|
+
* read `provider !== 'claude'` on the stated premise that every other harness
|
|
24
|
+
* ignores the layout, and the premise was false for Devin (OSK-10264): the
|
|
25
|
+
* daemon lays the same tree, passes it on `additionalDirectories`, and Devin
|
|
26
|
+
* loads it — measured live on 3000.10.31. Add a harness here only with the same
|
|
27
|
+
* kind of evidence, that its sessions actually surface a skill laid out this
|
|
28
|
+
* way. A harness that ignores the layout gains nothing and pays for the wire.
|
|
29
|
+
*
|
|
30
|
+
* ── Three readers, in three directions ─────────────────────────────────────
|
|
31
|
+
*
|
|
32
|
+
* 1. The server's PUSH gate (`buildDaemonServerSkills`): a harness on this
|
|
33
|
+
* list is one a pushed skill should be sent to.
|
|
34
|
+
* 2. The server's INVOKED-BODY gate (`buildInvokedSkillsPrompt`, OSK-10361):
|
|
35
|
+
* a harness on this list must NOT also have the body pasted into its
|
|
36
|
+
* prompt — it already has it from the tree, and on ACP the paste lands in
|
|
37
|
+
* the transcript at full skill size on every invoked turn.
|
|
38
|
+
* 3. The daemon's SHADOWING protection (`skill-shadowing.ts`, OSK-10044): a
|
|
39
|
+
* harness on this list resolves `/<name>` from its own `.claude/skills`
|
|
40
|
+
* roots, so a picked skill can be shadowed there by a same-named one — and
|
|
41
|
+
* the daemon is the only place that collision can be seen. That gate spelled
|
|
42
|
+
* `claude` and so never reached Devin, which the server-side paste was
|
|
43
|
+
* accidentally masking until (2) stopped pasting.
|
|
44
|
+
*
|
|
45
|
+
* Reader 3 is why the table moved out of the API: the daemon cannot require an
|
|
46
|
+
* api-side module, and a second copy of a table whose whole value is that every
|
|
47
|
+
* reader agrees is worse than no table at all.
|
|
48
|
+
*/
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = void 0;
|
|
51
|
+
exports.harnessLoadsClaudeSkillsLayout = harnessLoadsClaudeSkillsLayout;
|
|
52
|
+
exports.claudeSkillsLayoutEvidence = claudeSkillsLayoutEvidence;
|
|
53
|
+
const firstPartyHarness_js_1 = require("./firstPartyHarness.js");
|
|
54
|
+
/**
|
|
55
|
+
* Harness provider → the evidence that its sessions load the layout.
|
|
56
|
+
*
|
|
57
|
+
* The reason string is part of the entry, not decoration: an entry added
|
|
58
|
+
* without one is an entry nobody can audit later.
|
|
59
|
+
*/
|
|
60
|
+
exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = new Map([
|
|
61
|
+
['claude', 'Claude Code reads .claude/skills natively from --add-dir'],
|
|
62
|
+
[
|
|
63
|
+
'devin',
|
|
64
|
+
'Devin imports the Claude convention from additionalDirectories ' +
|
|
65
|
+
'(read_config_from.claude, on by default) — verified live on 3000.10.31',
|
|
66
|
+
],
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* Canonicalise the way every reader of this table must.
|
|
70
|
+
*
|
|
71
|
+
* The provider is lower-cased and trimmed, then passed through
|
|
72
|
+
* `canonicalHarnessProvider` so an accepted alias of the first-party harness
|
|
73
|
+
* resolves to the same decision as its canonical slug rather than falling
|
|
74
|
+
* through to "not a native resolver". Callers with a RICHER normalizer (the API
|
|
75
|
+
* has `normalizeSessionProvider`, which also folds `claude-code`) apply theirs
|
|
76
|
+
* first; this is the floor, not a replacement.
|
|
77
|
+
*/
|
|
78
|
+
function canonicalize(provider) {
|
|
79
|
+
return String((0, firstPartyHarness_js_1.canonicalHarnessProvider)(provider.trim().toLowerCase()));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether this harness resolves a skill from the `.claude/skills` tree the
|
|
83
|
+
* daemon materializes, so the body reaches it without being pasted anywhere.
|
|
84
|
+
*
|
|
85
|
+
* An absent provider is NOT a native resolver. "We do not know what this is"
|
|
86
|
+
* must never mean "it reads the layout" — that direction withholds the body
|
|
87
|
+
* from a harness that needed it, which is a turn that looks like the skill ran.
|
|
88
|
+
*/
|
|
89
|
+
function harnessLoadsClaudeSkillsLayout(provider) {
|
|
90
|
+
if (!provider || typeof provider !== 'string')
|
|
91
|
+
return false;
|
|
92
|
+
return exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT.has(canonicalize(provider));
|
|
93
|
+
}
|
|
94
|
+
/** The stated evidence for a harness on the list, or `null` when it is not. */
|
|
95
|
+
function claudeSkillsLayoutEvidence(provider) {
|
|
96
|
+
if (!provider || typeof provider !== 'string')
|
|
97
|
+
return null;
|
|
98
|
+
return exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT.get(canonicalize(provider)) ?? null;
|
|
99
|
+
}
|
|
@@ -32,6 +32,7 @@ export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithS
|
|
|
32
32
|
export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
|
|
33
33
|
export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, type AssembleBundleOptions as PairAssembleBundleOptions, type OpenBundleOptions as PairOpenBundleOptions, type PairBundle, type PairBundleErrorCode, type PairKeyPair, type PairPlaintextV1, type PairUrlFields, } from './daemonPair.js';
|
|
34
34
|
export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, type HarnessTrustTier, type CredentialDeliveryPolicy, type CredentialMode, } from './harnessTrust.js';
|
|
35
|
+
export { HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT, claudeSkillsLayoutEvidence, harnessLoadsClaudeSkillsLayout, } from './harnessSkillLayout.js';
|
|
35
36
|
export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, } from './spawnEnv.js';
|
|
36
37
|
export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
|
|
37
38
|
export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
|
|
@@ -25,8 +25,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.challenge = exports.generateVerifier = exports.invalidateHeadersHelperCache = exports.runHeadersHelper = exports.SENTINEL_TRIGGERING_FAILURES = exports.__resetAuthHelperFailureReasonForTest = exports.getLastAuthHelperFailureReason = exports.getDefaultHelperTtlMs = exports.invokeAuthHelper = exports.AuthHelperUnavailableError = exports.AuthHelperUntrustedError = exports.CI_TOKEN_PREFIX = exports.classifyTokenKind = exports.invalidateAuthHelperCache = exports.__resetCredentialResolverForTest = exports.CREDENTIAL_PRECEDENCE = exports.CredentialResolver = exports.__setFdReaderForTest = exports.readAuthFromFd = exports.createRefreshScheduler = exports.UNKNOWN_401_BUDGET = exports.__resetUnknown401StreaksForTest = exports.__refreshDiagnostics = exports.installLockExitHandlers = exports.clearReauthState = exports.needsReauthState = exports.persistReauthState = exports.withAuthLock = exports.revokeDaemonRefreshSession = exports.refreshDaemonToken = exports.classifyRefreshResponse = exports.EXIT_NEEDS_REAUTH = exports.formatReauthMessage = exports.handleMidStreamAuthExpiry = exports.isAuthExpiredSseEvent = exports.decodeJwtPayload = exports.decodeJwtExpiry = exports.TransientAuthFailure = exports.PermanentAuthFailure = exports.emitAuthTelemetry = exports.getAuthBinaryName = exports.getAuthKillSwitches = exports.getAuthConfigDir = exports.getAuthLogger = exports.configureAuthCore = exports.OAUTH_CLI_SCOPE = exports.isDaemonScope = exports.VALID_DAEMON_SCOPE_SET = exports.ALL_DAEMON_SCOPES = exports.SCOPES = void 0;
|
|
26
26
|
exports.tokenHash = exports.publicJwkFromX = exports.normalizeHtu = exports.jwkThumbprint = exports.generateDeviceKeyPair = exports.buildDeviceProof = exports.zeroizeKekCaches = exports.WindowsKek = exports.registerZeroizeHook = exports.MacosKek = exports.LinuxKek = exports.KekUnavailableError = exports.UnavailableKek = exports.InMemoryKek = exports.getKekStrategy = exports.__setKekStrategyForTest = exports.credEnvelopeWrap = exports.credEnvelopeUnwrap = exports.credEnvelopeSerialize = exports.credSealBuffer = exports.credOpenBuffer = exports.CRED_KEY_LEN = exports.isCredEnvelopeString = exports.generateDek = exports.CRED_ENVELOPE_MAGIC = exports.credEnvelopeEncrypt = exports.credEnvelopeDeserialize = exports.credEnvelopeDecrypt = exports.buildCredentialAad = exports.isValidRecoveryCode = exports.normalizeRecoveryCode = exports.parseRecoveryCode = exports.formatRecoveryCode = exports.MAX_TTL_DAYS = exports.MIN_TTL_DAYS = exports.parseTtlDays = exports.LoginExchangeError = exports.LoginInitError = exports.loginWithLocalhost = exports.LocalCallbackMalformedError = exports.LocalCallbackDeniedError = exports.LocalCallbackStateMismatchError = exports.LocalCallbackTimeoutError = exports.LocalCallbackBindError = exports.LocalCallbackError = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.startLocalCallback = exports.verifyChallenge = exports.safeEqual = exports.generateState = void 0;
|
|
27
27
|
exports.HARNESS_TIERS = exports.pairUint8ArrayToBase64Url = exports.pairUint8ArrayToBase64 = exports.parsePairUrl = exports.parsePairPlaintext = exports.parsePairBundle = exports.openPairBundleBase64 = exports.generatePairKeyPair = exports.buildPairUrl = exports.buildPairAad = exports.pairBase64UrlToUint8Array = exports.pairBase64ToUint8Array = exports.assemblePairBundleBase64 = exports.PairBundleError = exports.PAIR_SECRET_LEN = exports.PAIR_PUBKEY_LEN = exports.PAIR_NONCE_LEN = exports.PAIR_BUNDLE_MAX_BASE64_LEN = exports.PAIR_AAD_MAX_LEN = exports.PAIR_AAD_PREFIX = exports.__resetCachedDaemonIdForTest = exports.legacyDaemonUuids = exports.ensureMachineDaemonId = exports.__getDeviceIdentityStateForTest = exports.__setDeviceIdentityStateForTest = exports.__enrollmentBackoffPathForTest = exports.__enrollmentMarkerPathForTest = exports.__privateKeyPathForTest = exports.__publicKeyPathForTest = exports.describeDeviceIdentityState = exports.getDevicePublicKey = exports.isDeviceIdentityActive = exports.clearEnrollmentMarker = exports.enrollWithServer = exports.signServerRequest = exports.resetDeviceIdentity = exports.initDeviceIdentity = exports.__resetShutdownGuardForTest = exports.__getCredEnvelopeStateForTest = exports.__setCredEnvelopeStateForTest = exports.__wrappedDekFilePathForTest = exports.maybeDecryptOnRead = exports.maybeEncryptForWrite = exports.describeCredEnvelopeState = exports.hasCredEnvelopePathConflict = exports.isCredEnvelopeActive = exports.shutdownCredEnvelope = exports.resetCredEnvelope = exports.initCredEnvelope = exports.verifyDeviceProof = void 0;
|
|
28
|
-
exports.
|
|
29
|
-
exports.consolidateProfileState = void 0;
|
|
28
|
+
exports.resolveProfileStateDir = exports.legacyProfileStateDir = exports.profileStateDir = exports.SUPERSEDED_COPY_SUFFIX = exports.SUPERSEDED_MARKER_SUFFIX = exports.PROFILES_SEGMENT = exports.isConfigRootOverridden = exports.configRootPath = exports.resolveConfigRoot = exports.verifyManifest = exports.verifyArtifact = exports.getDeltaFor = exports.buildManifestMessage = exports.buildArtifactMessage = exports.MAX_SIGS = exports.OVERSKY_RELEASE_PUBLIC_KEYS = exports.resolveFirstPartyHarnessChannel = exports.resolveChannelManifestUrl = exports.isFirstPartyHarnessChannel = exports.firstPartyHarnessFeedCandidates = exports.firstPartyHarnessFeedBase = exports.firstPartyHarnessChannelPrefix = exports.FIRST_PARTY_HARNESS_CHANNELS = exports.DEFAULT_FIRST_PARTY_HARNESS_CHANNEL = exports.migrateFirstPartyHarnessHome = exports.linkFirstPartyHarnessAliases = exports.firstPartyHarnessInstalledBinaryPath = exports.firstPartyHarnessHomeDir = exports.firstPartyHarnessBinDir = exports.FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = exports.FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = exports.describeLegacyState = exports.findLegacyLocalState = exports.DEFAULT_BINARY_NAME = exports.LEGACY_NATIVE_ID_PREFIXES = exports.NATIVE_ID_PREFIX = exports.LEGACY_CONFIG_DIR_NAMES = exports.CONFIG_DIR_NAME = exports.SENSITIVE_ENV_PREFIX_EXCEPTIONS = exports.SENSITIVE_ENV_NAME_TOKENS = exports.SENSITIVE_ENV_SUFFIXES = exports.SENSITIVE_ENV_PREFIXES = exports.SENSITIVE_ENV_VARS = exports.sanitizeSpawnEnv = exports.harnessLoadsClaudeSkillsLayout = exports.claudeSkillsLayoutEvidence = exports.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT = exports.tierPermitsConfiguredAutoMode = exports.credentialPolicyForTier = exports.getHarnessTier = void 0;
|
|
29
|
+
exports.consolidateProfileState = exports.profileStateFilePathsToClear = exports.profileStateFileLocations = exports.resolveProfileStateFile = void 0;
|
|
30
30
|
// Daemon scope vocabulary — single source of truth for non-Mongoose tiers
|
|
31
31
|
// (daemon, desktop, cli). Schema-bound canonical lives in
|
|
32
32
|
// `@skrr-ai/data-schemas/common/daemonScopes`; the two are kept byte-equal
|
|
@@ -295,6 +295,14 @@ Object.defineProperty(exports, "HARNESS_TIERS", { enumerable: true, get: functio
|
|
|
295
295
|
Object.defineProperty(exports, "getHarnessTier", { enumerable: true, get: function () { return harnessTrust_js_1.getHarnessTier; } });
|
|
296
296
|
Object.defineProperty(exports, "credentialPolicyForTier", { enumerable: true, get: function () { return harnessTrust_js_1.credentialPolicyForTier; } });
|
|
297
297
|
Object.defineProperty(exports, "tierPermitsConfiguredAutoMode", { enumerable: true, get: function () { return harnessTrust_js_1.tierPermitsConfiguredAutoMode; } });
|
|
298
|
+
// OSK-10409 — which harnesses resolve the `.claude/skills` layout the daemon
|
|
299
|
+
// materializes. One declared table with per-harness evidence, read by the
|
|
300
|
+
// server's two skill gates and by the daemon's shadowing protection. See
|
|
301
|
+
// harnessSkillLayout.ts for why a second copy would be worse than none.
|
|
302
|
+
var harnessSkillLayout_js_1 = require("./harnessSkillLayout.js");
|
|
303
|
+
Object.defineProperty(exports, "HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT", { enumerable: true, get: function () { return harnessSkillLayout_js_1.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT; } });
|
|
304
|
+
Object.defineProperty(exports, "claudeSkillsLayoutEvidence", { enumerable: true, get: function () { return harnessSkillLayout_js_1.claudeSkillsLayoutEvidence; } });
|
|
305
|
+
Object.defineProperty(exports, "harnessLoadsClaudeSkillsLayout", { enumerable: true, get: function () { return harnessSkillLayout_js_1.harnessLoadsClaudeSkillsLayout; } });
|
|
298
306
|
// OSK-3892 — spawn-env sanitization, shared by the daemon (which has stripped
|
|
299
307
|
// its own credentials before every harness spawn all along) and the CLI (which
|
|
300
308
|
// spawns the SAME engine and did not). `daemon/src/env-sanitize.ts` re-exports
|
|
@@ -313,6 +313,13 @@ declare const ENGINE_ENV_SUFFIXES: Readonly<{
|
|
|
313
313
|
allowUpstreamEgress: "ALLOW_UPSTREAM_EGRESS";
|
|
314
314
|
compatOpencode: "COMPAT_OPENCODE";
|
|
315
315
|
noCompatPrompts: "NO_COMPAT_PROMPTS";
|
|
316
|
+
/**
|
|
317
|
+
* Advisory input-token budget for a managed session, set by the daemon from
|
|
318
|
+
* the server-issued `requestEnvelope.maxInputTokens` so the engine's
|
|
319
|
+
* auto-compaction can trigger before the relay refuses the request. The
|
|
320
|
+
* engine must treat it as a hint: enforcement stays server-side.
|
|
321
|
+
*/
|
|
322
|
+
maxInputTokens: "MAX_INPUT_TOKENS";
|
|
316
323
|
}>;
|
|
317
324
|
export type FirstPartyHarnessEngineEnvKey = keyof typeof ENGINE_ENV_SUFFIXES;
|
|
318
325
|
/**
|
|
@@ -350,7 +357,7 @@ export declare const FIRST_PARTY_HARNESS_ENGINE: Readonly<{
|
|
|
350
357
|
* `readFirstPartyHarnessEngineEnv` to read one the way the engine does and
|
|
351
358
|
* `firstPartyHarnessEngineEnvEntries` to set one every engine build honours.
|
|
352
359
|
*/
|
|
353
|
-
env: Readonly<Record<"appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts", string>>;
|
|
360
|
+
env: Readonly<Record<"appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts" | "maxInputTokens", string>>;
|
|
354
361
|
}>;
|
|
355
362
|
/**
|
|
356
363
|
* Every name the engine reads for one purpose, current namespace first — the
|
|
@@ -415,6 +415,13 @@ const ENGINE_ENV_SUFFIXES = Object.freeze({
|
|
|
415
415
|
allowUpstreamEgress: 'ALLOW_UPSTREAM_EGRESS',
|
|
416
416
|
compatOpencode: 'COMPAT_OPENCODE',
|
|
417
417
|
noCompatPrompts: 'NO_COMPAT_PROMPTS',
|
|
418
|
+
/**
|
|
419
|
+
* Advisory input-token budget for a managed session, set by the daemon from
|
|
420
|
+
* the server-issued `requestEnvelope.maxInputTokens` so the engine's
|
|
421
|
+
* auto-compaction can trigger before the relay refuses the request. The
|
|
422
|
+
* engine must treat it as a hint: enforcement stays server-side.
|
|
423
|
+
*/
|
|
424
|
+
maxInputTokens: 'MAX_INPUT_TOKENS',
|
|
418
425
|
});
|
|
419
426
|
function engineEnvName(appName, key) {
|
|
420
427
|
return `${appName.toUpperCase().replace(/-/g, '_')}_${ENGINE_ENV_SUFFIXES[key]}`;
|
|
@@ -53,6 +53,13 @@ import { FIRST_PARTY_HARNESS, FIRST_PARTY_HARNESS_ENGINE, firstPartyHarnessArtif
|
|
|
53
53
|
function olderSpellings() {
|
|
54
54
|
return firstPartyHarnessArtifactSpellings().filter((spelling) => spelling !== FIRST_PARTY_HARNESS.provider);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Entries rescued from a legacy home when the canonical one already exists as a
|
|
58
|
+
* shell. `bin` is what managed resolution needs; `skills` is the user's own
|
|
59
|
+
* data. Deliberately a short, named list rather than "everything": a merge this
|
|
60
|
+
* module cannot enumerate (there is no readdir here) would be guessing.
|
|
61
|
+
*/
|
|
62
|
+
const STRANDED_HOME_ENTRIES = ['bin', 'skills'];
|
|
56
63
|
/** Suffix of the rollback copy the installer keeps beside the binary. */
|
|
57
64
|
export const FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = '.previous';
|
|
58
65
|
/**
|
|
@@ -175,6 +182,44 @@ export function migrateFirstPartyHarnessHome(options = {}) {
|
|
|
175
182
|
break;
|
|
176
183
|
}
|
|
177
184
|
}
|
|
185
|
+
else if (isRealDirectory(fs.lstat(canonicalHome))) {
|
|
186
|
+
// 1b. The canonical home EXISTS, so the rename above is impossible — but it
|
|
187
|
+
// may be a shell rather than the real home. A managed write that reaches
|
|
188
|
+
// `<root>/<appName>` before this migration runs creates the directory, and
|
|
189
|
+
// from then on step 1 skips forever and the legacy home keeps the binary.
|
|
190
|
+
// Observed on a real machine: `<root>/<appName>` holding a 2-byte `{}`
|
|
191
|
+
// config written hours after `<root>/<legacy appName>/bin` received the
|
|
192
|
+
// signed 107MB engine, which left managed resolution pointing at a bin
|
|
193
|
+
// directory that did not exist and every managed session with no engine to
|
|
194
|
+
// spawn.
|
|
195
|
+
//
|
|
196
|
+
// A rename cannot merge two directories, and this module has no readdir, so
|
|
197
|
+
// this moves the two entries that carry weight and nothing else: `bin`,
|
|
198
|
+
// which is what resolution needs, and `skills`, which is user data. An
|
|
199
|
+
// entry already present at the canonical home is never touched — the shell
|
|
200
|
+
// may be empty, but it is not automatically wrong.
|
|
201
|
+
for (const legacyAppName of FIRST_PARTY_HARNESS_ENGINE.legacyAppNames) {
|
|
202
|
+
const aliasHome = path.join(root, legacyAppName);
|
|
203
|
+
if (!isRealDirectory(fs.lstat(aliasHome)))
|
|
204
|
+
continue;
|
|
205
|
+
for (const entry of STRANDED_HOME_ENTRIES) {
|
|
206
|
+
const from = path.join(aliasHome, entry);
|
|
207
|
+
const to = path.join(canonicalHome, entry);
|
|
208
|
+
if (!isRealDirectory(fs.lstat(from)))
|
|
209
|
+
continue;
|
|
210
|
+
if (fs.lstat(to) !== null)
|
|
211
|
+
continue;
|
|
212
|
+
try {
|
|
213
|
+
fs.rename(from, to);
|
|
214
|
+
moved.push(`${from} -> ${to}`);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
errors.push(`could not move ${from} to ${to}: ${describe(err)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
178
223
|
// 2. The binary and its companions — follow the provider — inside the home.
|
|
179
224
|
const binDir = path.join(canonicalHome, 'bin');
|
|
180
225
|
if (!isRealDirectory(fs.lstat(binDir))) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OSK-10409 — which harnesses resolve the `.claude/skills/<name>/SKILL.md`
|
|
3
|
+
* layout, shared by the server and the daemon.
|
|
4
|
+
*
|
|
5
|
+
* ── Why this lives in auth-core ────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The same reason `harnessTrust.ts` does: it is a DECLARED per-harness fact
|
|
8
|
+
* that several processes have to agree on, and the cost of them disagreeing is
|
|
9
|
+
* silent. It is not auth, and it does not pretend to be — auth-core is where
|
|
10
|
+
* this repo already keeps the small tables that a harness's identity answers.
|
|
11
|
+
*
|
|
12
|
+
* ── What the fact IS ───────────────────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The daemon materializes a session's skills at
|
|
15
|
+
* `<root>/.claude/skills/<name>/SKILL.md` and hands `<root>` to the harness —
|
|
16
|
+
* `--add-dir` for the Claude CLI, ACP `additionalDirectories` for Devin. A
|
|
17
|
+
* harness on this list READS that layout: it enumerates the tree, surfaces the
|
|
18
|
+
* manifest line, and resolves the body from disk. A harness that is not on it
|
|
19
|
+
* ignores the tree entirely, so nothing laid there ever reaches its model.
|
|
20
|
+
*
|
|
21
|
+
* It is a capability with EVIDENCE, never a brand check. The gate this replaced
|
|
22
|
+
* read `provider !== 'claude'` on the stated premise that every other harness
|
|
23
|
+
* ignores the layout, and the premise was false for Devin (OSK-10264): the
|
|
24
|
+
* daemon lays the same tree, passes it on `additionalDirectories`, and Devin
|
|
25
|
+
* loads it — measured live on 3000.10.31. Add a harness here only with the same
|
|
26
|
+
* kind of evidence, that its sessions actually surface a skill laid out this
|
|
27
|
+
* way. A harness that ignores the layout gains nothing and pays for the wire.
|
|
28
|
+
*
|
|
29
|
+
* ── Three readers, in three directions ─────────────────────────────────────
|
|
30
|
+
*
|
|
31
|
+
* 1. The server's PUSH gate (`buildDaemonServerSkills`): a harness on this
|
|
32
|
+
* list is one a pushed skill should be sent to.
|
|
33
|
+
* 2. The server's INVOKED-BODY gate (`buildInvokedSkillsPrompt`, OSK-10361):
|
|
34
|
+
* a harness on this list must NOT also have the body pasted into its
|
|
35
|
+
* prompt — it already has it from the tree, and on ACP the paste lands in
|
|
36
|
+
* the transcript at full skill size on every invoked turn.
|
|
37
|
+
* 3. The daemon's SHADOWING protection (`skill-shadowing.ts`, OSK-10044): a
|
|
38
|
+
* harness on this list resolves `/<name>` from its own `.claude/skills`
|
|
39
|
+
* roots, so a picked skill can be shadowed there by a same-named one — and
|
|
40
|
+
* the daemon is the only place that collision can be seen. That gate spelled
|
|
41
|
+
* `claude` and so never reached Devin, which the server-side paste was
|
|
42
|
+
* accidentally masking until (2) stopped pasting.
|
|
43
|
+
*
|
|
44
|
+
* Reader 3 is why the table moved out of the API: the daemon cannot require an
|
|
45
|
+
* api-side module, and a second copy of a table whose whole value is that every
|
|
46
|
+
* reader agrees is worse than no table at all.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Harness provider → the evidence that its sessions load the layout.
|
|
50
|
+
*
|
|
51
|
+
* The reason string is part of the entry, not decoration: an entry added
|
|
52
|
+
* without one is an entry nobody can audit later.
|
|
53
|
+
*/
|
|
54
|
+
export declare const HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT: ReadonlyMap<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* Whether this harness resolves a skill from the `.claude/skills` tree the
|
|
57
|
+
* daemon materializes, so the body reaches it without being pasted anywhere.
|
|
58
|
+
*
|
|
59
|
+
* An absent provider is NOT a native resolver. "We do not know what this is"
|
|
60
|
+
* must never mean "it reads the layout" — that direction withholds the body
|
|
61
|
+
* from a harness that needed it, which is a turn that looks like the skill ran.
|
|
62
|
+
*/
|
|
63
|
+
export declare function harnessLoadsClaudeSkillsLayout(provider: string | null | undefined): boolean;
|
|
64
|
+
/** The stated evidence for a harness on the list, or `null` when it is not. */
|
|
65
|
+
export declare function claudeSkillsLayoutEvidence(provider: string | null | undefined): string | null;
|