@caupulican/pi-adaptative 0.93.4 → 0.93.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.93.5] - 2026-08-17
2
+
3
+ ### Changed
4
+
5
+ - Made the skill vault multi-slot: up to three concurrent skills share one byte budget with least-recently-used eviction, load results state the pending activation, base directory, and any evicted skills instead of claiming immediate activation, and unload/status operate per slot.
6
+ - Bash failure results now report the effective working directory the command ran in on a final `cwd:` line — shell-reported `$PWD` from a collision-safe length-framed POSIX exit sentinel, or the state-tracked effective cwd on the Windows contract — and the tool description states the persistent-cwd contract.
7
+
8
+ ### Fixed
9
+
10
+ - Stopped tool-failure records from destroying evidence: missing-path failures retain the diagnostic naming the exact path, and executed process-exit failures carry a bounded sanitized tail of the command output as evidence while blocked replays gain nothing new.
11
+ - Made catalogued per-error guidance lead the failure correction ahead of the recovery gate's loaded-action text, so specific advice (such as listing a missing path's parent) is no longer displaced by generic repair-action instructions.
12
+
1
13
  ## [0.93.4] - 2026-08-17
2
14
 
3
15
  ### Changed
@@ -2,6 +2,7 @@ import { type Skill } from "./skills.ts";
2
2
  export declare const DEFAULT_SKILL_IDLE_TIMEOUT_MS: number;
3
3
  export declare const MAX_ACTIVE_SKILL_BODY_BYTES: number;
4
4
  export declare const MIN_ACTIVE_SKILL_BODY_BYTES: number;
5
+ export declare const MAX_LOADED_SKILLS = 3;
5
6
  type SkillVaultRequester = "model" | "user";
6
7
  type SkillVaultUnloadReason = "explicit" | "idle_expired" | "resource_unavailable" | "budget_exceeded";
