@poncho-ai/harness 0.47.1 → 0.48.0
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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +23 -0
- package/dist/index.d.ts +24 -5
- package/dist/index.js +20 -7
- package/package.json +1 -1
- package/src/harness.ts +38 -6
- package/src/skill-context.ts +11 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.48.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
[34mCLI[39m tsup v8.5.1
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
11
|
+
[32mESM[39m [1mdist/index.js [22m[32m528.41 KB[39m
|
|
12
12
|
[32mESM[39m [1mdist/isolate-VY35DGLM.js [22m[32m49.43 KB[39m
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 216ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 7309ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m87.43 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.48.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#125](https://github.com/cesr/poncho-ai/pull/125) [`ff66aae`](https://github.com/cesr/poncho-ai/commit/ff66aaeebe6017ca9e1ee4b31ffe0d89bdf5ef28) Thanks [@cesr](https://github.com/cesr)! - harness: add `systemSkillPaths` for platform-shipped system skills
|
|
8
|
+
|
|
9
|
+
New optional `HarnessOptions.systemSkillPaths` (absolute directories,
|
|
10
|
+
each scanned for `<name>/SKILL.md` at init). System skills are surfaced
|
|
11
|
+
in `<available_skills>` like any other skill, with their bodies read
|
|
12
|
+
from local disk on activation — letting a platform ship default skills
|
|
13
|
+
with the deploy instead of writing them into every tenant's VFS.
|
|
14
|
+
|
|
15
|
+
Precedence is purely additive: per tenant the skill set resolves as
|
|
16
|
+
repo skills > the tenant's own VFS skills > system skills. So a tenant's
|
|
17
|
+
`/skills/<same-name>/` overrides a same-named system skill (mirroring
|
|
18
|
+
the VFS override behavior platforms already rely on for system jobs),
|
|
19
|
+
and the existing repo-vs-VFS precedence is unchanged. Empty by default —
|
|
20
|
+
no behavior change for existing consumers.
|
|
21
|
+
|
|
22
|
+
Also exports `loadSkillMetadataFromDirs(dirs)` (extracted from
|
|
23
|
+
`loadSkillMetadata`) for scanning an explicit list of absolute skill
|
|
24
|
+
directories.
|
|
25
|
+
|
|
3
26
|
## 0.47.1
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1211,6 +1211,17 @@ interface HarnessOptions {
|
|
|
1211
1211
|
* Empty by default — no system mounts in the CLI / dev workflow.
|
|
1212
1212
|
*/
|
|
1213
1213
|
virtualMounts?: VirtualMount[];
|
|
1214
|
+
/**
|
|
1215
|
+
* Absolute directories of platform-shipped "system" skills. Each is
|
|
1216
|
+
* scanned for `<name>/SKILL.md` at init; the bodies live on local disk
|
|
1217
|
+
* and ship with the deploy. System skills are surfaced in
|
|
1218
|
+
* `<available_skills>` like any other skill, but sit at the LOWEST
|
|
1219
|
+
* precedence: a tenant's own `/skills/<same-name>/` (and a repo skill)
|
|
1220
|
+
* overrides a system skill of the same name. Pair with a read-only
|
|
1221
|
+
* `virtualMounts` entry (e.g. "/system/skills/") if the same files
|
|
1222
|
+
* should also be browsable in the VFS. Empty by default.
|
|
1223
|
+
*/
|
|
1224
|
+
systemSkillPaths?: string[];
|
|
1214
1225
|
}
|
|
1215
1226
|
interface HarnessRunOutput {
|
|
1216
1227
|
runId: string;
|
|
@@ -1243,6 +1254,8 @@ declare class AgentHarness {
|
|
|
1243
1254
|
private loadedConfig?;
|
|
1244
1255
|
private readonly injectedConfig?;
|
|
1245
1256
|
private loadedSkills;
|
|
1257
|
+
private systemSkills;
|
|
1258
|
+
private readonly systemSkillPaths;
|
|
1246
1259
|
private skillFingerprint;
|
|
1247
1260
|
private lastSkillRefreshAt;
|
|
1248
1261
|
private readonly activeSkillNames;
|
|
@@ -1298,10 +1311,15 @@ declare class AgentHarness {
|
|
|
1298
1311
|
private getMemoryStore;
|
|
1299
1312
|
private listActiveSkills;
|
|
1300
1313
|
/**
|
|
1301
|
-
* Resolve the skill set visible to a given tenant
|
|
1302
|
-
*
|
|
1303
|
-
*
|
|
1304
|
-
*
|
|
1314
|
+
* Resolve the skill set visible to a given tenant. Three tiers, by
|
|
1315
|
+
* precedence: repo skills > the tenant's own VFS skills > platform
|
|
1316
|
+
* system skills. So a repo skill wins over a same-named VFS skill
|
|
1317
|
+
* (unchanged), and a tenant's `/skills/<name>/` overrides a same-named
|
|
1318
|
+
* system skill (the deploy-shipped default). Cached per tenant; cache
|
|
1319
|
+
* invalidates on VFS writes under /skills/ via invalidateSkillsForTenant.
|
|
1320
|
+
* System skills are static within a process, so they don't participate
|
|
1321
|
+
* in the fingerprint — but a VFS override does (it changes a /skills
|
|
1322
|
+
* path), which recomputes the cache and lets the override take effect.
|
|
1305
1323
|
*/
|
|
1306
1324
|
private getSkillsForTenant;
|
|
1307
1325
|
invalidateSkillsForTenant(tenantId: string): void;
|
|
@@ -1451,6 +1469,7 @@ declare const parseSkillFrontmatter: (content: string) => {
|
|
|
1451
1469
|
};
|
|
1452
1470
|
} | undefined;
|
|
1453
1471
|
declare const loadSkillMetadata: (workingDir: string, extraSkillPaths?: string[]) => Promise<SkillMetadata[]>;
|
|
1472
|
+
declare const loadSkillMetadataFromDirs: (skillDirs: string[]) => Promise<SkillMetadata[]>;
|
|
1454
1473
|
declare const buildSkillContextWindow: (skills: SkillMetadata[]) => string;
|
|
1455
1474
|
declare const loadVfsSkillMetadata: (engine: StorageEngine, tenantId: string) => Promise<SkillMetadata[]>;
|
|
1456
1475
|
declare const mergeSkills: (repoSkills: SkillMetadata[], vfsSkills: SkillMetadata[], onCollision?: (vfsSkill: SkillMetadata) => void) => SkillMetadata[];
|
|
@@ -2080,4 +2099,4 @@ interface RunConversationTurnResult {
|
|
|
2080
2099
|
}
|
|
2081
2100
|
declare const runConversationTurn: (opts: RunConversationTurnOpts) => Promise<RunConversationTurnResult>;
|
|
2082
2101
|
|
|
2083
|
-
export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type NetworkConfig, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, type SubagentTranscript, type SubagentTranscriptMode, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, type VirtualMount, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, decodeFileInputData, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, normalizeToolAccess, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
|
|
2102
|
+
export { type ActiveConversationRun, type ActiveSubagentRun, type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, AgentOrchestrator, type ApprovalEventItem, type ArchivedToolResult$1 as ArchivedToolResult, type BashConfig, BashEnvironmentManager, type BashExecutionLimits, type BuiltInToolToggles, CALLBACK_LOCK_STALE_MS, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type ContinuationHooks, type Conversation, type ConversationCreateInit, type ConversationState, type ConversationStatusSnapshot, type ConversationStore, type ConversationSummary, type CreateSkillToolsOptions, type CronJobConfig, DEFAULT_AGENT_DESCRIPTION, DEFAULT_AGENT_NAME, DEFAULT_MAX_STEPS, DEFAULT_MODEL_NAME, DEFAULT_MODEL_PROVIDER, DEFAULT_TEMPERATURE, DEFAULT_TIMEOUT, type DefaultAgentDefinitionOptions, type EventSink, type ExecuteTurnResult, type HarnessOptions, type HarnessRunOutput, type HistorySource, InMemoryConversationStore, InMemoryEngine, InMemoryStateStore, type IsolateBinding, type IsolateConfig, LocalMcpBridge, LocalUploadStore, MAX_CONCURRENT_SUBAGENTS, MAX_CONTINUATION_COUNT, MAX_SUBAGENT_CALLBACK_COUNT, MAX_SUBAGENT_NESTING, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, type NetworkConfig, OPENAI_CODEX_CLIENT_ID, type OpenAICodexAuthConfig, type OpenAICodexDeviceAuthRequest, type OpenAICodexSession, type OrchestratorHooks, type OrchestratorOptions, type OtlpConfig, type OtlpOption, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentApproval, type PendingSubagentResult, type PendingToolCall, type PonchoConfig, PonchoFsAdapter, PostgresEngine, type ProviderConfig, type Recurrence, type RecurrenceType, type Reminder, type ReminderCreateInput, type ReminderStatus, type ReminderStore, type RemoteMcpServerConfig, type RunConversationTurnOpts, type RunConversationTurnResult, type RunOutcome, type RunRequest, type RuntimeRenderContext, S3UploadStore, STALE_SUBAGENT_THRESHOLD_MS, STORAGE_SCHEMA_VERSION, type SecretsStore, type SkillContextEntry, type SkillMetadata, type SkillSource, SqliteEngine, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type StorageEngine, type StorageFactoryOptions, type StorageProvider, type StoredApproval, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, type SubagentTranscript, type SubagentTranscriptMode, TOOL_RESULT_ARCHIVE_PARAM, type TelemetryConfig, TelemetryEmitter, type TenantTokenPayload, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type TurnDraftState, type TurnResultMetadata, type TurnSection, type UploadStore, type UploadsConfig, VFS_SCHEME, VercelBlobUploadStore, type VfsDirEntry, type VfsStat, type VirtualMount, applyTurnMetadata, buildAgentDirectoryName, buildApprovalCheckpoints, buildAssistantMetadata, buildSkillContextWindow, buildToolCompletedText, cloneSections, compactMessages, completeOpenAICodexDeviceAuth, computeNextOccurrence, createBashTool, createConversationStore, createConversationStoreFromEngine, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryStoreFromEngine, createMemoryTools, createModelProvider, createReminderStore, createReminderStoreFromEngine, createReminderTools, createSearchTools, createSecretsStore, createSkillTools, createStateStore, createStorageEngine, createSubagentTools, createTodoStoreFromEngine, createTurnDraftState, createUploadStore, createWriteTool, decodeFileInputData, defaultAgentDefinition, deleteOpenAICodexSession, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, executeConversationTurn, findSafeSplitPoint, flushTurnDraft, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getOpenAICodexAccessToken, getOpenAICodexAuthFilePath, getOpenAICodexRequiredScopes, getPonchoStoreRoot, isMessageArray, jsonSchemaToZod, loadCanonicalHistory, loadPonchoConfig, loadRunHistory, loadSkillContext, loadSkillInstructions, loadSkillMetadata, loadSkillMetadataFromDirs, loadVfsSkillMetadata, mergeSkills, normalizeApprovalCheckpoint, normalizeOtlp, normalizeScriptPolicyPath, normalizeToolAccess, parseAgentFile, parseAgentMarkdown, parseSkillFrontmatter, ponchoDocsTool, readOpenAICodexSession, readSkillResource, recordStandardTurnEvent, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveEnv, resolveMemoryConfig, resolveRunRequest, resolveSkillDirs, resolveStateConfig, runConversationTurn, slugifyStorageComponent, startOpenAICodexDeviceAuth, verifyTenantToken, withToolResultArchiveParam, writeOpenAICodexSession };
|
package/dist/index.js
CHANGED
|
@@ -7363,7 +7363,9 @@ var collectSkillManifests = async (directory) => {
|
|
|
7363
7363
|
return files;
|
|
7364
7364
|
};
|
|
7365
7365
|
var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
7366
|
-
|
|
7366
|
+
return loadSkillMetadataFromDirs(resolveSkillDirs(workingDir, extraSkillPaths));
|
|
7367
|
+
};
|
|
7368
|
+
var loadSkillMetadataFromDirs = async (skillDirs) => {
|
|
7367
7369
|
const allManifests = [];
|
|
7368
7370
|
for (const dir of skillDirs) {
|
|
7369
7371
|
try {
|
|
@@ -9120,6 +9122,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
9120
9122
|
loadedConfig;
|
|
9121
9123
|
injectedConfig;
|
|
9122
9124
|
loadedSkills = [];
|
|
9125
|
+
systemSkills = [];
|
|
9126
|
+
systemSkillPaths = [];
|
|
9123
9127
|
skillFingerprint = "";
|
|
9124
9128
|
lastSkillRefreshAt = 0;
|
|
9125
9129
|
activeSkillNames = /* @__PURE__ */ new Set();
|
|
@@ -9325,6 +9329,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9325
9329
|
this.injectedStorageEngine = true;
|
|
9326
9330
|
}
|
|
9327
9331
|
this.virtualMounts = options.virtualMounts ?? [];
|
|
9332
|
+
this.systemSkillPaths = options.systemSkillPaths ?? [];
|
|
9328
9333
|
if (options.toolDefinitions?.length) {
|
|
9329
9334
|
this.dispatcher.registerMany(options.toolDefinitions);
|
|
9330
9335
|
}
|
|
@@ -9486,14 +9491,19 @@ var AgentHarness = class _AgentHarness {
|
|
|
9486
9491
|
return [...this.activeSkillNames].sort();
|
|
9487
9492
|
}
|
|
9488
9493
|
/**
|
|
9489
|
-
* Resolve the skill set visible to a given tenant
|
|
9490
|
-
*
|
|
9491
|
-
*
|
|
9492
|
-
*
|
|
9494
|
+
* Resolve the skill set visible to a given tenant. Three tiers, by
|
|
9495
|
+
* precedence: repo skills > the tenant's own VFS skills > platform
|
|
9496
|
+
* system skills. So a repo skill wins over a same-named VFS skill
|
|
9497
|
+
* (unchanged), and a tenant's `/skills/<name>/` overrides a same-named
|
|
9498
|
+
* system skill (the deploy-shipped default). Cached per tenant; cache
|
|
9499
|
+
* invalidates on VFS writes under /skills/ via invalidateSkillsForTenant.
|
|
9500
|
+
* System skills are static within a process, so they don't participate
|
|
9501
|
+
* in the fingerprint — but a VFS override does (it changes a /skills
|
|
9502
|
+
* path), which recomputes the cache and lets the override take effect.
|
|
9493
9503
|
*/
|
|
9494
9504
|
async getSkillsForTenant(tenantId) {
|
|
9495
9505
|
if (!this.storageEngine) {
|
|
9496
|
-
return this.loadedSkills;
|
|
9506
|
+
return mergeSkills(this.loadedSkills, this.systemSkills);
|
|
9497
9507
|
}
|
|
9498
9508
|
const effectiveTenant = tenantId || "__default__";
|
|
9499
9509
|
const engineWithRefresh = this.storageEngine;
|
|
@@ -9506,7 +9516,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9506
9516
|
return cached.skills;
|
|
9507
9517
|
}
|
|
9508
9518
|
const vfsSkills = await loadVfsSkillMetadata(this.storageEngine, effectiveTenant);
|
|
9509
|
-
const
|
|
9519
|
+
const repoAndVfs = mergeSkills(this.loadedSkills, vfsSkills, (skipped) => {
|
|
9510
9520
|
const key = `${effectiveTenant}:${skipped.name}`;
|
|
9511
9521
|
if (this.vfsSkillCollisionWarnings.has(key)) return;
|
|
9512
9522
|
this.vfsSkillCollisionWarnings.add(key);
|
|
@@ -9514,6 +9524,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9514
9524
|
`VFS skill "${skipped.name}" for tenant ${effectiveTenant} ignored: a repo skill with the same name takes precedence.`
|
|
9515
9525
|
);
|
|
9516
9526
|
});
|
|
9527
|
+
const merged = mergeSkills(repoAndVfs, this.systemSkills);
|
|
9517
9528
|
this.skillCache.set(effectiveTenant, { skills: merged, fingerprint });
|
|
9518
9529
|
return merged;
|
|
9519
9530
|
}
|
|
@@ -9844,6 +9855,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
9844
9855
|
const extraSkillPaths = config?.skillPaths;
|
|
9845
9856
|
const skillMetadata = await loadSkillMetadata(this.workingDir, extraSkillPaths);
|
|
9846
9857
|
this.loadedSkills = skillMetadata;
|
|
9858
|
+
this.systemSkills = this.systemSkillPaths.length ? await loadSkillMetadataFromDirs(this.systemSkillPaths) : [];
|
|
9847
9859
|
this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
|
|
9848
9860
|
this.registerSkillTools();
|
|
9849
9861
|
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
@@ -13980,6 +13992,7 @@ export {
|
|
|
13980
13992
|
loadSkillContext,
|
|
13981
13993
|
loadSkillInstructions,
|
|
13982
13994
|
loadSkillMetadata,
|
|
13995
|
+
loadSkillMetadataFromDirs,
|
|
13983
13996
|
loadVfsSkillMetadata,
|
|
13984
13997
|
mergeSkills,
|
|
13985
13998
|
normalizeApprovalCheckpoint,
|
package/package.json
CHANGED
package/src/harness.ts
CHANGED
|
@@ -55,6 +55,7 @@ import { createModelProvider, getModelContextWindow, type ModelProviderFactory,
|
|
|
55
55
|
import {
|
|
56
56
|
buildSkillContextWindow,
|
|
57
57
|
loadSkillMetadata,
|
|
58
|
+
loadSkillMetadataFromDirs,
|
|
58
59
|
loadVfsSkillMetadata,
|
|
59
60
|
mergeSkills,
|
|
60
61
|
} from "./skill-context.js";
|
|
@@ -134,6 +135,17 @@ export interface HarnessOptions {
|
|
|
134
135
|
* Empty by default — no system mounts in the CLI / dev workflow.
|
|
135
136
|
*/
|
|
136
137
|
virtualMounts?: VirtualMount[];
|
|
138
|
+
/**
|
|
139
|
+
* Absolute directories of platform-shipped "system" skills. Each is
|
|
140
|
+
* scanned for `<name>/SKILL.md` at init; the bodies live on local disk
|
|
141
|
+
* and ship with the deploy. System skills are surfaced in
|
|
142
|
+
* `<available_skills>` like any other skill, but sit at the LOWEST
|
|
143
|
+
* precedence: a tenant's own `/skills/<same-name>/` (and a repo skill)
|
|
144
|
+
* overrides a system skill of the same name. Pair with a read-only
|
|
145
|
+
* `virtualMounts` entry (e.g. "/system/skills/") if the same files
|
|
146
|
+
* should also be browsable in the VFS. Empty by default.
|
|
147
|
+
*/
|
|
148
|
+
systemSkillPaths?: string[];
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
export interface HarnessRunOutput {
|
|
@@ -839,6 +851,8 @@ export class AgentHarness {
|
|
|
839
851
|
private loadedConfig?: PonchoConfig;
|
|
840
852
|
private readonly injectedConfig?: PonchoConfig;
|
|
841
853
|
private loadedSkills: SkillMetadata[] = [];
|
|
854
|
+
private systemSkills: SkillMetadata[] = [];
|
|
855
|
+
private readonly systemSkillPaths: string[] = [];
|
|
842
856
|
private skillFingerprint = "";
|
|
843
857
|
private lastSkillRefreshAt = 0;
|
|
844
858
|
private readonly activeSkillNames = new Set<string>();
|
|
@@ -1077,6 +1091,7 @@ export class AgentHarness {
|
|
|
1077
1091
|
this.injectedStorageEngine = true;
|
|
1078
1092
|
}
|
|
1079
1093
|
this.virtualMounts = options.virtualMounts ?? [];
|
|
1094
|
+
this.systemSkillPaths = options.systemSkillPaths ?? [];
|
|
1080
1095
|
|
|
1081
1096
|
if (options.toolDefinitions?.length) {
|
|
1082
1097
|
this.dispatcher.registerMany(options.toolDefinitions);
|
|
@@ -1271,14 +1286,19 @@ export class AgentHarness {
|
|
|
1271
1286
|
}
|
|
1272
1287
|
|
|
1273
1288
|
/**
|
|
1274
|
-
* Resolve the skill set visible to a given tenant
|
|
1275
|
-
*
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1289
|
+
* Resolve the skill set visible to a given tenant. Three tiers, by
|
|
1290
|
+
* precedence: repo skills > the tenant's own VFS skills > platform
|
|
1291
|
+
* system skills. So a repo skill wins over a same-named VFS skill
|
|
1292
|
+
* (unchanged), and a tenant's `/skills/<name>/` overrides a same-named
|
|
1293
|
+
* system skill (the deploy-shipped default). Cached per tenant; cache
|
|
1294
|
+
* invalidates on VFS writes under /skills/ via invalidateSkillsForTenant.
|
|
1295
|
+
* System skills are static within a process, so they don't participate
|
|
1296
|
+
* in the fingerprint — but a VFS override does (it changes a /skills
|
|
1297
|
+
* path), which recomputes the cache and lets the override take effect.
|
|
1278
1298
|
*/
|
|
1279
1299
|
private async getSkillsForTenant(tenantId: string | undefined | null): Promise<SkillMetadata[]> {
|
|
1280
1300
|
if (!this.storageEngine) {
|
|
1281
|
-
return this.loadedSkills;
|
|
1301
|
+
return mergeSkills(this.loadedSkills, this.systemSkills);
|
|
1282
1302
|
}
|
|
1283
1303
|
// Mirror the rest of the harness: undefined tenantId falls back to
|
|
1284
1304
|
// "__default__" so dev-mode (no auth) conversations see the same VFS
|
|
@@ -1305,7 +1325,7 @@ export class AgentHarness {
|
|
|
1305
1325
|
return cached.skills;
|
|
1306
1326
|
}
|
|
1307
1327
|
const vfsSkills = await loadVfsSkillMetadata(this.storageEngine, effectiveTenant);
|
|
1308
|
-
const
|
|
1328
|
+
const repoAndVfs = mergeSkills(this.loadedSkills, vfsSkills, (skipped) => {
|
|
1309
1329
|
const key = `${effectiveTenant}:${skipped.name}`;
|
|
1310
1330
|
if (this.vfsSkillCollisionWarnings.has(key)) return;
|
|
1311
1331
|
this.vfsSkillCollisionWarnings.add(key);
|
|
@@ -1313,6 +1333,11 @@ export class AgentHarness {
|
|
|
1313
1333
|
`VFS skill "${skipped.name}" for tenant ${effectiveTenant} ignored: a repo skill with the same name takes precedence.`,
|
|
1314
1334
|
);
|
|
1315
1335
|
});
|
|
1336
|
+
// System skills sit at the bottom: a repo or VFS skill of the same
|
|
1337
|
+
// name overrides them. Overriding a system default is the intended
|
|
1338
|
+
// user workflow (mirrors /jobs system-default overrides), so the
|
|
1339
|
+
// collision is silent — not a warning.
|
|
1340
|
+
const merged = mergeSkills(repoAndVfs, this.systemSkills);
|
|
1316
1341
|
this.skillCache.set(effectiveTenant, { skills: merged, fingerprint });
|
|
1317
1342
|
return merged;
|
|
1318
1343
|
}
|
|
@@ -1706,6 +1731,13 @@ export class AgentHarness {
|
|
|
1706
1731
|
const extraSkillPaths = config?.skillPaths;
|
|
1707
1732
|
const skillMetadata = await loadSkillMetadata(this.workingDir, extraSkillPaths);
|
|
1708
1733
|
this.loadedSkills = skillMetadata;
|
|
1734
|
+
// Platform-shipped system skills, scanned from absolute dirs on disk.
|
|
1735
|
+
// Loaded once at init (they ship with the deploy and don't change
|
|
1736
|
+
// within a process). Merged at LOWEST precedence per tenant — see
|
|
1737
|
+
// getSkillsForTenant.
|
|
1738
|
+
this.systemSkills = this.systemSkillPaths.length
|
|
1739
|
+
? await loadSkillMetadataFromDirs(this.systemSkillPaths)
|
|
1740
|
+
: [];
|
|
1709
1741
|
this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
|
|
1710
1742
|
this.registerSkillTools();
|
|
1711
1743
|
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
package/src/skill-context.ts
CHANGED
|
@@ -209,7 +209,17 @@ export const loadSkillMetadata = async (
|
|
|
209
209
|
workingDir: string,
|
|
210
210
|
extraSkillPaths?: string[],
|
|
211
211
|
): Promise<SkillMetadata[]> => {
|
|
212
|
-
|
|
212
|
+
return loadSkillMetadataFromDirs(resolveSkillDirs(workingDir, extraSkillPaths));
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Scan an explicit list of absolute directories for `<name>/SKILL.md`
|
|
216
|
+
// manifests and return their metadata as `source: "repo"` skills (body
|
|
217
|
+
// read from disk on activation). Used both by `loadSkillMetadata` (after
|
|
218
|
+
// resolving repo skill dirs against the working dir) and directly for
|
|
219
|
+
// platform-shipped "system" skills whose source dirs are already absolute.
|
|
220
|
+
export const loadSkillMetadataFromDirs = async (
|
|
221
|
+
skillDirs: string[],
|
|
222
|
+
): Promise<SkillMetadata[]> => {
|
|
213
223
|
const allManifests: string[] = [];
|
|
214
224
|
|
|
215
225
|
for (const dir of skillDirs) {
|