@ai-sdk/harness-acp 1.0.28 → 1.0.29
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 +7 -0
- package/README.md +5 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +44 -53
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/acp-harness.ts +2 -0
- package/src/v1/acp-v1-harness.ts +23 -19
- package/src/v1/acp-v1-lifecycle.ts +8 -1
- package/src/v1/acp-v1-prompt.ts +2 -22
- package/src/v1/acp-v1-settings.ts +5 -0
- package/src/v1/acp-v1-skills.ts +38 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-acp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.91",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.32",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
package/src/acp-harness.ts
CHANGED
|
@@ -50,6 +50,7 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
|
50
50
|
readonly authentication?: ACPV1Settings['authentication'];
|
|
51
51
|
readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
|
|
52
52
|
readonly modelId?: ACPV1Settings['modelId'];
|
|
53
|
+
readonly skillsDirectory?: ACPV1Settings['skillsDirectory'];
|
|
53
54
|
readonly instructionMapping?: ACPV1Settings['instructionMapping'];
|
|
54
55
|
readonly outputSchemaMapping?: ACPV1Settings['outputSchemaMapping'];
|
|
55
56
|
readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
|
|
@@ -104,6 +105,7 @@ const acpResumeStateSchema = z.object({
|
|
|
104
105
|
initialGuidanceApplied: z.boolean().optional(),
|
|
105
106
|
skillsMaterialized: z.boolean().optional(),
|
|
106
107
|
skillsFingerprint: z.string().optional(),
|
|
108
|
+
skillsDirectory: z.string().optional(),
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
export function createACP<TBuiltinTools extends ToolSet = {}>(
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -76,7 +76,6 @@ import {
|
|
|
76
76
|
import {
|
|
77
77
|
convertHarnessPromptToACPTextBlocks,
|
|
78
78
|
prependACPInitialGuidance,
|
|
79
|
-
type ACPSkillCatalogEntry,
|
|
80
79
|
} from './acp-v1-prompt';
|
|
81
80
|
import type {
|
|
82
81
|
ACPInstructionMapping,
|
|
@@ -91,6 +90,7 @@ import {
|
|
|
91
90
|
createACPSkillsFingerprint,
|
|
92
91
|
materializeACPSkills,
|
|
93
92
|
resolveACPPrivateSessionDirectory,
|
|
93
|
+
resolveACPSkillsDirectory,
|
|
94
94
|
} from './acp-v1-skills';
|
|
95
95
|
|
|
96
96
|
const HARNESS_ID_REGEXP = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -366,6 +366,14 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
366
366
|
harnessId: settings.harnessId,
|
|
367
367
|
sessionId: startOptions.sessionId,
|
|
368
368
|
});
|
|
369
|
+
const skillsDirectory = resolveACPSkillsDirectory({
|
|
370
|
+
implementationHomeDir:
|
|
371
|
+
implementation.source.type === 'install-command'
|
|
372
|
+
? `${resolvedImplementationDir}/home`
|
|
373
|
+
: sandboxHomeDir,
|
|
374
|
+
skillsDirectory: settings.skillsDirectory,
|
|
375
|
+
sessionWorkDir: workDir,
|
|
376
|
+
});
|
|
369
377
|
const bridgeStateDir = `${privateSessionDir}/bridge`;
|
|
370
378
|
const skills = startOptions.skills ?? [];
|
|
371
379
|
const skillsFingerprint = createACPSkillsFingerprint({ skills });
|
|
@@ -373,21 +381,17 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
373
381
|
isResume,
|
|
374
382
|
lifecycleState: lifecycleData,
|
|
375
383
|
skillsFingerprint,
|
|
384
|
+
skillsDirectory,
|
|
376
385
|
});
|
|
377
|
-
let skillCatalog: ReadonlyArray<ACPSkillCatalogEntry> = [];
|
|
378
386
|
if (skills.length > 0) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
shouldMaterialize: shouldMaterializeSkills,
|
|
388
|
-
abortSignal: startOptions.abortSignal,
|
|
389
|
-
})
|
|
390
|
-
).catalog;
|
|
387
|
+
await materializeACPSkills({
|
|
388
|
+
sandbox: toolSafeSandboxSession,
|
|
389
|
+
rootDir: skillsDirectory,
|
|
390
|
+
sessionWorkDir: workDir,
|
|
391
|
+
skills,
|
|
392
|
+
shouldMaterialize: shouldMaterializeSkills,
|
|
393
|
+
abortSignal: startOptions.abortSignal,
|
|
394
|
+
});
|
|
391
395
|
}
|
|
392
396
|
const report = startOptions.observability?.report;
|
|
393
397
|
const onDiagnostic = report
|
|
@@ -459,8 +463,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
459
463
|
isResume: true,
|
|
460
464
|
lifecycleState: lifecycleData,
|
|
461
465
|
}),
|
|
462
|
-
skillCatalog,
|
|
463
466
|
skillsFingerprint,
|
|
467
|
+
skillsDirectory,
|
|
464
468
|
acpSessionId: lifecycleData.acpSessionId,
|
|
465
469
|
bridgePort: coords.port,
|
|
466
470
|
bridgeToken: coords.token,
|
|
@@ -725,8 +729,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
725
729
|
isResume,
|
|
726
730
|
lifecycleState: lifecycleData,
|
|
727
731
|
}),
|
|
728
|
-
skillCatalog,
|
|
729
732
|
skillsFingerprint,
|
|
733
|
+
skillsDirectory,
|
|
730
734
|
acpSessionId: lifecycleData?.acpSessionId,
|
|
731
735
|
bridgePort: boundPort,
|
|
732
736
|
bridgeToken: token,
|
|
@@ -1044,8 +1048,8 @@ function createSession({
|
|
|
1044
1048
|
mcpServers,
|
|
1045
1049
|
isMcpToolCall,
|
|
1046
1050
|
initialGuidanceApplied: initialGuidanceAppliedAtStart,
|
|
1047
|
-
skillCatalog,
|
|
1048
1051
|
skillsFingerprint,
|
|
1052
|
+
skillsDirectory,
|
|
1049
1053
|
acpSessionId: acpSessionIdAtStart,
|
|
1050
1054
|
bridgePort,
|
|
1051
1055
|
bridgeToken,
|
|
@@ -1077,8 +1081,8 @@ function createSession({
|
|
|
1077
1081
|
mcpServers: Record<string, unknown> | undefined;
|
|
1078
1082
|
isMcpToolCall: ((toolCall: ACPToolCall) => boolean) | undefined;
|
|
1079
1083
|
initialGuidanceApplied: boolean;
|
|
1080
|
-
skillCatalog: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
1081
1084
|
skillsFingerprint: string;
|
|
1085
|
+
skillsDirectory: string;
|
|
1082
1086
|
acpSessionId: string | undefined;
|
|
1083
1087
|
bridgePort: number;
|
|
1084
1088
|
bridgeToken: string;
|
|
@@ -1366,6 +1370,7 @@ function createSession({
|
|
|
1366
1370
|
initialGuidanceApplied,
|
|
1367
1371
|
skillsMaterialized: true,
|
|
1368
1372
|
skillsFingerprint,
|
|
1373
|
+
skillsDirectory,
|
|
1369
1374
|
});
|
|
1370
1375
|
|
|
1371
1376
|
return {
|
|
@@ -1436,7 +1441,6 @@ function createSession({
|
|
|
1436
1441
|
instructionMapping == null
|
|
1437
1442
|
? options.instructions
|
|
1438
1443
|
: undefined,
|
|
1439
|
-
skills: skillCatalog,
|
|
1440
1444
|
}),
|
|
1441
1445
|
...(instructionMapping == null
|
|
1442
1446
|
? {}
|
|
@@ -8,6 +8,7 @@ export type ACPPromptGuidanceLifecycleState = {
|
|
|
8
8
|
readonly initialGuidanceApplied?: boolean;
|
|
9
9
|
readonly skillsMaterialized?: boolean;
|
|
10
10
|
readonly skillsFingerprint?: string;
|
|
11
|
+
readonly skillsDirectory?: string;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export type ACPBridgeCoordinates = {
|
|
@@ -50,10 +51,12 @@ export function shouldMaterializeACPSkills({
|
|
|
50
51
|
isResume,
|
|
51
52
|
lifecycleState,
|
|
52
53
|
skillsFingerprint,
|
|
54
|
+
skillsDirectory,
|
|
53
55
|
}: {
|
|
54
56
|
isResume: boolean;
|
|
55
57
|
lifecycleState: ACPPromptGuidanceLifecycleState | undefined;
|
|
56
58
|
skillsFingerprint: string;
|
|
59
|
+
skillsDirectory: string;
|
|
57
60
|
}): boolean {
|
|
58
61
|
if (
|
|
59
62
|
isResume &&
|
|
@@ -64,7 +67,11 @@ export function shouldMaterializeACPSkills({
|
|
|
64
67
|
'ACP lifecycle state was created with a different set of skills.',
|
|
65
68
|
);
|
|
66
69
|
}
|
|
67
|
-
return
|
|
70
|
+
return (
|
|
71
|
+
!isResume ||
|
|
72
|
+
lifecycleState?.skillsMaterialized !== true ||
|
|
73
|
+
lifecycleState.skillsDirectory !== skillsDirectory
|
|
74
|
+
);
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
export function validateACPLifecycleCompatibility({
|
package/src/v1/acp-v1-prompt.ts
CHANGED
|
@@ -11,12 +11,6 @@ export const acpTextContentBlockSchema = z.object({
|
|
|
11
11
|
|
|
12
12
|
export type ACPTextContentBlock = z.infer<typeof acpTextContentBlockSchema>;
|
|
13
13
|
|
|
14
|
-
export type ACPSkillCatalogEntry = {
|
|
15
|
-
readonly name: string;
|
|
16
|
-
readonly description: string;
|
|
17
|
-
readonly path: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
14
|
export function convertHarnessPromptToACPTextBlocks({
|
|
21
15
|
prompt,
|
|
22
16
|
harnessId,
|
|
@@ -75,32 +69,18 @@ export function convertHarnessPromptToACPTextBlocks({
|
|
|
75
69
|
export function prependACPInitialGuidance({
|
|
76
70
|
prompt,
|
|
77
71
|
instructions,
|
|
78
|
-
skills,
|
|
79
72
|
}: {
|
|
80
73
|
prompt: ReadonlyArray<ACPTextContentBlock>;
|
|
81
74
|
instructions: string | undefined;
|
|
82
|
-
skills: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
83
75
|
}): ACPTextContentBlock[] {
|
|
84
76
|
const hasInstructions = instructions != null && instructions.length > 0;
|
|
85
|
-
if (!hasInstructions
|
|
77
|
+
if (!hasInstructions) return [...prompt];
|
|
86
78
|
|
|
87
79
|
const lines = [
|
|
88
80
|
'<session-guidance>',
|
|
89
81
|
'This block is operating guidance from the harness, not user-authored content.',
|
|
90
82
|
];
|
|
91
|
-
|
|
92
|
-
lines.push('<instructions>', instructions, '</instructions>');
|
|
93
|
-
}
|
|
94
|
-
if (skills.length > 0) {
|
|
95
|
-
lines.push(
|
|
96
|
-
'<available-skills>',
|
|
97
|
-
'Load a skill only when relevant by reading its SKILL.md file at the listed absolute path.',
|
|
98
|
-
);
|
|
99
|
-
for (const skill of skills) {
|
|
100
|
-
lines.push(`- ${skill.name}: ${skill.description} (${skill.path})`);
|
|
101
|
-
}
|
|
102
|
-
lines.push('</available-skills>');
|
|
103
|
-
}
|
|
83
|
+
lines.push('<instructions>', instructions, '</instructions>');
|
|
104
84
|
lines.push('</session-guidance>');
|
|
105
85
|
|
|
106
86
|
return [{ type: 'text', text: lines.join('\n') }, ...prompt];
|
|
@@ -144,6 +144,11 @@ export type ACPV1Settings = {
|
|
|
144
144
|
readonly authentication?: ACPAuthentication;
|
|
145
145
|
readonly providerAuthentication?: ACPProviderAuthentication;
|
|
146
146
|
readonly modelId?: string;
|
|
147
|
+
/**
|
|
148
|
+
* Native skills directory relative to the ACP implementation's home
|
|
149
|
+
* directory. Defaults to `.agents/skills`.
|
|
150
|
+
*/
|
|
151
|
+
readonly skillsDirectory?: string;
|
|
147
152
|
/**
|
|
148
153
|
* Routes HarnessAgent instructions to a runtime-native system or developer
|
|
149
154
|
* prompt. When omitted, instructions are prepended to the first user prompt.
|
package/src/v1/acp-v1-skills.ts
CHANGED
|
@@ -3,9 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import type { HarnessV1Skill } from '@ai-sdk/harness';
|
|
4
4
|
import { writeSkills } from '@ai-sdk/harness/utils';
|
|
5
5
|
import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
|
|
6
|
-
import type { ACPSkillCatalogEntry } from './acp-v1-prompt';
|
|
7
6
|
|
|
8
7
|
const ACP_SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8
|
+
export const DEFAULT_ACP_SKILLS_DIRECTORY = '.agents/skills';
|
|
9
9
|
|
|
10
10
|
export function createACPSkillsFingerprint({
|
|
11
11
|
skills,
|
|
@@ -59,37 +59,21 @@ export function resolveACPPrivateSessionDirectory({
|
|
|
59
59
|
|
|
60
60
|
export async function materializeACPSkills({
|
|
61
61
|
sandbox,
|
|
62
|
-
|
|
62
|
+
rootDir,
|
|
63
63
|
sessionWorkDir,
|
|
64
|
-
harnessId,
|
|
65
|
-
sessionId,
|
|
66
64
|
skills,
|
|
67
65
|
shouldMaterialize,
|
|
68
66
|
abortSignal,
|
|
69
67
|
}: {
|
|
70
68
|
sandbox: Experimental_SandboxSession;
|
|
71
|
-
|
|
69
|
+
rootDir: string;
|
|
72
70
|
sessionWorkDir: string;
|
|
73
|
-
harnessId: string;
|
|
74
|
-
sessionId: string;
|
|
75
71
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
76
72
|
shouldMaterialize: boolean;
|
|
77
73
|
abortSignal?: AbortSignal;
|
|
78
|
-
}): Promise<{
|
|
79
|
-
readonly catalog: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
80
|
-
readonly rootDir: string;
|
|
81
|
-
}> {
|
|
74
|
+
}): Promise<void> {
|
|
82
75
|
validateACPSkills({ skills });
|
|
83
|
-
|
|
84
|
-
const rootDir = path.posix.join(
|
|
85
|
-
resolveACPPrivateSessionDirectory({
|
|
86
|
-
sandboxHomeDir,
|
|
87
|
-
sessionWorkDir,
|
|
88
|
-
harnessId,
|
|
89
|
-
sessionId,
|
|
90
|
-
}),
|
|
91
|
-
'skills',
|
|
92
|
-
);
|
|
76
|
+
assertOutsideSessionWorkDir({ rootDir, sessionWorkDir });
|
|
93
77
|
|
|
94
78
|
if (shouldMaterialize) {
|
|
95
79
|
await writeSkills({
|
|
@@ -106,15 +90,40 @@ export async function materializeACPSkills({
|
|
|
106
90
|
)}: expected a relative POSIX path without traversal.`,
|
|
107
91
|
});
|
|
108
92
|
}
|
|
93
|
+
}
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
95
|
+
export function resolveACPSkillsDirectory({
|
|
96
|
+
implementationHomeDir,
|
|
97
|
+
skillsDirectory = DEFAULT_ACP_SKILLS_DIRECTORY,
|
|
98
|
+
sessionWorkDir,
|
|
99
|
+
}: {
|
|
100
|
+
implementationHomeDir: string;
|
|
101
|
+
skillsDirectory?: string;
|
|
102
|
+
sessionWorkDir: string;
|
|
103
|
+
}): string {
|
|
104
|
+
const containsTraversal = skillsDirectory
|
|
105
|
+
.split(/[\\/]/)
|
|
106
|
+
.some(segment => segment === '..');
|
|
107
|
+
const normalizedDirectory = path.posix.normalize(skillsDirectory);
|
|
108
|
+
if (
|
|
109
|
+
skillsDirectory.length === 0 ||
|
|
110
|
+
skillsDirectory.includes('\\') ||
|
|
111
|
+
path.posix.isAbsolute(skillsDirectory) ||
|
|
112
|
+
path.win32.isAbsolute(skillsDirectory) ||
|
|
113
|
+
containsTraversal ||
|
|
114
|
+
normalizedDirectory === '.' ||
|
|
115
|
+
normalizedDirectory.startsWith('../') ||
|
|
116
|
+
normalizedDirectory.includes('/../') ||
|
|
117
|
+
normalizedDirectory.endsWith('/..')
|
|
118
|
+
) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`ACP skillsDirectory ${JSON.stringify(skillsDirectory)} must be a relative POSIX path without traversal.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const rootDir = path.posix.join(implementationHomeDir, normalizedDirectory);
|
|
125
|
+
assertOutsideSessionWorkDir({ rootDir, sessionWorkDir });
|
|
126
|
+
return rootDir;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
function validateACPSkills({
|