7
8
  interface LoadedSkill {
@@ -16,25 +17,25 @@ interface LoadedSkill {
16
17
  fileModifiedAtMs: number;
17
18
  fileChangedAtMs: number;
18
19
  }
19
- type SkillVaultState = {
20
- state: "unloaded";
21
- reason?: SkillVaultUnloadReason;
22
- } | ({
20
+ type SkillSlotState = ({
23
21
  state: "loaded_pending";
24
22
  } & LoadedSkill) | ({
25
23
  state: "active";
26
24
  lastUsedAtMs: number;
27
25
  useCount: number;
28
26
  } & LoadedSkill);
29
- export interface SkillVaultStatus {
30
- state: SkillVaultState["state"];
31
- idleTimeoutMs: number;
32
- name?: string;
33
- loadedAtMs?: number;
27
+ export interface SkillSlotStatus {
28
+ state: SkillSlotState["state"];
29
+ name: string;
30
+ loadedAtMs: number;
34
31
  lastUsedAtMs?: number;
35
32
  idleForMs?: number;
36
33
  expiresInMs?: number;
37
34
  useCount?: number;
35
+ }
36
+ export interface SkillVaultStatus {
37
+ idleTimeoutMs: number;
38
+ slots: SkillSlotStatus[];
38
39
  reason?: SkillVaultUnloadReason;
39
40
  }
40
41
  export interface SkillSearchResult {
@@ -47,7 +48,8 @@ export type SkillLoadResult = {
47
48
  ok: true;
48
49
  state: "loaded_pending";
49
50
  name: string;
50
- replaced?: string;
51
+ baseDir: string;
52
+ evicted?: string[];
51
53
  } | {
52
54
  ok: false;
53
55
  reason: "not_found" | "body_too_large" | "invalid_body" | "read_failed";
@@ -69,14 +71,15 @@ export declare class SkillVaultController {
69
71
  private readonly idleTimeoutMs;
70
72
  private readonly getMaxBodyBytes;
71
73
  private readonly onSkillUsed;
72
- private current;
74
+ private slots;
75
+ private unloadReason;
73
76
  private contextRevision;
74
77
  constructor(options: SkillVaultControllerOptions);
75
78
  search(rawQuery: string): SkillSearchResult;
76
79
  load(name: string, requester: SkillVaultRequester): SkillLoadResult;
77
- unload(): {
80
+ unload(name?: string): {
78
81
  ok: true;
79
- unloaded?: string;
82
+ unloaded: string[];
80
83
  };
81
84
  status(): SkillVaultStatus;
82
85
  commitSystemPromptSection(): string | undefined;
@@ -88,8 +91,9 @@ export declare class SkillVaultController {
88
91
  getContextRevision(): number;
89
92
  /** Record host-observed work derived from an active skill, independent of agent cooperation. */
90
93
  noteActivity(): void;
94
+ private slotStatus;
91
95
  private reconcile;
92
- private reconcileResource;
96
+ private reconcileSlot;
93
97
  private replaceState;
94
98
  private resolveMaxBodyBytes;
95
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"skill-vault.d.ts","sourceRoot":"","sources":["../../src/core/skill-vault.ts"],"names":[],"mappings":"AAKA,OAAO,EAA+B,KAAK,KAAK,EAAyB,MAAM,aAAa,CAAC;AAG7F,eAAO,MAAM,6BAA6B,QAAiB,CAAC;AAC5D,eAAO,MAAM,2BAA2B,QAAY,CAAC;AACrD,eAAO,MAAM,2BAA2B,QAAW,CAAC;AAIpD,KAAK,mBAAmB,GAAG,OAAO,GAAG,MAAM,CAAC;AAC5C,KAAK,sBAAsB,GAAG,UAAU,GAAG,cAAc,GAAG,sBAAsB,GAAG,iBAAiB,CAAC;AAEvG,UAAU,WAAW;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACxB;AAED,KAAK,eAAe,GACjB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,MAAM,CAAC,EAAE,sBAAsB,CAAA;CAAE,GACtD,CAAC;IAAE,KAAK,EAAE,gBAAgB,CAAA;CAAE,GAAG,WAAW,CAAC,GAC3C,CAAC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAAC,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,MAAM,eAAe,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,GAAG,cAAc,GAAG,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3G,MAAM,WAAW,2BAA2B;IAC3C,SAAS,IAAI,SAAS,KAAK,EAAE,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AA4BD,+FAA+F;AAC/F,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKzF;AAED,wGAAwG;AACxG,qBAAa,oBAAoB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAe;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyD;IACrF,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,eAAe,CAAK;IAE5B,YAAY,OAAO,EAAE,2BAA2B,EAS/C;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAY1C;IAED,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,GAAG,eAAe,CAqElE;IAED,MAAM,IAAI;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAIxC;IAED,MAAM,IAAI,gBAAgB,CA6BzB;IAED,yBAAyB,IAAI,MAAM,GAAG,SAAS,CAgB9C;IAED,gGAAgG;IAChG,0BAA0B,IAAI,MAAM,GAAG,SAAS,CAG/C;IAED,0EAA0E;IAC1E,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEvE;IAED,wEAAwE;IACxE,kBAAkB,IAAI,MAAM,CAG3B;IAED,gGAAgG;IAChG,YAAY,IAAI,IAAI,CAKnB;IAED,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,iBAAiB;IAoCzB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,mBAAmB;CAK3B"}
1
+ {"version":3,"file":"skill-vault.d.ts","sourceRoot":"","sources":["../../src/core/skill-vault.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ import { readBoundedTextFileSync, sameFileVersion } from "./util/bounded-file.js
8
8
  export const DEFAULT_SKILL_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
9
9
  export const MAX_ACTIVE_SKILL_BODY_BYTES = 64 * 1024;
10
10
  export const MIN_ACTIVE_SKILL_BODY_BYTES = 4 * 1024;
11
+ export const MAX_LOADED_SKILLS = 3;
11
12
  const MAX_SEARCH_RESULTS = 5;
12
13
  const MAX_SEARCH_DESCRIPTION_CHARS = 240;
13
14
  function compactDescription(description) {
@@ -36,6 +37,29 @@ function searchScore(skill, query, tokens) {
36
37
  function activeSkillContext(skill, body) {
37
38
  return [`ACTIVE SKILL ${skill.name}`, `BASE ${skill.baseDir}`, "NON-NEGOTIABLE WHILE ACTIVE:", body].join("\n");
38
39
  }
40
+ function slotLastUsedAtMs(slot) {
41
+ return slot.state === "loaded_pending" ? slot.loadedAtMs : slot.lastUsedAtMs;
42
+ }
43
+ function aggregateBodyBytes(slots) {
44
+ let total = 0;
45
+ for (const slot of slots.values())
46
+ total += slot.bodyBytes;
47
+ return total;
48
+ }
49
+ function leastRecentlyUsedName(slots, excludeName) {
50
+ let lruName;
51
+ let lruUsedAtMs = Number.POSITIVE_INFINITY;
52
+ for (const [name, slot] of slots) {
53
+ if (name === excludeName)
54
+ continue;
55
+ const usedAtMs = slotLastUsedAtMs(slot);
56
+ if (usedAtMs < lruUsedAtMs) {
57
+ lruUsedAtMs = usedAtMs;
58
+ lruName = name;
59
+ }
60
+ }
61
+ return lruName;
62
+ }
39
63
  /** Reserve at most roughly one context token's worth of bytes per advertised context token. */
40
64
  export function resolveActiveSkillBodyByteLimit(contextWindow) {
41
65
  if (contextWindow === undefined || !Number.isFinite(contextWindow) || contextWindow <= 0) {
@@ -46,7 +70,7 @@ export function resolveActiveSkillBodyByteLimit(contextWindow) {
46
70
  /** One host-owned, event-driven lifecycle for lazy skill discovery and transient context projection. */
47
71
  export class SkillVaultController {
48
72
  constructor(options) {
49
- this.current = { state: "unloaded" };
73
+ this.slots = new Map();
50
74
  this.contextRevision = 0;
51
75
  if (!Number.isFinite(options.idleTimeoutMs ?? DEFAULT_SKILL_IDLE_TIMEOUT_MS)) {
52
76
  throw new TypeError("Skill idle timeout must be finite.");
@@ -72,6 +96,8 @@ export class SkillVaultController {
72
96
  return { candidates };
73
97
  }
74
98
  load(name, requester) {
99
+ const now = this.now();
100
+ this.reconcile(now);
75
101
  const skill = this.getSkills().find((candidate) => candidate.name === name && (requester === "user" || !candidate.disableModelInvocation));
76
102
  if (!skill) {
77
103
  return { ok: false, reason: "not_found", message: `No eligible skill named ${JSON.stringify(name)}.` };
@@ -122,80 +148,87 @@ export class SkillVaultController {
122
148
  if (!sameFileVersion(before, file)) {
123
149
  return { ok: false, reason: "read_failed", message: "Skill changed while it was being loaded." };
124
150
  }
125
- const replaced = this.current.state === "unloaded" ? undefined : this.current.skill.name;
126
- this.replaceState({
151
+ const next = new Map(this.slots);
152
+ next.set(skill.name, {
127
153
  state: "loaded_pending",
128
154
  skill,
129
155
  bodyBytes,
130
156
  systemPromptSection: activeSkillContext(skill, body),
131
157
  requester,
132
- loadedAtMs: this.now(),
158
+ loadedAtMs: now,
133
159
  fileDevice: file.dev,
134
160
  fileInode: file.ino,
135
161
  fileSize: file.size,
136
162
  fileModifiedAtMs: file.mtimeMs,
137
163
  fileChangedAtMs: file.ctimeMs,
138
164
  });
139
- return { ok: true, state: "loaded_pending", name: skill.name, ...(replaced ? { replaced } : {}) };
165
+ const evicted = [];
166
+ while (next.size > MAX_LOADED_SKILLS || aggregateBodyBytes(next) > maxBodyBytes) {
167
+ const victim = leastRecentlyUsedName(next, skill.name);
168
+ if (!victim)
169
+ break;
170
+ next.delete(victim);
171
+ evicted.push(victim);
172
+ }
173
+ this.replaceState(next);
174
+ return {
175
+ ok: true,
176
+ state: "loaded_pending",
177
+ name: skill.name,
178
+ baseDir: skill.baseDir,
179
+ ...(evicted.length > 0 ? { evicted } : {}),
180
+ };
140
181
  }
141
- unload() {
142
- const unloaded = this.current.state === "unloaded" ? undefined : this.current.skill.name;
143
- this.replaceState({ state: "unloaded", reason: "explicit" });
144
- return { ok: true, ...(unloaded ? { unloaded } : {}) };
182
+ unload(name) {
183
+ this.reconcile(this.now());
184
+ const target = name?.trim();
185
+ const unloaded = !target ? [...this.slots.keys()] : this.slots.has(target) ? [target] : [];
186
+ if (unloaded.length > 0) {
187
+ const next = new Map(this.slots);
188
+ for (const slotName of unloaded)
189
+ next.delete(slotName);
190
+ this.replaceState(next, "explicit");
191
+ }
192
+ return { ok: true, unloaded };
145
193
  }
146
194
  status() {
147
195
  const now = this.now();
148
196
  this.reconcile(now);
149
- if (this.current.state === "unloaded") {
150
- return {
151
- state: "unloaded",
152
- idleTimeoutMs: this.idleTimeoutMs,
153
- ...(this.current.reason ? { reason: this.current.reason } : {}),
154
- };
155
- }
156
- if (this.current.state === "loaded_pending") {
157
- return {
158
- state: "loaded_pending",
159
- idleTimeoutMs: this.idleTimeoutMs,
160
- name: this.current.skill.name,
161
- loadedAtMs: this.current.loadedAtMs,
162
- };
163
- }
164
- const idleForMs = Math.max(0, now - this.current.lastUsedAtMs);
197
+ const slots = [...this.slots.values()].map((slot) => this.slotStatus(slot, now));
165
198
  return {
166
- state: "active",
167
199
  idleTimeoutMs: this.idleTimeoutMs,
168
- name: this.current.skill.name,
169
- loadedAtMs: this.current.loadedAtMs,
170
- lastUsedAtMs: this.current.lastUsedAtMs,
171
- idleForMs,
172
- expiresInMs: Math.max(0, this.idleTimeoutMs - idleForMs),
173
- useCount: this.current.useCount,
200
+ slots,
201
+ ...(slots.length === 0 && this.unloadReason ? { reason: this.unloadReason } : {}),
174
202
  };
175
203
  }
176
204
  commitSystemPromptSection() {
177
205
  const now = this.now();
178
206
  this.reconcile(now);
179
- if (this.current.state === "unloaded")
207
+ if (this.slots.size === 0)
180
208
  return undefined;
181
- const loaded = this.current;
182
- const firstUse = loaded.state === "loaded_pending";
183
- const useCount = firstUse ? 1 : loaded.useCount + 1;
184
- this.current = { ...loaded, state: "active", lastUsedAtMs: now, useCount };
185
- if (firstUse) {
186
- try {
187
- this.onSkillUsed?.(loaded.skill, now);
188
- }
189
- catch {
190
- // Usage telemetry must never block skill application.
209
+ const sections = [];
210
+ for (const [name, slot] of this.slots) {
211
+ sections.push(slot.systemPromptSection);
212
+ const firstUse = slot.state === "loaded_pending";
213
+ const useCount = firstUse ? 1 : slot.useCount + 1;
214
+ this.slots.set(name, { ...slot, state: "active", lastUsedAtMs: now, useCount });
215
+ if (firstUse) {
216
+ try {
217
+ this.onSkillUsed?.(slot.skill, now);
218
+ }
219
+ catch {
220
+ // Usage telemetry must never block skill application.
221
+ }
191
222
  }
192
223
  }
193
- return loaded.systemPromptSection;
224
+ return sections.join("\n\n");
194
225
  }
195
226
  /** Model the next request's transient system cost without treating a diagnostic read as use. */
196
227
  previewSystemPromptSection() {
197
228
  this.reconcile(this.now());
198
- return this.current.state === "unloaded" ? undefined : this.current.systemPromptSection;
229
+ if (this.slots.size === 0)
230
+ return undefined;
231
+ return [...this.slots.values()].map((slot) => slot.systemPromptSection).join("\n\n");
199
232
  }
200
233
  /** Compose the exact provider system prompt for read-only diagnostics. */
201
234
  previewRequestSystemPrompt(base) {
@@ -209,55 +242,81 @@ export class SkillVaultController {
209
242
  /** Record host-observed work derived from an active skill, independent of agent cooperation. */
210
243
  noteActivity() {
211
244
  const now = this.now();
212
- if (this.current.state === "active") {
213
- this.current = { ...this.current, lastUsedAtMs: now };
245
+ for (const [name, slot] of this.slots) {
246
+ if (slot.state === "active") {
247
+ this.slots.set(name, { ...slot, lastUsedAtMs: now });
248
+ }
214
249
  }
215
250
  }
251
+ slotStatus(slot, now) {
252
+ if (slot.state === "loaded_pending") {
253
+ return { state: "loaded_pending", name: slot.skill.name, loadedAtMs: slot.loadedAtMs };
254
+ }
255
+ const idleForMs = Math.max(0, now - slot.lastUsedAtMs);
256
+ return {
257
+ state: "active",
258
+ name: slot.skill.name,
259
+ loadedAtMs: slot.loadedAtMs,
260
+ lastUsedAtMs: slot.lastUsedAtMs,
261
+ idleForMs,
262
+ expiresInMs: Math.max(0, this.idleTimeoutMs - idleForMs),
263
+ useCount: slot.useCount,
264
+ };
265
+ }
216
266
  reconcile(now) {
217
- if (!this.reconcileResource())
218
- return;
219
- const current = this.current;
220
- if (current.state === "unloaded")
267
+ if (this.slots.size === 0)
221
268
  return;
222
- const lastUsedAtMs = current.state === "loaded_pending" ? current.loadedAtMs : current.lastUsedAtMs;
223
- if (now - lastUsedAtMs >= this.idleTimeoutMs) {
224
- this.replaceState({ state: "unloaded", reason: "idle_expired" });
269
+ const maxBodyBytes = this.resolveMaxBodyBytes();
270
+ const skills = this.getSkills();
271
+ const next = new Map(this.slots);
272
+ let reason;
273
+ for (const [name, slot] of this.slots) {
274
+ const slotReason = this.reconcileSlot(slot, skills, maxBodyBytes, now);
275
+ if (slotReason !== undefined) {
276
+ next.delete(name);
277
+ reason = slotReason;
278
+ }
225
279
  }
226
- }
227
- reconcileResource() {
228
- if (this.current.state === "unloaded")
229
- return false;
230
- const loaded = this.current;
231
- if (loaded.bodyBytes > this.resolveMaxBodyBytes()) {
232
- this.replaceState({ state: "unloaded", reason: "budget_exceeded" });
233
- return false;
280
+ while (aggregateBodyBytes(next) > maxBodyBytes) {
281
+ const victim = leastRecentlyUsedName(next);
282
+ if (!victim)
283
+ break;
284
+ next.delete(victim);
285
+ reason = "budget_exceeded";
234
286
  }
235
- const currentSkill = this.getSkills().find((skill) => skill.name === loaded.skill.name &&
236
- skill.filePath === loaded.skill.filePath &&
237
- (loaded.requester === "user" || !skill.disableModelInvocation));
238
- if (!currentSkill) {
239
- this.replaceState({ state: "unloaded", reason: "resource_unavailable" });
240
- return false;
287
+ if (next.size !== this.slots.size) {
288
+ this.replaceState(next, reason);
241
289
  }
290
+ }
291
+ reconcileSlot(slot, skills, maxBodyBytes, now) {
292
+ if (slot.bodyBytes > maxBodyBytes)
293
+ return "budget_exceeded";
294
+ const currentSkill = skills.find((skill) => skill.name === slot.skill.name &&
295
+ skill.filePath === slot.skill.filePath &&
296
+ (slot.requester === "user" || !skill.disableModelInvocation));
297
+ if (!currentSkill)
298
+ return "resource_unavailable";
242
299
  try {
243
- const file = statSync(loaded.skill.filePath);
244
- if (file.dev !== loaded.fileDevice ||
245
- file.ino !== loaded.fileInode ||
246
- file.size !== loaded.fileSize ||
247
- file.mtimeMs !== loaded.fileModifiedAtMs ||
248
- file.ctimeMs !== loaded.fileChangedAtMs) {
249
- this.replaceState({ state: "unloaded", reason: "resource_unavailable" });
250
- return false;
300
+ const file = statSync(slot.skill.filePath);
301
+ if (file.dev !== slot.fileDevice ||
302
+ file.ino !== slot.fileInode ||
303
+ file.size !== slot.fileSize ||
304
+ file.mtimeMs !== slot.fileModifiedAtMs ||
305
+ file.ctimeMs !== slot.fileChangedAtMs) {
306
+ return "resource_unavailable";
251
307
  }
252
308
  }
253
309
  catch {
254
- this.replaceState({ state: "unloaded", reason: "resource_unavailable" });
255
- return false;
310
+ return "resource_unavailable";
256
311
  }
257
- return true;
312
+ if (now - slotLastUsedAtMs(slot) >= this.idleTimeoutMs)
313
+ return "idle_expired";
314
+ return undefined;
258
315
  }
259
- replaceState(state) {
260
- this.current = state;
316
+ replaceState(slots, unloadReason) {
317
+ this.slots = slots;
318
+ if (unloadReason !== undefined)
319
+ this.unloadReason = unloadReason;
261
320
  this.contextRevision++;
262
321
  }
263
322
  resolveMaxBodyBytes() {
@@ -1 +1 @@
1
- {"version":3,"file":"skill-vault.js","sourceRoot":"","sources":["../../src/core/skill-vault.ts"],"names":[],"mappings":"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","sourcesContent":["import { statSync } from \"node:fs\";\nimport { performance } from \"node:perf_hooks\";\nimport { composeRequestSystemPrompt } from \"@caupulican/pi-agent-core/provider-request-planner\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { stripResourceProfileBlocks } from \"./resource-profile-blocks.ts\";\nimport { MAX_SKILL_FRONTMATTER_BYTES, type Skill, type SkillFrontmatter } from \"./skills.ts\";\nimport { readBoundedTextFileSync, sameFileVersion } from \"./util/bounded-file.ts\";\n\nexport const DEFAULT_SKILL_IDLE_TIMEOUT_MS = 10 * 60 * 1000;\nexport const MAX_ACTIVE_SKILL_BODY_BYTES = 64 * 1024;\nexport const MIN_ACTIVE_SKILL_BODY_BYTES = 4 * 1024;\nconst MAX_SEARCH_RESULTS = 5;\nconst MAX_SEARCH_DESCRIPTION_CHARS = 240;\n\ntype SkillVaultRequester = \"model\" | \"user\";\ntype SkillVaultUnloadReason = \"explicit\" | \"idle_expired\" | \"resource_unavailable\" | \"budget_exceeded\";\n\ninterface LoadedSkill {\n\tskill: Skill;\n\tbodyBytes: number;\n\tsystemPromptSection: string;\n\trequester: SkillVaultRequester;\n\tloadedAtMs: number;\n\tfileDevice: number;\n\tfileInode: number;\n\tfileSize: number;\n\tfileModifiedAtMs: number;\n\tfileChangedAtMs: number;\n}\n\ntype SkillVaultState =\n\t| { state: \"unloaded\"; reason?: SkillVaultUnloadReason }\n\t| ({ state: \"loaded_pending\" } & LoadedSkill)\n\t| ({ state: \"active\"; lastUsedAtMs: number; useCount: number } & LoadedSkill);\n\nexport interface SkillVaultStatus {\n\tstate: SkillVaultState[\"state\"];\n\tidleTimeoutMs: number;\n\tname?: string;\n\tloadedAtMs?: number;\n\tlastUsedAtMs?: number;\n\tidleForMs?: number;\n\texpiresInMs?: number;\n\tuseCount?: number;\n\treason?: SkillVaultUnloadReason;\n}\n\nexport interface SkillSearchResult {\n\tcandidates: Array<{ name: string; description: string }>;\n}\n\nexport type SkillLoadResult =\n\t| { ok: true; state: \"loaded_pending\"; name: string; replaced?: string }\n\t| { ok: false; reason: \"not_found\" | \"body_too_large\" | \"invalid_body\" | \"read_failed\"; message: string };\n\nexport interface SkillVaultControllerOptions {\n\tgetSkills(): readonly Skill[];\n\tnow?: () => number;\n\tidleTimeoutMs?: number;\n\tgetMaxBodyBytes?: () => number;\n\tonSkillUsed?: (skill: Skill, usedAtMs: number) => void;\n}\n\nfunction compactDescription(description: string): string {\n\tconst normalized = description.replace(/\\s+/g, \" \").trim();\n\tif (normalized.length <= MAX_SEARCH_DESCRIPTION_CHARS) return normalized;\n\treturn `${normalized.slice(0, MAX_SEARCH_DESCRIPTION_CHARS - 1)}…`;\n}\n\nfunction queryTokens(value: string): string[] {\n\treturn [...new Set(value.toLowerCase().match(/[\\p{L}\\p{N}]+/gu) ?? [])];\n}\n\nfunction searchScore(skill: Skill, query: string, tokens: readonly string[]): number {\n\tconst name = skill.name.toLowerCase();\n\tconst description = skill.description.toLowerCase();\n\tlet score = name === query ? 100 : name.includes(query) ? 40 : description.includes(query) ? 20 : 0;\n\tfor (const token of tokens) {\n\t\tif (name === token) score += 16;\n\t\telse if (name.includes(token)) score += 8;\n\t\tif (description.includes(token)) score += 3;\n\t}\n\treturn score;\n}\n\nfunction activeSkillContext(skill: Skill, body: string): string {\n\treturn [`ACTIVE SKILL ${skill.name}`, `BASE ${skill.baseDir}`, \"NON-NEGOTIABLE WHILE ACTIVE:\", body].join(\"\\n\");\n}\n\n/** Reserve at most roughly one context token's worth of bytes per advertised context token. */\nexport function resolveActiveSkillBodyByteLimit(contextWindow: number | undefined): number {\n\tif (contextWindow === undefined || !Number.isFinite(contextWindow) || contextWindow <= 0) {\n\t\treturn MAX_ACTIVE_SKILL_BODY_BYTES;\n\t}\n\treturn Math.min(MAX_ACTIVE_SKILL_BODY_BYTES, Math.max(MIN_ACTIVE_SKILL_BODY_BYTES, Math.floor(contextWindow)));\n}\n\n/** One host-owned, event-driven lifecycle for lazy skill discovery and transient context projection. */\nexport class SkillVaultController {\n\tprivate readonly getSkills: () => readonly Skill[];\n\tprivate readonly now: () => number;\n\tprivate readonly idleTimeoutMs: number;\n\tprivate readonly getMaxBodyBytes: () => number;\n\tprivate readonly onSkillUsed: ((skill: Skill, usedAtMs: number) => void) | undefined;\n\tprivate current: SkillVaultState = { state: \"unloaded\" };\n\tprivate contextRevision = 0;\n\n\tconstructor(options: SkillVaultControllerOptions) {\n\t\tif (!Number.isFinite(options.idleTimeoutMs ?? DEFAULT_SKILL_IDLE_TIMEOUT_MS)) {\n\t\t\tthrow new TypeError(\"Skill idle timeout must be finite.\");\n\t\t}\n\t\tthis.getSkills = options.getSkills;\n\t\tthis.now = options.now ?? (() => performance.now());\n\t\tthis.idleTimeoutMs = Math.max(1, options.idleTimeoutMs ?? DEFAULT_SKILL_IDLE_TIMEOUT_MS);\n\t\tthis.getMaxBodyBytes = options.getMaxBodyBytes ?? (() => MAX_ACTIVE_SKILL_BODY_BYTES);\n\t\tthis.onSkillUsed = options.onSkillUsed;\n\t}\n\n\tsearch(rawQuery: string): SkillSearchResult {\n\t\tconst query = rawQuery.trim().toLowerCase();\n\t\tconst tokens = queryTokens(query);\n\t\tif (!query || tokens.length === 0) return { candidates: [] };\n\t\tconst candidates = this.getSkills()\n\t\t\t.filter((skill) => !skill.disableModelInvocation)\n\t\t\t.map((skill) => ({ skill, score: searchScore(skill, query, tokens) }))\n\t\t\t.filter((entry) => entry.score > 0)\n\t\t\t.sort((left, right) => right.score - left.score || left.skill.name.localeCompare(right.skill.name))\n\t\t\t.slice(0, MAX_SEARCH_RESULTS)\n\t\t\t.map(({ skill }) => ({ name: skill.name, description: compactDescription(skill.description) }));\n\t\treturn { candidates };\n\t}\n\n\tload(name: string, requester: SkillVaultRequester): SkillLoadResult {\n\t\tconst skill = this.getSkills().find(\n\t\t\t(candidate) => candidate.name === name && (requester === \"user\" || !candidate.disableModelInvocation),\n\t\t);\n\t\tif (!skill) {\n\t\t\treturn { ok: false, reason: \"not_found\", message: `No eligible skill named ${JSON.stringify(name)}.` };\n\t\t}\n\t\tconst maxBodyBytes = this.resolveMaxBodyBytes();\n\t\tlet before: ReturnType<typeof statSync>;\n\t\ttry {\n\t\t\tbefore = statSync(skill.filePath);\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: \"read_failed\", message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = readBoundedTextFileSync(\n\t\t\t\tskill.filePath,\n\t\t\t\tmaxBodyBytes + MAX_SKILL_FRONTMATTER_BYTES,\n\t\t\t\t`Skill ${JSON.stringify(skill.name)}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn message.includes(\"exceeds its byte limit\")\n\t\t\t\t? {\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\treason: \"body_too_large\",\n\t\t\t\t\t\tmessage: `Skill body exceeds ${maxBodyBytes} bytes; not truncated.`,\n\t\t\t\t\t}\n\t\t\t\t: { ok: false, reason: \"read_failed\", message };\n\t\t}\n\t\tconst parsed = parseFrontmatter<SkillFrontmatter>(raw);\n\t\tconst currentName = parsed.frontmatter.name ?? skill.name;\n\t\tconst body = stripResourceProfileBlocks(parsed.body).trim();\n\t\tif (currentName !== skill.name || !parsed.frontmatter.description || !body) {\n\t\t\treturn { ok: false, reason: \"invalid_body\", message: \"Skill metadata changed or its body is empty.\" };\n\t\t}\n\t\tconst bodyBytes = Buffer.byteLength(body, \"utf8\");\n\t\tif (bodyBytes > maxBodyBytes) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\treason: \"body_too_large\",\n\t\t\t\tmessage: `Skill body exceeds ${maxBodyBytes} bytes; not truncated.`,\n\t\t\t};\n\t\t}\n\t\tlet file: ReturnType<typeof statSync>;\n\t\ttry {\n\t\t\tfile = statSync(skill.filePath);\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: \"read_failed\", message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t\tif (!sameFileVersion(before, file)) {\n\t\t\treturn { ok: false, reason: \"read_failed\", message: \"Skill changed while it was being loaded.\" };\n\t\t}\n\t\tconst replaced = this.current.state === \"unloaded\" ? undefined : this.current.skill.name;\n\t\tthis.replaceState({\n\t\t\tstate: \"loaded_pending\",\n\t\t\tskill,\n\t\t\tbodyBytes,\n\t\t\tsystemPromptSection: activeSkillContext(skill, body),\n\t\t\trequester,\n\t\t\tloadedAtMs: this.now(),\n\t\t\tfileDevice: file.dev,\n\t\t\tfileInode: file.ino,\n\t\t\tfileSize: file.size,\n\t\t\tfileModifiedAtMs: file.mtimeMs,\n\t\t\tfileChangedAtMs: file.ctimeMs,\n\t\t});\n\t\treturn { ok: true, state: \"loaded_pending\", name: skill.name, ...(replaced ? { replaced } : {}) };\n\t}\n\n\tunload(): { ok: true; unloaded?: string } {\n\t\tconst unloaded = this.current.state === \"unloaded\" ? undefined : this.current.skill.name;\n\t\tthis.replaceState({ state: \"unloaded\", reason: \"explicit\" });\n\t\treturn { ok: true, ...(unloaded ? { unloaded } : {}) };\n\t}\n\n\tstatus(): SkillVaultStatus {\n\t\tconst now = this.now();\n\t\tthis.reconcile(now);\n\t\tif (this.current.state === \"unloaded\") {\n\t\t\treturn {\n\t\t\t\tstate: \"unloaded\",\n\t\t\t\tidleTimeoutMs: this.idleTimeoutMs,\n\t\t\t\t...(this.current.reason ? { reason: this.current.reason } : {}),\n\t\t\t};\n\t\t}\n\t\tif (this.current.state === \"loaded_pending\") {\n\t\t\treturn {\n\t\t\t\tstate: \"loaded_pending\",\n\t\t\t\tidleTimeoutMs: this.idleTimeoutMs,\n\t\t\t\tname: this.current.skill.name,\n\t\t\t\tloadedAtMs: this.current.loadedAtMs,\n\t\t\t};\n\t\t}\n\t\tconst idleForMs = Math.max(0, now - this.current.lastUsedAtMs);\n\t\treturn {\n\t\t\tstate: \"active\",\n\t\t\tidleTimeoutMs: this.idleTimeoutMs,\n\t\t\tname: this.current.skill.name,\n\t\t\tloadedAtMs: this.current.loadedAtMs,\n\t\t\tlastUsedAtMs: this.current.lastUsedAtMs,\n\t\t\tidleForMs,\n\t\t\texpiresInMs: Math.max(0, this.idleTimeoutMs - idleForMs),\n\t\t\tuseCount: this.current.useCount,\n\t\t};\n\t}\n\n\tcommitSystemPromptSection(): string | undefined {\n\t\tconst now = this.now();\n\t\tthis.reconcile(now);\n\t\tif (this.current.state === \"unloaded\") return undefined;\n\t\tconst loaded = this.current;\n\t\tconst firstUse = loaded.state === \"loaded_pending\";\n\t\tconst useCount = firstUse ? 1 : loaded.useCount + 1;\n\t\tthis.current = { ...loaded, state: \"active\", lastUsedAtMs: now, useCount };\n\t\tif (firstUse) {\n\t\t\ttry {\n\t\t\t\tthis.onSkillUsed?.(loaded.skill, now);\n\t\t\t} catch {\n\t\t\t\t// Usage telemetry must never block skill application.\n\t\t\t}\n\t\t}\n\t\treturn loaded.systemPromptSection;\n\t}\n\n\t/** Model the next request's transient system cost without treating a diagnostic read as use. */\n\tpreviewSystemPromptSection(): string | undefined {\n\t\tthis.reconcile(this.now());\n\t\treturn this.current.state === \"unloaded\" ? undefined : this.current.systemPromptSection;\n\t}\n\n\t/** Compose the exact provider system prompt for read-only diagnostics. */\n\tpreviewRequestSystemPrompt(base: string | undefined): string | undefined {\n\t\treturn composeRequestSystemPrompt(base, this.previewSystemPromptSection());\n\t}\n\n\t/** Monotonic identity for provider-visible skill projection changes. */\n\tgetContextRevision(): number {\n\t\tthis.reconcile(this.now());\n\t\treturn this.contextRevision;\n\t}\n\n\t/** Record host-observed work derived from an active skill, independent of agent cooperation. */\n\tnoteActivity(): void {\n\t\tconst now = this.now();\n\t\tif (this.current.state === \"active\") {\n\t\t\tthis.current = { ...this.current, lastUsedAtMs: now };\n\t\t}\n\t}\n\n\tprivate reconcile(now: number): void {\n\t\tif (!this.reconcileResource()) return;\n\t\tconst current = this.current;\n\t\tif (current.state === \"unloaded\") return;\n\t\tconst lastUsedAtMs = current.state === \"loaded_pending\" ? current.loadedAtMs : current.lastUsedAtMs;\n\t\tif (now - lastUsedAtMs >= this.idleTimeoutMs) {\n\t\t\tthis.replaceState({ state: \"unloaded\", reason: \"idle_expired\" });\n\t\t}\n\t}\n\n\tprivate reconcileResource(): boolean {\n\t\tif (this.current.state === \"unloaded\") return false;\n\t\tconst loaded = this.current;\n\t\tif (loaded.bodyBytes > this.resolveMaxBodyBytes()) {\n\t\t\tthis.replaceState({ state: \"unloaded\", reason: \"budget_exceeded\" });\n\t\t\treturn false;\n\t\t}\n\t\tconst currentSkill = this.getSkills().find(\n\t\t\t(skill) =>\n\t\t\t\tskill.name === loaded.skill.name &&\n\t\t\t\tskill.filePath === loaded.skill.filePath &&\n\t\t\t\t(loaded.requester === \"user\" || !skill.disableModelInvocation),\n\t\t);\n\t\tif (!currentSkill) {\n\t\t\tthis.replaceState({ state: \"unloaded\", reason: \"resource_unavailable\" });\n\t\t\treturn false;\n\t\t}\n\t\ttry {\n\t\t\tconst file = statSync(loaded.skill.filePath);\n\t\t\tif (\n\t\t\t\tfile.dev !== loaded.fileDevice ||\n\t\t\t\tfile.ino !== loaded.fileInode ||\n\t\t\t\tfile.size !== loaded.fileSize ||\n\t\t\t\tfile.mtimeMs !== loaded.fileModifiedAtMs ||\n\t\t\t\tfile.ctimeMs !== loaded.fileChangedAtMs\n\t\t\t) {\n\t\t\t\tthis.replaceState({ state: \"unloaded\", reason: \"resource_unavailable\" });\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch {\n\t\t\tthis.replaceState({ state: \"unloaded\", reason: \"resource_unavailable\" });\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate replaceState(state: SkillVaultState): void {\n\t\tthis.current = state;\n\t\tthis.contextRevision++;\n\t}\n\n\tprivate resolveMaxBodyBytes(): number {\n\t\tconst configured = this.getMaxBodyBytes();\n\t\tif (!Number.isFinite(configured)) return 1;\n\t\treturn Math.min(MAX_ACTIVE_SKILL_BODY_BYTES, Math.max(1, Math.floor(configured)));\n\t}\n}\n"]}
1
+ {"version":3,"file":"skill-vault.js","sourceRoot":"","sources":["../../src/core/skill-vault.ts"],"names":[],"mappings":"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","sourcesContent":["import { statSync } from \"node:fs\";\nimport { performance } from \"node:perf_hooks\";\nimport { composeRequestSystemPrompt } from \"@caupulican/pi-agent-core/provider-request-planner\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { stripResourceProfileBlocks } from \"./resource-profile-blocks.ts\";\nimport { MAX_SKILL_FRONTMATTER_BYTES, type Skill, type SkillFrontmatter } from \"./skills.ts\";\nimport { readBoundedTextFileSync, sameFileVersion } from \"./util/bounded-file.ts\";\n\nexport const DEFAULT_SKILL_IDLE_TIMEOUT_MS = 10 * 60 * 1000;\nexport const MAX_ACTIVE_SKILL_BODY_BYTES = 64 * 1024;\nexport const MIN_ACTIVE_SKILL_BODY_BYTES = 4 * 1024;\nexport const MAX_LOADED_SKILLS = 3;\nconst MAX_SEARCH_RESULTS = 5;\nconst MAX_SEARCH_DESCRIPTION_CHARS = 240;\n\ntype SkillVaultRequester = \"model\" | \"user\";\ntype SkillVaultUnloadReason = \"explicit\" | \"idle_expired\" | \"resource_unavailable\" | \"budget_exceeded\";\n\ninterface LoadedSkill {\n\tskill: Skill;\n\tbodyBytes: number;\n\tsystemPromptSection: string;\n\trequester: SkillVaultRequester;\n\tloadedAtMs: number;\n\tfileDevice: number;\n\tfileInode: number;\n\tfileSize: number;\n\tfileModifiedAtMs: number;\n\tfileChangedAtMs: number;\n}\n\ntype SkillSlotState =\n\t| ({ state: \"loaded_pending\" } & LoadedSkill)\n\t| ({ state: \"active\"; lastUsedAtMs: number; useCount: number } & LoadedSkill);\n\nexport interface SkillSlotStatus {\n\tstate: SkillSlotState[\"state\"];\n\tname: string;\n\tloadedAtMs: number;\n\tlastUsedAtMs?: number;\n\tidleForMs?: number;\n\texpiresInMs?: number;\n\tuseCount?: number;\n}\n\nexport interface SkillVaultStatus {\n\tidleTimeoutMs: number;\n\tslots: SkillSlotStatus[];\n\treason?: SkillVaultUnloadReason;\n}\n\nexport interface SkillSearchResult {\n\tcandidates: Array<{ name: string; description: string }>;\n}\n\nexport type SkillLoadResult =\n\t| { ok: true; state: \"loaded_pending\"; name: string; baseDir: string; evicted?: string[] }\n\t| { ok: false; reason: \"not_found\" | \"body_too_large\" | \"invalid_body\" | \"read_failed\"; message: string };\n\nexport interface SkillVaultControllerOptions {\n\tgetSkills(): readonly Skill[];\n\tnow?: () => number;\n\tidleTimeoutMs?: number;\n\tgetMaxBodyBytes?: () => number;\n\tonSkillUsed?: (skill: Skill, usedAtMs: number) => void;\n}\n\nfunction compactDescription(description: string): string {\n\tconst normalized = description.replace(/\\s+/g, \" \").trim();\n\tif (normalized.length <= MAX_SEARCH_DESCRIPTION_CHARS) return normalized;\n\treturn `${normalized.slice(0, MAX_SEARCH_DESCRIPTION_CHARS - 1)}…`;\n}\n\nfunction queryTokens(value: string): string[] {\n\treturn [...new Set(value.toLowerCase().match(/[\\p{L}\\p{N}]+/gu) ?? [])];\n}\n\nfunction searchScore(skill: Skill, query: string, tokens: readonly string[]): number {\n\tconst name = skill.name.toLowerCase();\n\tconst description = skill.description.toLowerCase();\n\tlet score = name === query ? 100 : name.includes(query) ? 40 : description.includes(query) ? 20 : 0;\n\tfor (const token of tokens) {\n\t\tif (name === token) score += 16;\n\t\telse if (name.includes(token)) score += 8;\n\t\tif (description.includes(token)) score += 3;\n\t}\n\treturn score;\n}\n\nfunction activeSkillContext(skill: Skill, body: string): string {\n\treturn [`ACTIVE SKILL ${skill.name}`, `BASE ${skill.baseDir}`, \"NON-NEGOTIABLE WHILE ACTIVE:\", body].join(\"\\n\");\n}\n\nfunction slotLastUsedAtMs(slot: SkillSlotState): number {\n\treturn slot.state === \"loaded_pending\" ? slot.loadedAtMs : slot.lastUsedAtMs;\n}\n\nfunction aggregateBodyBytes(slots: ReadonlyMap<string, SkillSlotState>): number {\n\tlet total = 0;\n\tfor (const slot of slots.values()) total += slot.bodyBytes;\n\treturn total;\n}\n\nfunction leastRecentlyUsedName(slots: ReadonlyMap<string, SkillSlotState>, excludeName?: string): string | undefined {\n\tlet lruName: string | undefined;\n\tlet lruUsedAtMs = Number.POSITIVE_INFINITY;\n\tfor (const [name, slot] of slots) {\n\t\tif (name === excludeName) continue;\n\t\tconst usedAtMs = slotLastUsedAtMs(slot);\n\t\tif (usedAtMs < lruUsedAtMs) {\n\t\t\tlruUsedAtMs = usedAtMs;\n\t\t\tlruName = name;\n\t\t}\n\t}\n\treturn lruName;\n}\n\n/** Reserve at most roughly one context token's worth of bytes per advertised context token. */\nexport function resolveActiveSkillBodyByteLimit(contextWindow: number | undefined): number {\n\tif (contextWindow === undefined || !Number.isFinite(contextWindow) || contextWindow <= 0) {\n\t\treturn MAX_ACTIVE_SKILL_BODY_BYTES;\n\t}\n\treturn Math.min(MAX_ACTIVE_SKILL_BODY_BYTES, Math.max(MIN_ACTIVE_SKILL_BODY_BYTES, Math.floor(contextWindow)));\n}\n\n/** One host-owned, event-driven lifecycle for lazy skill discovery and transient context projection. */\nexport class SkillVaultController {\n\tprivate readonly getSkills: () => readonly Skill[];\n\tprivate readonly now: () => number;\n\tprivate readonly idleTimeoutMs: number;\n\tprivate readonly getMaxBodyBytes: () => number;\n\tprivate readonly onSkillUsed: ((skill: Skill, usedAtMs: number) => void) | undefined;\n\tprivate slots = new Map<string, SkillSlotState>();\n\tprivate unloadReason: SkillVaultUnloadReason | undefined;\n\tprivate contextRevision = 0;\n\n\tconstructor(options: SkillVaultControllerOptions) {\n\t\tif (!Number.isFinite(options.idleTimeoutMs ?? DEFAULT_SKILL_IDLE_TIMEOUT_MS)) {\n\t\t\tthrow new TypeError(\"Skill idle timeout must be finite.\");\n\t\t}\n\t\tthis.getSkills = options.getSkills;\n\t\tthis.now = options.now ?? (() => performance.now());\n\t\tthis.idleTimeoutMs = Math.max(1, options.idleTimeoutMs ?? DEFAULT_SKILL_IDLE_TIMEOUT_MS);\n\t\tthis.getMaxBodyBytes = options.getMaxBodyBytes ?? (() => MAX_ACTIVE_SKILL_BODY_BYTES);\n\t\tthis.onSkillUsed = options.onSkillUsed;\n\t}\n\n\tsearch(rawQuery: string): SkillSearchResult {\n\t\tconst query = rawQuery.trim().toLowerCase();\n\t\tconst tokens = queryTokens(query);\n\t\tif (!query || tokens.length === 0) return { candidates: [] };\n\t\tconst candidates = this.getSkills()\n\t\t\t.filter((skill) => !skill.disableModelInvocation)\n\t\t\t.map((skill) => ({ skill, score: searchScore(skill, query, tokens) }))\n\t\t\t.filter((entry) => entry.score > 0)\n\t\t\t.sort((left, right) => right.score - left.score || left.skill.name.localeCompare(right.skill.name))\n\t\t\t.slice(0, MAX_SEARCH_RESULTS)\n\t\t\t.map(({ skill }) => ({ name: skill.name, description: compactDescription(skill.description) }));\n\t\treturn { candidates };\n\t}\n\n\tload(name: string, requester: SkillVaultRequester): SkillLoadResult {\n\t\tconst now = this.now();\n\t\tthis.reconcile(now);\n\t\tconst skill = this.getSkills().find(\n\t\t\t(candidate) => candidate.name === name && (requester === \"user\" || !candidate.disableModelInvocation),\n\t\t);\n\t\tif (!skill) {\n\t\t\treturn { ok: false, reason: \"not_found\", message: `No eligible skill named ${JSON.stringify(name)}.` };\n\t\t}\n\t\tconst maxBodyBytes = this.resolveMaxBodyBytes();\n\t\tlet before: ReturnType<typeof statSync>;\n\t\ttry {\n\t\t\tbefore = statSync(skill.filePath);\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: \"read_failed\", message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = readBoundedTextFileSync(\n\t\t\t\tskill.filePath,\n\t\t\t\tmaxBodyBytes + MAX_SKILL_FRONTMATTER_BYTES,\n\t\t\t\t`Skill ${JSON.stringify(skill.name)}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn message.includes(\"exceeds its byte limit\")\n\t\t\t\t? {\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\treason: \"body_too_large\",\n\t\t\t\t\t\tmessage: `Skill body exceeds ${maxBodyBytes} bytes; not truncated.`,\n\t\t\t\t\t}\n\t\t\t\t: { ok: false, reason: \"read_failed\", message };\n\t\t}\n\t\tconst parsed = parseFrontmatter<SkillFrontmatter>(raw);\n\t\tconst currentName = parsed.frontmatter.name ?? skill.name;\n\t\tconst body = stripResourceProfileBlocks(parsed.body).trim();\n\t\tif (currentName !== skill.name || !parsed.frontmatter.description || !body) {\n\t\t\treturn { ok: false, reason: \"invalid_body\", message: \"Skill metadata changed or its body is empty.\" };\n\t\t}\n\t\tconst bodyBytes = Buffer.byteLength(body, \"utf8\");\n\t\tif (bodyBytes > maxBodyBytes) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\treason: \"body_too_large\",\n\t\t\t\tmessage: `Skill body exceeds ${maxBodyBytes} bytes; not truncated.`,\n\t\t\t};\n\t\t}\n\t\tlet file: ReturnType<typeof statSync>;\n\t\ttry {\n\t\t\tfile = statSync(skill.filePath);\n\t\t} catch (error) {\n\t\t\treturn { ok: false, reason: \"read_failed\", message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t\tif (!sameFileVersion(before, file)) {\n\t\t\treturn { ok: false, reason: \"read_failed\", message: \"Skill changed while it was being loaded.\" };\n\t\t}\n\t\tconst next = new Map(this.slots);\n\t\tnext.set(skill.name, {\n\t\t\tstate: \"loaded_pending\",\n\t\t\tskill,\n\t\t\tbodyBytes,\n\t\t\tsystemPromptSection: activeSkillContext(skill, body),\n\t\t\trequester,\n\t\t\tloadedAtMs: now,\n\t\t\tfileDevice: file.dev,\n\t\t\tfileInode: file.ino,\n\t\t\tfileSize: file.size,\n\t\t\tfileModifiedAtMs: file.mtimeMs,\n\t\t\tfileChangedAtMs: file.ctimeMs,\n\t\t});\n\t\tconst evicted: string[] = [];\n\t\twhile (next.size > MAX_LOADED_SKILLS || aggregateBodyBytes(next) > maxBodyBytes) {\n\t\t\tconst victim = leastRecentlyUsedName(next, skill.name);\n\t\t\tif (!victim) break;\n\t\t\tnext.delete(victim);\n\t\t\tevicted.push(victim);\n\t\t}\n\t\tthis.replaceState(next);\n\t\treturn {\n\t\t\tok: true,\n\t\t\tstate: \"loaded_pending\",\n\t\t\tname: skill.name,\n\t\t\tbaseDir: skill.baseDir,\n\t\t\t...(evicted.length > 0 ? { evicted } : {}),\n\t\t};\n\t}\n\n\tunload(name?: string): { ok: true; unloaded: string[] } {\n\t\tthis.reconcile(this.now());\n\t\tconst target = name?.trim();\n\t\tconst unloaded = !target ? [...this.slots.keys()] : this.slots.has(target) ? [target] : [];\n\t\tif (unloaded.length > 0) {\n\t\t\tconst next = new Map(this.slots);\n\t\t\tfor (const slotName of unloaded) next.delete(slotName);\n\t\t\tthis.replaceState(next, \"explicit\");\n\t\t}\n\t\treturn { ok: true, unloaded };\n\t}\n\n\tstatus(): SkillVaultStatus {\n\t\tconst now = this.now();\n\t\tthis.reconcile(now);\n\t\tconst slots = [...this.slots.values()].map((slot) => this.slotStatus(slot, now));\n\t\treturn {\n\t\t\tidleTimeoutMs: this.idleTimeoutMs,\n\t\t\tslots,\n\t\t\t...(slots.length === 0 && this.unloadReason ? { reason: this.unloadReason } : {}),\n\t\t};\n\t}\n\n\tcommitSystemPromptSection(): string | undefined {\n\t\tconst now = this.now();\n\t\tthis.reconcile(now);\n\t\tif (this.slots.size === 0) return undefined;\n\t\tconst sections: string[] = [];\n\t\tfor (const [name, slot] of this.slots) {\n\t\t\tsections.push(slot.systemPromptSection);\n\t\t\tconst firstUse = slot.state === \"loaded_pending\";\n\t\t\tconst useCount = firstUse ? 1 : slot.useCount + 1;\n\t\t\tthis.slots.set(name, { ...slot, state: \"active\", lastUsedAtMs: now, useCount });\n\t\t\tif (firstUse) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.onSkillUsed?.(slot.skill, now);\n\t\t\t\t} catch {\n\t\t\t\t\t// Usage telemetry must never block skill application.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn sections.join(\"\\n\\n\");\n\t}\n\n\t/** Model the next request's transient system cost without treating a diagnostic read as use. */\n\tpreviewSystemPromptSection(): string | undefined {\n\t\tthis.reconcile(this.now());\n\t\tif (this.slots.size === 0) return undefined;\n\t\treturn [...this.slots.values()].map((slot) => slot.systemPromptSection).join(\"\\n\\n\");\n\t}\n\n\t/** Compose the exact provider system prompt for read-only diagnostics. */\n\tpreviewRequestSystemPrompt(base: string | undefined): string | undefined {\n\t\treturn composeRequestSystemPrompt(base, this.previewSystemPromptSection());\n\t}\n\n\t/** Monotonic identity for provider-visible skill projection changes. */\n\tgetContextRevision(): number {\n\t\tthis.reconcile(this.now());\n\t\treturn this.contextRevision;\n\t}\n\n\t/** Record host-observed work derived from an active skill, independent of agent cooperation. */\n\tnoteActivity(): void {\n\t\tconst now = this.now();\n\t\tfor (const [name, slot] of this.slots) {\n\t\t\tif (slot.state === \"active\") {\n\t\t\t\tthis.slots.set(name, { ...slot, lastUsedAtMs: now });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate slotStatus(slot: SkillSlotState, now: number): SkillSlotStatus {\n\t\tif (slot.state === \"loaded_pending\") {\n\t\t\treturn { state: \"loaded_pending\", name: slot.skill.name, loadedAtMs: slot.loadedAtMs };\n\t\t}\n\t\tconst idleForMs = Math.max(0, now - slot.lastUsedAtMs);\n\t\treturn {\n\t\t\tstate: \"active\",\n\t\t\tname: slot.skill.name,\n\t\t\tloadedAtMs: slot.loadedAtMs,\n\t\t\tlastUsedAtMs: slot.lastUsedAtMs,\n\t\t\tidleForMs,\n\t\t\texpiresInMs: Math.max(0, this.idleTimeoutMs - idleForMs),\n\t\t\tuseCount: slot.useCount,\n\t\t};\n\t}\n\n\tprivate reconcile(now: number): void {\n\t\tif (this.slots.size === 0) return;\n\t\tconst maxBodyBytes = this.resolveMaxBodyBytes();\n\t\tconst skills = this.getSkills();\n\t\tconst next = new Map(this.slots);\n\t\tlet reason: SkillVaultUnloadReason | undefined;\n\t\tfor (const [name, slot] of this.slots) {\n\t\t\tconst slotReason = this.reconcileSlot(slot, skills, maxBodyBytes, now);\n\t\t\tif (slotReason !== undefined) {\n\t\t\t\tnext.delete(name);\n\t\t\t\treason = slotReason;\n\t\t\t}\n\t\t}\n\t\twhile (aggregateBodyBytes(next) > maxBodyBytes) {\n\t\t\tconst victim = leastRecentlyUsedName(next);\n\t\t\tif (!victim) break;\n\t\t\tnext.delete(victim);\n\t\t\treason = \"budget_exceeded\";\n\t\t}\n\t\tif (next.size !== this.slots.size) {\n\t\t\tthis.replaceState(next, reason);\n\t\t}\n\t}\n\n\tprivate reconcileSlot(\n\t\tslot: SkillSlotState,\n\t\tskills: readonly Skill[],\n\t\tmaxBodyBytes: number,\n\t\tnow: number,\n\t): SkillVaultUnloadReason | undefined {\n\t\tif (slot.bodyBytes > maxBodyBytes) return \"budget_exceeded\";\n\t\tconst currentSkill = skills.find(\n\t\t\t(skill) =>\n\t\t\t\tskill.name === slot.skill.name &&\n\t\t\t\tskill.filePath === slot.skill.filePath &&\n\t\t\t\t(slot.requester === \"user\" || !skill.disableModelInvocation),\n\t\t);\n\t\tif (!currentSkill) return \"resource_unavailable\";\n\t\ttry {\n\t\t\tconst file = statSync(slot.skill.filePath);\n\t\t\tif (\n\t\t\t\tfile.dev !== slot.fileDevice ||\n\t\t\t\tfile.ino !== slot.fileInode ||\n\t\t\t\tfile.size !== slot.fileSize ||\n\t\t\t\tfile.mtimeMs !== slot.fileModifiedAtMs ||\n\t\t\t\tfile.ctimeMs !== slot.fileChangedAtMs\n\t\t\t) {\n\t\t\t\treturn \"resource_unavailable\";\n\t\t\t}\n\t\t} catch {\n\t\t\treturn \"resource_unavailable\";\n\t\t}\n\t\tif (now - slotLastUsedAtMs(slot) >= this.idleTimeoutMs) return \"idle_expired\";\n\t\treturn undefined;\n\t}\n\n\tprivate replaceState(slots: Map<string, SkillSlotState>, unloadReason?: SkillVaultUnloadReason): void {\n\t\tthis.slots = slots;\n\t\tif (unloadReason !== undefined) this.unloadReason = unloadReason;\n\t\tthis.contextRevision++;\n\t}\n\n\tprivate resolveMaxBodyBytes(): number {\n\t\tconst configured = this.getMaxBodyBytes();\n\t\tif (!Number.isFinite(configured)) return 1;\n\t\treturn Math.min(MAX_ACTIVE_SKILL_BODY_BYTES, Math.max(1, Math.floor(configured)));\n\t}\n}\n"]}
@@ -41,7 +41,8 @@ export interface BashOperations {
41
41
  * @param command The command to execute
42
42
  * @param cwd Working directory
43
43
  * @param options Execution options
44
- * @returns Promise resolving to exit code (null if killed)
44
+ * @returns Promise resolving to exit code (null if killed) plus, when the backend tracks it,
45
+ * the shell-reported working directory after the command ran
45
46
  */
46
47
  exec: (command: string, cwd: string, options: {
47
48
  onData: (data: Buffer) => void;
@@ -50,6 +51,7 @@ export interface BashOperations {
50
51
  env?: NodeJS.ProcessEnv;
51
52
  }) => Promise<{
52
53
  exitCode: number | null;
54
+ cwd?: string;
53
55
  }>;
54
56
  }
55
57
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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"}