@tangle-network/agent-app 0.60.181 → 0.60.183
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/index.js +12 -12
- package/dist/chat-routes/index.js +23 -23
- package/dist/chat-store/index.js +4 -4
- package/dist/{chunk-6ZUVYAKB.js → chunk-6O2NQ7WD.js} +9 -9
- package/dist/{chunk-IIE3K2Y5.js → chunk-EOPFHXEA.js} +1 -1
- package/dist/{chunk-PYPWEXTD.js → chunk-J26IXWFK.js} +12 -12
- package/dist/chunk-LTWJYNEC.js +100 -0
- package/dist/chunk-LTWJYNEC.js.map +1 -0
- package/dist/{chunk-YR6TZ777.js → chunk-TNWDYAFX.js} +6 -6
- package/dist/{chunk-T5PPQPOD.js → chunk-TXUAGSFR.js} +1 -1
- package/dist/{chunk-T5PPQPOD.js.map → chunk-TXUAGSFR.js.map} +1 -1
- package/dist/{chunk-XNKLQG26.js → chunk-ZGUXI5SI.js} +5 -5
- package/dist/design-canvas-react/index.js +4 -4
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/model-resolution/allow-list.d.ts +64 -0
- package/dist/model-resolution/index.d.ts +1 -0
- package/dist/model-resolution/index.js +28 -0
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/profile/index.js +2 -1
- package/dist/profile/index.js.map +1 -1
- package/dist/public-consultation/index.d.ts +1 -1
- package/dist/public-consultation/index.js +8 -8
- package/dist/skills/index.d.ts +1 -0
- package/dist/skills/index.js +26 -2
- package/dist/skills/marketplace-drizzle.d.ts +121 -0
- package/dist/skills/marketplace-drizzle.js +34 -0
- package/dist/skills/marketplace-drizzle.js.map +1 -0
- package/dist/skills/marketplace.d.ts +104 -0
- package/dist/skills-placement/index.js +2 -1
- package/dist/skills-placement/index.js.map +1 -1
- package/dist/spend/delegate-budget.d.ts +59 -0
- package/dist/spend/index.d.ts +1 -0
- package/dist/spend/index.js +43 -0
- package/dist/spend/index.js.map +1 -1
- package/dist/stream/index.js +2 -2
- package/dist/toolbelt/index.d.ts +80 -0
- package/dist/toolbelt/index.js +178 -0
- package/dist/toolbelt/index.js.map +1 -0
- package/dist/web-react/index.js +25 -25
- package/package.json +11 -1
- /package/dist/{chunk-6ZUVYAKB.js.map → chunk-6O2NQ7WD.js.map} +0 -0
- /package/dist/{chunk-IIE3K2Y5.js.map → chunk-EOPFHXEA.js.map} +0 -0
- /package/dist/{chunk-PYPWEXTD.js.map → chunk-J26IXWFK.js.map} +0 -0
- /package/dist/{chunk-YR6TZ777.js.map → chunk-TNWDYAFX.js.map} +0 -0
- /package/dist/{chunk-XNKLQG26.js.map → chunk-ZGUXI5SI.js.map} +0 -0
package/dist/assistant/index.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ReasoningRow,
|
|
3
3
|
chatToolCallPart
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-6O2NQ7WD.js";
|
|
5
5
|
import "../chunk-EQRVOMMS.js";
|
|
6
6
|
import "../chunk-3HUFJ5LO.js";
|
|
7
7
|
import "../chunk-FBVLEGEG.js";
|
|
8
|
-
import "../chunk-UDLA3NGY.js";
|
|
9
|
-
import "../chunk-A2GG4D4A.js";
|
|
10
|
-
import "../chunk-E4AY62T2.js";
|
|
11
|
-
import "../chunk-3RHAF2P5.js";
|
|
12
8
|
import "../chunk-Y6PRDUFJ.js";
|
|
13
|
-
import "../chunk-GEYACSFW.js";
|
|
14
9
|
import {
|
|
15
10
|
ChatComposer
|
|
16
11
|
} from "../chunk-BBHVV37T.js";
|
|
17
12
|
import "../chunk-L7MB2LLM.js";
|
|
13
|
+
import "../chunk-UDLA3NGY.js";
|
|
14
|
+
import "../chunk-A2GG4D4A.js";
|
|
15
|
+
import "../chunk-E4AY62T2.js";
|
|
18
16
|
import "../chunk-SJWIZT7B.js";
|
|
17
|
+
import "../chunk-3RHAF2P5.js";
|
|
19
18
|
import "../chunk-YESFGM56.js";
|
|
20
19
|
import "../chunk-YGBEGBJY.js";
|
|
21
20
|
import {
|
|
22
21
|
ModelPicker
|
|
23
22
|
} from "../chunk-TJ4DYKF2.js";
|
|
23
|
+
import "../chunk-GEYACSFW.js";
|
|
24
|
+
import "../chunk-ATQQCG4I.js";
|
|
25
|
+
import "../chunk-WSEXFTN4.js";
|
|
24
26
|
import "../chunk-RASGKEAL.js";
|
|
25
27
|
import {
|
|
26
28
|
AsyncView
|
|
27
29
|
} from "../chunk-LN5RBVGP.js";
|
|
28
|
-
import "../chunk-ATQQCG4I.js";
|
|
29
|
-
import "../chunk-WSEXFTN4.js";
|
|
30
30
|
import "../chunk-FQYAJYPW.js";
|
|
31
31
|
import "../chunk-WTC6AOPL.js";
|
|
32
32
|
import "../chunk-VBRUG4N5.js";
|
|
33
33
|
import "../chunk-B7CL7ENS.js";
|
|
34
34
|
import "../chunk-PV33JZKV.js";
|
|
35
35
|
import "../chunk-AMUXRAUO.js";
|
|
36
|
-
import "../chunk-
|
|
37
|
-
import "../chunk-ZVEEWGDK.js";
|
|
36
|
+
import "../chunk-TNWDYAFX.js";
|
|
38
37
|
import "../chunk-YHRWZZJN.js";
|
|
38
|
+
import "../chunk-ZVEEWGDK.js";
|
|
39
39
|
import "../chunk-TXD5HXLE.js";
|
|
40
|
-
import "../chunk-
|
|
41
|
-
import "../chunk-LQYIBL3G.js";
|
|
40
|
+
import "../chunk-ZGUXI5SI.js";
|
|
42
41
|
import "../chunk-6VQCFJWP.js";
|
|
42
|
+
import "../chunk-LQYIBL3G.js";
|
|
43
43
|
|
|
44
44
|
// src/assistant/sse.ts
|
|
45
45
|
var SSEChunkParser = class {
|
|
@@ -30,6 +30,12 @@ import {
|
|
|
30
30
|
DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
|
|
31
31
|
reconcileStaleTurnLock
|
|
32
32
|
} from "../chunk-CCVWR33L.js";
|
|
33
|
+
import {
|
|
34
|
+
flattenHistory,
|
|
35
|
+
readSandboxBinaryBytes,
|
|
36
|
+
statSandboxFileSize
|
|
37
|
+
} from "../chunk-QW4UOEPH.js";
|
|
38
|
+
import "../chunk-4LBCTFBP.js";
|
|
33
39
|
import {
|
|
34
40
|
ModelFailoverExhaustedError,
|
|
35
41
|
buildModelChain,
|
|
@@ -38,12 +44,6 @@ import {
|
|
|
38
44
|
readUpstreamUnavailableCode,
|
|
39
45
|
runWithModelFailover
|
|
40
46
|
} from "../chunk-63EKFZOE.js";
|
|
41
|
-
import {
|
|
42
|
-
flattenHistory,
|
|
43
|
-
readSandboxBinaryBytes,
|
|
44
|
-
statSandboxFileSize
|
|
45
|
-
} from "../chunk-QW4UOEPH.js";
|
|
46
|
-
import "../chunk-4LBCTFBP.js";
|
|
47
47
|
import "../chunk-VBRUG4N5.js";
|
|
48
48
|
import "../chunk-LWSJK546.js";
|
|
49
49
|
import "../chunk-75ZIWV4U.js";
|
|
@@ -72,17 +72,22 @@ import {
|
|
|
72
72
|
rowIdOf,
|
|
73
73
|
storeSupportsDraftPersistence,
|
|
74
74
|
streamChatRouteAsSandboxEvents
|
|
75
|
-
} from "../chunk-
|
|
75
|
+
} from "../chunk-J26IXWFK.js";
|
|
76
|
+
import "../chunk-Q7WEEN7P.js";
|
|
76
77
|
import "../chunk-UTAIRVOK.js";
|
|
77
78
|
import "../chunk-RN4WPTBH.js";
|
|
78
|
-
import "../chunk-Q7WEEN7P.js";
|
|
79
79
|
import "../chunk-3NCPXNBZ.js";
|
|
80
|
+
import "../chunk-Z4RD5UF7.js";
|
|
81
|
+
import "../chunk-YG66IHDU.js";
|
|
82
|
+
import {
|
|
83
|
+
coalesceDeltas,
|
|
84
|
+
createBufferedTurnTap
|
|
85
|
+
} from "../chunk-ISP6ZH3G.js";
|
|
80
86
|
import {
|
|
81
87
|
attachmentInputToPart,
|
|
82
88
|
attachmentKindForMime,
|
|
83
89
|
toChatMessageParts
|
|
84
|
-
} from "../chunk-
|
|
85
|
-
import "../chunk-ZVEEWGDK.js";
|
|
90
|
+
} from "../chunk-TNWDYAFX.js";
|
|
86
91
|
import {
|
|
87
92
|
ChatTurnInputError,
|
|
88
93
|
DISPATCH_MAX_MEDIA_PARTS,
|
|
@@ -116,15 +121,10 @@ import {
|
|
|
116
121
|
turnResponseEventFor,
|
|
117
122
|
validateSandboxMentionPath
|
|
118
123
|
} from "../chunk-YHRWZZJN.js";
|
|
124
|
+
import "../chunk-ZVEEWGDK.js";
|
|
119
125
|
import {
|
|
120
126
|
isWorkspaceFileExportable
|
|
121
127
|
} from "../chunk-TXD5HXLE.js";
|
|
122
|
-
import "../chunk-Z4RD5UF7.js";
|
|
123
|
-
import "../chunk-YG66IHDU.js";
|
|
124
|
-
import {
|
|
125
|
-
coalesceDeltas,
|
|
126
|
-
createBufferedTurnTap
|
|
127
|
-
} from "../chunk-ISP6ZH3G.js";
|
|
128
128
|
import {
|
|
129
129
|
asRecord,
|
|
130
130
|
asString,
|
|
@@ -137,13 +137,7 @@ import {
|
|
|
137
137
|
normalizePersistedPart,
|
|
138
138
|
normalizeToolEvent,
|
|
139
139
|
terminalizeDanglingAssistantToolUpdates
|
|
140
|
-
} from "../chunk-
|
|
141
|
-
import {
|
|
142
|
-
parsePlanSubmittedEvent,
|
|
143
|
-
persistedPartToPlan,
|
|
144
|
-
planFollowUpTurnId,
|
|
145
|
-
planToPersistedPart
|
|
146
|
-
} from "../chunk-LQYIBL3G.js";
|
|
140
|
+
} from "../chunk-ZGUXI5SI.js";
|
|
147
141
|
import {
|
|
148
142
|
cancelStatusFor,
|
|
149
143
|
interactionPartKey,
|
|
@@ -154,6 +148,12 @@ import {
|
|
|
154
148
|
parseInteractionCancel,
|
|
155
149
|
parseInteractionRequest
|
|
156
150
|
} from "../chunk-6VQCFJWP.js";
|
|
151
|
+
import {
|
|
152
|
+
parsePlanSubmittedEvent,
|
|
153
|
+
persistedPartToPlan,
|
|
154
|
+
planFollowUpTurnId,
|
|
155
|
+
planToPersistedPart
|
|
156
|
+
} from "../chunk-LQYIBL3G.js";
|
|
157
157
|
|
|
158
158
|
// src/chat-routes/model-failover-stream.ts
|
|
159
159
|
var TERMINAL_FAILURE_TYPES = /* @__PURE__ */ new Set(["error", "session.run.failed"]);
|
package/dist/chat-store/index.js
CHANGED
|
@@ -14,15 +14,15 @@ import {
|
|
|
14
14
|
mentionInputToPart,
|
|
15
15
|
mentionPartsFromMessageParts,
|
|
16
16
|
toChatMessageParts
|
|
17
|
-
} from "../chunk-
|
|
18
|
-
import "../chunk-ZVEEWGDK.js";
|
|
17
|
+
} from "../chunk-TNWDYAFX.js";
|
|
19
18
|
import "../chunk-YHRWZZJN.js";
|
|
19
|
+
import "../chunk-ZVEEWGDK.js";
|
|
20
20
|
import "../chunk-TXD5HXLE.js";
|
|
21
21
|
import {
|
|
22
22
|
attachmentPartKey
|
|
23
|
-
} from "../chunk-
|
|
24
|
-
import "../chunk-LQYIBL3G.js";
|
|
23
|
+
} from "../chunk-ZGUXI5SI.js";
|
|
25
24
|
import "../chunk-6VQCFJWP.js";
|
|
25
|
+
import "../chunk-LQYIBL3G.js";
|
|
26
26
|
|
|
27
27
|
// src/chat-store/core.ts
|
|
28
28
|
var BULK_DELETE_MAX_THREADS = 200;
|
|
@@ -29,15 +29,15 @@ import {
|
|
|
29
29
|
usePending,
|
|
30
30
|
usePopover
|
|
31
31
|
} from "./chunk-TJ4DYKF2.js";
|
|
32
|
-
import {
|
|
33
|
-
AsyncView
|
|
34
|
-
} from "./chunk-LN5RBVGP.js";
|
|
35
32
|
import {
|
|
36
33
|
ReviewFeedbackForm
|
|
37
34
|
} from "./chunk-ATQQCG4I.js";
|
|
35
|
+
import {
|
|
36
|
+
AsyncView
|
|
37
|
+
} from "./chunk-LN5RBVGP.js";
|
|
38
38
|
import {
|
|
39
39
|
attachmentPartsFromMessageParts
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-TNWDYAFX.js";
|
|
41
41
|
import {
|
|
42
42
|
TURN_INTENT_EVENT,
|
|
43
43
|
TURN_PHASE_EVENT,
|
|
@@ -45,10 +45,6 @@ import {
|
|
|
45
45
|
isTurnPhaseEvent,
|
|
46
46
|
settleTurnResponse
|
|
47
47
|
} from "./chunk-YHRWZZJN.js";
|
|
48
|
-
import {
|
|
49
|
-
parsePlanSubmittedEvent,
|
|
50
|
-
persistedPartToPlan
|
|
51
|
-
} from "./chunk-LQYIBL3G.js";
|
|
52
48
|
import {
|
|
53
49
|
cancelStatusFor,
|
|
54
50
|
fieldAcceptsFreeText,
|
|
@@ -59,6 +55,10 @@ import {
|
|
|
59
55
|
persistedPartToInteraction,
|
|
60
56
|
questionInteractionContentSignature
|
|
61
57
|
} from "./chunk-6VQCFJWP.js";
|
|
58
|
+
import {
|
|
59
|
+
parsePlanSubmittedEvent,
|
|
60
|
+
persistedPartToPlan
|
|
61
|
+
} from "./chunk-LQYIBL3G.js";
|
|
62
62
|
|
|
63
63
|
// src/web-react/index.tsx
|
|
64
64
|
import { useEffect as useEffect16, useMemo as useMemo8, useRef as useRef15, useState as useState22, memo } from "react";
|
|
@@ -7207,4 +7207,4 @@ export {
|
|
|
7207
7207
|
useThinkingSeconds,
|
|
7208
7208
|
ChatMessages
|
|
7209
7209
|
};
|
|
7210
|
-
//# sourceMappingURL=chunk-
|
|
7210
|
+
//# sourceMappingURL=chunk-6O2NQ7WD.js.map
|
|
@@ -4,10 +4,20 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
parseJsonObjectBody
|
|
6
6
|
} from "./chunk-3NCPXNBZ.js";
|
|
7
|
+
import {
|
|
8
|
+
normalizeClientTurnId,
|
|
9
|
+
resolveChatTurn
|
|
10
|
+
} from "./chunk-YG66IHDU.js";
|
|
11
|
+
import {
|
|
12
|
+
coalesceDeltas,
|
|
13
|
+
createBufferedTurnTap,
|
|
14
|
+
replayTurnEvents,
|
|
15
|
+
stampReplaySeq
|
|
16
|
+
} from "./chunk-ISP6ZH3G.js";
|
|
7
17
|
import {
|
|
8
18
|
mentionInputToPart,
|
|
9
19
|
toChatMessageParts
|
|
10
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-TNWDYAFX.js";
|
|
11
21
|
import {
|
|
12
22
|
ChatTurnInputError,
|
|
13
23
|
TURN_INTENT_EVENT,
|
|
@@ -21,16 +31,6 @@ import {
|
|
|
21
31
|
parseFileMentions,
|
|
22
32
|
turnResponseEventFor
|
|
23
33
|
} from "./chunk-YHRWZZJN.js";
|
|
24
|
-
import {
|
|
25
|
-
normalizeClientTurnId,
|
|
26
|
-
resolveChatTurn
|
|
27
|
-
} from "./chunk-YG66IHDU.js";
|
|
28
|
-
import {
|
|
29
|
-
coalesceDeltas,
|
|
30
|
-
createBufferedTurnTap,
|
|
31
|
-
replayTurnEvents,
|
|
32
|
-
stampReplaySeq
|
|
33
|
-
} from "./chunk-ISP6ZH3G.js";
|
|
34
34
|
|
|
35
35
|
// src/chat-routes/draft-persistence.ts
|
|
36
36
|
var CONTENT_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -1270,4 +1270,4 @@ export {
|
|
|
1270
1270
|
createChatTurnRoutes,
|
|
1271
1271
|
streamChatRouteAsSandboxEvents
|
|
1272
1272
|
};
|
|
1273
|
-
//# sourceMappingURL=chunk-
|
|
1273
|
+
//# sourceMappingURL=chunk-J26IXWFK.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/skills/marketplace.ts
|
|
2
|
+
var SLUG = /^[a-z0-9][a-z0-9-]*$/;
|
|
3
|
+
var MAX_DESCRIPTION_CHARS = 240;
|
|
4
|
+
var MAX_MARKETPLACE_ENTRIES = 64;
|
|
5
|
+
function validateMarketplaceSkill(entry) {
|
|
6
|
+
if (!SLUG.test(entry.id)) throw new Error(`marketplace skill id "${entry.id}" must be a slug ([a-z0-9-])`);
|
|
7
|
+
if (!SLUG.test(entry.product)) throw new Error(`marketplace skill product "${entry.product}" must be a slug ([a-z0-9-])`);
|
|
8
|
+
const description = entry.description.trim();
|
|
9
|
+
if (!description) throw new Error(`marketplace skill ${entry.product}:${entry.id} has an empty description`);
|
|
10
|
+
if (description.length > MAX_DESCRIPTION_CHARS) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`marketplace skill ${entry.product}:${entry.id} description exceeds ${MAX_DESCRIPTION_CHARS} chars (got ${description.length}) \u2014 discovery must stay one line`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return { ...entry, description };
|
|
16
|
+
}
|
|
17
|
+
function namespacedSkillId(entry) {
|
|
18
|
+
return `${entry.product}:${entry.id}`;
|
|
19
|
+
}
|
|
20
|
+
function parseNamespacedSkillId(mountedId) {
|
|
21
|
+
const colon = mountedId.indexOf(":");
|
|
22
|
+
if (colon <= 0) return { product: null, id: mountedId };
|
|
23
|
+
return { product: mountedId.slice(0, colon), id: mountedId.slice(colon + 1) };
|
|
24
|
+
}
|
|
25
|
+
function validateMarketplaceCatalog(entries) {
|
|
26
|
+
if (entries.length > MAX_MARKETPLACE_ENTRIES) {
|
|
27
|
+
throw new Error(`marketplace catalog exceeds ${MAX_MARKETPLACE_ENTRIES} entries (got ${entries.length})`);
|
|
28
|
+
}
|
|
29
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30
|
+
const validated = entries.map((entry) => {
|
|
31
|
+
const clean = validateMarketplaceSkill(entry);
|
|
32
|
+
const key = namespacedSkillId(clean);
|
|
33
|
+
if (seen.has(key)) throw new Error(`marketplace catalog has a duplicate entry: ${key}`);
|
|
34
|
+
seen.add(key);
|
|
35
|
+
return clean;
|
|
36
|
+
});
|
|
37
|
+
return validated.sort((a, b) => a.product === b.product ? a.id < b.id ? -1 : a.id > b.id ? 1 : 0 : a.product < b.product ? -1 : 1);
|
|
38
|
+
}
|
|
39
|
+
function mergeSkillCatalogs(local, shared) {
|
|
40
|
+
const byId = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const entry of local) byId.set(entry.id, entry);
|
|
42
|
+
for (const entry of shared) {
|
|
43
|
+
const id = namespacedSkillId(entry);
|
|
44
|
+
if (byId.has(id)) continue;
|
|
45
|
+
byId.set(id, { id, description: `${entry.description} (shared by ${entry.product})` });
|
|
46
|
+
}
|
|
47
|
+
return [...byId.values()].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
48
|
+
}
|
|
49
|
+
function marketplaceDiscoveryLine(shared) {
|
|
50
|
+
if (shared.length === 0) return null;
|
|
51
|
+
const entries = shared.map((entry) => `${namespacedSkillId(entry)} (${entry.description})`).sort().join("; ");
|
|
52
|
+
return `Shared skills from other products, mountable by id: ${entries}.`;
|
|
53
|
+
}
|
|
54
|
+
function createInMemoryMarketplaceStore(entries) {
|
|
55
|
+
const validated = validateMarketplaceCatalog(entries);
|
|
56
|
+
return { async list() {
|
|
57
|
+
return validated;
|
|
58
|
+
} };
|
|
59
|
+
}
|
|
60
|
+
var createStaticMarketplaceStore = createInMemoryMarketplaceStore;
|
|
61
|
+
function createKVMarketplaceStore(kv, prefix = "marketplace") {
|
|
62
|
+
return {
|
|
63
|
+
async list() {
|
|
64
|
+
const raw = await kv.get(`${prefix}:catalog`);
|
|
65
|
+
if (!raw) return [];
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(raw);
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error(`marketplace catalog at ${prefix}:catalog is not valid JSON`);
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(parsed)) throw new Error(`marketplace catalog at ${prefix}:catalog is not a JSON array`);
|
|
73
|
+
return validateMarketplaceCatalog(parsed);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function marketplaceRowsFor(entries) {
|
|
78
|
+
return validateMarketplaceCatalog(entries).map((entry) => ({
|
|
79
|
+
id: namespacedSkillId(entry),
|
|
80
|
+
product: entry.product,
|
|
81
|
+
skillId: entry.id,
|
|
82
|
+
description: entry.description,
|
|
83
|
+
source: entry.source ?? null
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
MAX_MARKETPLACE_ENTRIES,
|
|
89
|
+
validateMarketplaceSkill,
|
|
90
|
+
namespacedSkillId,
|
|
91
|
+
parseNamespacedSkillId,
|
|
92
|
+
validateMarketplaceCatalog,
|
|
93
|
+
mergeSkillCatalogs,
|
|
94
|
+
marketplaceDiscoveryLine,
|
|
95
|
+
createInMemoryMarketplaceStore,
|
|
96
|
+
createStaticMarketplaceStore,
|
|
97
|
+
createKVMarketplaceStore,
|
|
98
|
+
marketplaceRowsFor
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=chunk-LTWJYNEC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/skills/marketplace.ts"],"sourcesContent":["/**\n * The cross-agent skill marketplace — a shared catalog contract on top of the\n * self-mount verb.\n *\n * Every product with the self-improve verb carries its own skill catalog\n * (8–12 skills each, fleet-wide), mounting them through\n * `createMountProfileChangeTool`, which already validates ids, caps mounts\n * and protects the baseline. What the fleet lacks is SHARING: a kicad skill\n * proven on hardware-agent should mount on the legal agent (patent drawings)\n * or the creative agent (product CAD) without a copy in every repo.\n *\n * This module is the marketplace's substrate: the catalog ENTRY format, a\n * read-only store port with the standard adapters, the merge that unions a\n * product's local catalog with shared entries (namespaced `product:id`,\n * local precedence, deduped, capped), and the one-line discovery fragment\n * products surface next to the verb's catalog. Mounting, validation, caps\n * and baseline protection stay exactly where they were proven — in the\n * verb. The marketplace only decides what MAY be discovered and mounted.\n *\n * An entry advertises the skill's identity, not its content: the owning\n * product ships SKILL.md bodies and executable companions in its own repo\n * (and its own profile mounts), so sharing is opt-in per product and the\n * entry is honest about what a foreign mount means — the description and\n * the owning product, nothing hidden.\n */\n\n/** A shareable skill as the owning product publishes it. */\nexport interface MarketplaceSkill {\n /** Registry-unique skill id WITHIN the owning product (slug: lowercase\n * letters, digits, hyphens). Mounted cross-product as `product:id`. */\n readonly id: string\n /** The owning product's slug (same alphabet). */\n readonly product: string\n /** One-line description surfaced in discovery and the verb's catalog. */\n readonly description: string\n /** Optional pointer to where the skill's content lives (repo path, url).\n * Provenance only — never fetched by this module. */\n readonly source?: string\n}\n\nconst SLUG = /^[a-z0-9][a-z0-9-]*$/\nconst MAX_DESCRIPTION_CHARS = 240\n/** Bound on a shared catalog so discovery text stays a prompt-sized line,\n * not a dump. */\nexport const MAX_MARKETPLACE_ENTRIES = 64\n\n/** Validate one marketplace entry — fail loud on a malformed catalog rather\n * than silently mis-mounting it. */\nexport function validateMarketplaceSkill(entry: MarketplaceSkill): MarketplaceSkill {\n if (!SLUG.test(entry.id)) throw new Error(`marketplace skill id \"${entry.id}\" must be a slug ([a-z0-9-])`)\n if (!SLUG.test(entry.product)) throw new Error(`marketplace skill product \"${entry.product}\" must be a slug ([a-z0-9-])`)\n const description = entry.description.trim()\n if (!description) throw new Error(`marketplace skill ${entry.product}:${entry.id} has an empty description`)\n if (description.length > MAX_DESCRIPTION_CHARS) {\n throw new Error(\n `marketplace skill ${entry.product}:${entry.id} description exceeds ${MAX_DESCRIPTION_CHARS} chars ` +\n `(got ${description.length}) — discovery must stay one line`,\n )\n }\n return { ...entry, description }\n}\n\n/** The mounted id for a shared skill: `product:id` (namespaced so a foreign\n * skill can never shadow a local one). */\nexport function namespacedSkillId(entry: { product: string; id: string }): string {\n return `${entry.product}:${entry.id}`\n}\n\n/** Split a namespaced id back into `{ product, id }`; a bare id resolves to\n * product `null` (a local skill). */\nexport function parseNamespacedSkillId(mountedId: string): { product: string | null; id: string } {\n const colon = mountedId.indexOf(':')\n if (colon <= 0) return { product: null, id: mountedId }\n return { product: mountedId.slice(0, colon), id: mountedId.slice(colon + 1) }\n}\n\n/** Validate a whole shared catalog: entries well-formed, ids unique, size\n * capped. Returns the validated entries in stable (product, id) order. */\nexport function validateMarketplaceCatalog(entries: readonly MarketplaceSkill[]): readonly MarketplaceSkill[] {\n if (entries.length > MAX_MARKETPLACE_ENTRIES) {\n throw new Error(`marketplace catalog exceeds ${MAX_MARKETPLACE_ENTRIES} entries (got ${entries.length})`)\n }\n const seen = new Set<string>()\n const validated = entries.map((entry) => {\n const clean = validateMarketplaceSkill(entry)\n const key = namespacedSkillId(clean)\n if (seen.has(key)) throw new Error(`marketplace catalog has a duplicate entry: ${key}`)\n seen.add(key)\n return clean\n })\n return validated.sort((a, b) => (a.product === b.product\n ? (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)\n : (a.product < b.product ? -1 : 1)))\n}\n\n/** A shared-skill entry as it joins a product's local catalog: the\n * namespaced mount id plus the description (annotated with the owner). */\nexport interface CatalogSkillEntry {\n readonly id: string\n readonly description: string\n}\n\n/** Union a product's local catalog with shared marketplace entries.\n *\n * Precedence: LOCAL WINS — a namespaced shared id can never collide with a\n * bare local one, and if a product ever adopts its own shared entry, the\n * local description stays authoritative. Output is deduped, sorted, and\n * safe to hand straight to `createMountProfileChangeTool`'s `skills`. */\nexport function mergeSkillCatalogs(\n local: readonly CatalogSkillEntry[],\n shared: readonly MarketplaceSkill[],\n): readonly CatalogSkillEntry[] {\n const byId = new Map<string, CatalogSkillEntry>()\n for (const entry of local) byId.set(entry.id, entry)\n for (const entry of shared) {\n const id = namespacedSkillId(entry)\n if (byId.has(id)) continue\n byId.set(id, { id, description: `${entry.description} (shared by ${entry.product})` })\n }\n return [...byId.values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n}\n\n/** The discovery fragment: one line naming every shared skill a foreign\n * agent can mount, for system prompts and tool descriptions. Returns null\n * when there is nothing to share (no line, no prompt cost). */\nexport function marketplaceDiscoveryLine(shared: readonly MarketplaceSkill[]): string | null {\n if (shared.length === 0) return null\n const entries = shared\n .map((entry) => `${namespacedSkillId(entry)} (${entry.description})`)\n .sort()\n .join('; ')\n return `Shared skills from other products, mountable by id: ${entries}.`\n}\n\n/** Read-only view over a shared catalog. Products source it from their own\n * D1/KV, a platform registry, or a pinned static list — the port stays the\n * same. */\nexport interface MarketplaceStore {\n list(): Promise<readonly MarketplaceSkill[]>\n}\n\n/** Non-durable default for tests and single-instance products. Validates on\n * construction so a malformed catalog fails at wiring, not at mount. */\nexport function createInMemoryMarketplaceStore(entries: readonly MarketplaceSkill[]): MarketplaceStore {\n const validated = validateMarketplaceCatalog(entries)\n return { async list() { return validated } }\n}\n\n/** Static-list store over a product-pinned catalog (checked into the\n * product repo). Same validation guarantees, no async source. */\nexport const createStaticMarketplaceStore = createInMemoryMarketplaceStore\n\n/** A KV-backed read-only MarketplaceStore (Cloudflare KV or any lookalike).\n * The whole catalog is one JSON value at `prefix:catalog`. */\nexport function createKVMarketplaceStore(\n kv: { get(key: string): Promise<string | null> },\n prefix = 'marketplace',\n): MarketplaceStore {\n return {\n async list(): Promise<readonly MarketplaceSkill[]> {\n const raw = await kv.get(`${prefix}:catalog`)\n if (!raw) return []\n let parsed: unknown\n try { parsed = JSON.parse(raw) } catch { throw new Error(`marketplace catalog at ${prefix}:catalog is not valid JSON`) }\n if (!Array.isArray(parsed)) throw new Error(`marketplace catalog at ${prefix}:catalog is not a JSON array`)\n return validateMarketplaceCatalog(parsed as MarketplaceSkill[])\n },\n }\n}\n\n/** Publish helper for the write side (products/platform own the writer):\n * render validated rows for the standard table (see\n * `./skills/marketplace-drizzle` for the table itself). */\nexport interface MarketplaceRow {\n readonly id: string\n readonly product: string\n readonly skillId: string\n readonly description: string\n readonly source: string | null\n}\n\nexport function marketplaceRowsFor(entries: readonly MarketplaceSkill[]): readonly MarketplaceRow[] {\n return validateMarketplaceCatalog(entries).map((entry) => ({\n id: namespacedSkillId(entry),\n product: entry.product,\n skillId: entry.id,\n description: entry.description,\n source: entry.source ?? null,\n }))\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mentionKindForPath
|
|
3
|
+
} from "./chunk-YHRWZZJN.js";
|
|
1
4
|
import {
|
|
2
5
|
persistedPartToWorkProduct
|
|
3
6
|
} from "./chunk-ZVEEWGDK.js";
|
|
4
7
|
import {
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
persistedPartToInteraction
|
|
9
|
+
} from "./chunk-6VQCFJWP.js";
|
|
7
10
|
import {
|
|
8
11
|
persistedPartToPlan,
|
|
9
12
|
planToPersistedPart
|
|
10
13
|
} from "./chunk-LQYIBL3G.js";
|
|
11
|
-
import {
|
|
12
|
-
persistedPartToInteraction
|
|
13
|
-
} from "./chunk-6VQCFJWP.js";
|
|
14
14
|
|
|
15
15
|
// src/chat-store/parts.ts
|
|
16
16
|
function toChatMessageParts(parts) {
|
|
@@ -135,4 +135,4 @@ export {
|
|
|
135
135
|
buildAttachmentPromptBlock,
|
|
136
136
|
historyContentWithAttachments
|
|
137
137
|
};
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
138
|
+
//# sourceMappingURL=chunk-TNWDYAFX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/skills/index.ts"],"sourcesContent":["/**\n * Unified skill + corpus mounter for agent products.\n *\n * Every agent product hand-rolls the same two file-mount systems and then\n * drifts on the seams between them. (1) An ALWAYS-MOUNTED markdown corpus —\n * (`skills/<slug>/SKILL.md`, `doctrine` and `knowledge` markdown trees) — discovered\n * by a Vite `?raw` glob in the Worker bundle and by Node `fs` under the eval\n * CLI, then projected into `resources.files`. (2) A TIER-GATED installable\n * registry — a hand-authored array of `SkillEntry` whose free tier mounts at\n * the harness skill-discovery path and whose paid tier is installed on demand.\n * Both ride the same `resources.files` channel but use different provenance\n * (file-backed vs inline), different mount paths (relative corpus path vs\n * `~/.claude/skills/<id>/SKILL.md`), and different selection rules. This module\n * makes both DATA: a corpus loader that accepts a Vite glob-result map (or an\n * fs fallback), a registry adapter that tier-gates, and a single\n * `composeShellResources` that projects either onto the SDK file-mount shape.\n *\n * A THIRD surface lives alongside those two: adoptable `SkillEntry`s sourced\n * from `SKILL.md` frontmatter rather than hand-authored fields.\n * `parseSkillFrontmatter` is the ONE frontmatter parser (hand-rolled, no YAML\n * dependency — fail loud on a malformed block rather than silently mis-reading\n * a field); `skillEntryFromMarkdown`/`parseCorpusSkills` turn raw markdown into\n * `SkillEntry`s. From there a skill reaches the agent by one of two DELIVERY\n * MODES: `inline` renders the skill body straight into the system prompt (every\n * harness can read it, at prompt-byte cost), or `mounted` projects it onto the\n * typed `resources.skills` channel (`AgentProfileResourceRef[]`) plus an index\n * section that just names the file, and lets the platform materializer place it\n * at the harness-native skill dir. `composeSkills` builds either shape;\n * `mergeComposedSkills` combines batches and throws (via\n * `assertSkillDeliveryDisjoint`) on a skill accidentally delivered both\n * ways. Picking WHICH harnesses can take `mounted` delivery is platform-bound\n * (see `@tangle-network/agent-app/skills-placement`) and deliberately kept out\n * of this substrate-free module.\n *\n * Storage-independent and exact over the profile boundary: the only inbound\n * seam is the glob-result map the consumer passes in (its call site keeps the\n * literal `import.meta.glob` Vite must static-analyze); the only outbound seam\n * is `@tangle-network/agent-interface`'s\n * `AgentProfileFileMount[]`/`AgentProfileResourceRef[]`,\n * the exact shapes `resources.files`/`resources.skills` consume. Node builtins\n * are resolved lazily via `process.getBuiltinModule` so a static `node:*`\n * import never reaches the Vite SSR bundle.\n */\n\nimport type { AgentProfileFileMount, AgentProfileResourceRef } from '@tangle-network/agent-interface'\nimport { parseSkillFrontmatter } from './frontmatter'\n\n/** Construct the inline arm of `AgentProfileResourceRef`. Inlined here so this\n * leaf subpath stays type-only over `@tangle-network/agent-interface`. */\nfunction inlineResource(name: string, content: string): AgentProfileResourceRef {\n return { kind: 'inline', name, content }\n}\n\n/** A Vite eager `?raw` glob result: glob key -> raw file body. The consumer\n * produces this by calling `import.meta.glob('<lit>', { eager: true, query:\n * '?raw', import: 'default' })` at its own call site — the literal must stay\n * literal so Vite can static-analyze it; passing the result here keeps that\n * constraint at the edge and the loader substrate-free. */\nexport type GlobModules = Record<string, string>\n\n/** One markdown document discovered from the corpus. */\nexport interface CorpusEntry {\n /** Slug derived from the glob key (folder slug for `SKILL.md` layouts, or the\n * normalized relative path for flat `*.md` layouts). */\n id: string\n /** Glob/fs key the entry was loaded from, normalized to a stable relative\n * form (leading `./` and absolute prefixes stripped). */\n key: string\n /** Raw markdown body (including any frontmatter). */\n content: string\n}\n\n/** A hand-authored, tier-gated installable skill. Mirrors the per-product\n * registry entry (gtm/insurance `SkillEntry`); the runtime's certified `skill`\n * artifact kind is unrelated. `skillMd` is the inline body — file provenance\n * does not apply to the registry. */\nexport interface SkillEntry {\n id: string\n name: string\n description: string\n author?: { name: string; url?: string }\n source?: string\n category?: string\n tags?: string[]\n /** Gate keyword. `composeShellResources`/`registrySkills` treat `free` as\n * always-mounted; everything else is install-on-demand. */\n tier: string\n skillMd: string\n}\n\n/** Harness skill-discovery path the Claude Code backend reads natively. The\n * registry mounts here; the corpus mounts at its relative path.\n *\n * @deprecated Hardcodes the claude-code path (`~/.claude/skills/<id>/SKILL.md`).\n * It is NOT a path other harnesses read — OpenCode discovers `.opencode/skills`,\n * not `~/.claude/skills` (the doc comment here previously claimed otherwise);\n * codex, kimi-code, and the rest each have their own dir or none at all. Use\n * {@link skillRefs} to put a skill on the typed `resources.skills` channel and\n * let the platform materializer place it correctly, or resolve the\n * harness-native dir directly via `@tangle-network/agent-app/skills-placement`.\n * Kept only for the pre-existing `registrySkills`/`userSkillMounts` callers;\n * do not add new call sites. */\nexport function skillMountPath(id: string): string {\n return `~/.claude/skills/${id}/SKILL.md`\n}\n\n/** Strip a glob/fs key down to a stable relative form: drop a leading `./`,\n * and for an absolute fs path keep only the tail from the last anchor segment\n * the pattern implies. We normalize on the trailing `<dir>/.../*.md` so the\n * Vite key (`./skills/x/SKILL.md`) and the fs key (`/abs/.../skills/x/SKILL.md`)\n * collapse to the same value. */\nfunction normalizeKey(key: string, anchor: string): string {\n const marker = `${anchor}/`\n const at = key.lastIndexOf(marker)\n if (at >= 0) return key.slice(at)\n return key.startsWith('./') ? key.slice(2) : key\n}\n\n/** Folder-slug for a `<anchor>/<slug>/SKILL.md` layout; falls back to the\n * normalized key (sans `<anchor>/` prefix, sans `.md`) for flat layouts. */\nfunction toCorpusId(normalizedKey: string, anchor: string): string {\n const nested = normalizedKey.match(new RegExp(`${anchor}/([^/]+)/SKILL\\\\.md$`))\n if (nested) return nested[1]!\n const flat = normalizedKey.match(new RegExp(`${anchor}/(.+)\\\\.md$`))\n if (flat) return flat[1]!\n return normalizedKey\n}\n\n/** Resolve Node builtins lazily. `process.getBuiltinModule` (Node 22+) is\n * absent in workerd, so this returns undefined there and the fs path is never\n * taken — Workers always reach the loader through the Vite glob map. A static\n * `import 'node:fs'` would break Vite SSR bundling in the consumer apps, so it\n * is deliberately avoided. */\nfunction nodeBuiltins():\n | { fs: typeof import('node:fs'); path: typeof import('node:path'); url: typeof import('node:url') }\n | undefined {\n const getBuiltin = (globalThis as { process?: { getBuiltinModule?: (id: string) => unknown } })\n .process?.getBuiltinModule\n if (typeof getBuiltin !== 'function') return undefined\n return {\n fs: getBuiltin('node:fs') as typeof import('node:fs'),\n path: getBuiltin('node:path') as typeof import('node:path'),\n url: getBuiltin('node:url') as typeof import('node:url'),\n }\n}\n\n/** Options for {@link loadMarkdownCorpus}. */\nexport interface LoadCorpusOptions {\n /** The anchor folder name that appears in both glob keys and fs paths\n * (`skills`, `doctrine`, `knowledge`). Used to normalize keys + derive ids. */\n anchor: string\n /** Vite glob-result map. When present and non-empty it is authoritative and\n * the fs path is skipped. Omit it (or pass an empty map) only outside Vite. */\n globModules?: GlobModules\n /** Absolute or `import.meta.url`-relative base dir the fs fallback walks when\n * `globModules` is empty. Required for the fs path to run; without it the fs\n * fallback returns no entries (Workers never need it). */\n fsBaseDir?: string\n /** Walk strategy for the fs fallback. `nested` finds `<dir>/<slug>/SKILL.md`\n * one level deep; `flat` recurses for every `*.md`. Default: `flat`. */\n fsLayout?: 'nested' | 'flat'\n /** Drop an entry by its normalized key after load. Covers the per-product\n * skip lists (corpus index/log files, scaffold templates, allow-lists). */\n skip?: (normalizedKey: string) => boolean\n}\n\n/** Outcome of {@link loadMarkdownCorpus}: the entries plus which path produced\n * them, so a caller can fail loud when both are empty rather than silently\n * mounting nothing. */\nexport interface CorpusLoadResult {\n source: 'vite' | 'fs' | 'empty'\n entries: CorpusEntry[]\n}\n\nfunction fsWalkFlat(\n builtins: NonNullable<ReturnType<typeof nodeBuiltins>>,\n root: string,\n): GlobModules {\n const { fs, path } = builtins\n const out: GlobModules = {}\n if (!fs.existsSync(root)) return out\n const walk = (dir: string) => {\n let entries: import('node:fs').Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue\n const full = path.join(dir, entry.name)\n if (entry.isDirectory()) walk(full)\n else if (entry.isFile() && entry.name.endsWith('.md')) out[full] = fs.readFileSync(full, 'utf8')\n }\n }\n walk(root)\n return out\n}\n\nfunction fsWalkNested(\n builtins: NonNullable<ReturnType<typeof nodeBuiltins>>,\n root: string,\n): GlobModules {\n const { fs, path } = builtins\n const out: GlobModules = {}\n if (!fs.existsSync(root)) return out\n let entries: import('node:fs').Dirent[]\n try {\n entries = fs.readdirSync(root, { withFileTypes: true })\n } catch {\n return out\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const skillFile = path.join(root, entry.name, 'SKILL.md')\n if (!fs.existsSync(skillFile)) continue\n out[skillFile] = fs.readFileSync(skillFile, 'utf8')\n }\n return out\n}\n\n/** Resolve `fsBaseDir` against `import.meta.url` when relative-looking, so a\n * consumer can pass a bare folder name (`'skills'`) and have it land beside\n * the calling module. Absolute paths pass through. */\nfunction resolveFsBase(\n builtins: NonNullable<ReturnType<typeof nodeBuiltins>>,\n fsBaseDir: string,\n importMetaUrl?: string,\n): string {\n const { path, url } = builtins\n if (path.isAbsolute(fsBaseDir)) return fsBaseDir\n const here = importMetaUrl\n ? path.dirname(url.fileURLToPath(importMetaUrl))\n : process.cwd()\n return path.join(here, fsBaseDir)\n}\n\n/**\n * Load a markdown corpus, preferring a Vite glob-result map and falling back to\n * a Node fs walk. Selection is by non-empty glob result — never an env flag.\n * Entries are normalized, optionally skip-filtered, and sorted by id for\n * determinism. The `import.meta.glob` literal stays at the CONSUMER call site\n * (passed in as `globModules`); this loader never constructs a glob.\n */\nexport function loadMarkdownCorpus(\n options: LoadCorpusOptions,\n importMetaUrl?: string,\n): CorpusLoadResult {\n const { anchor, globModules, fsBaseDir, fsLayout = 'flat', skip } = options\n\n let modules: GlobModules\n let source: CorpusLoadResult['source']\n if (globModules && Object.keys(globModules).length > 0) {\n modules = globModules\n source = 'vite'\n } else {\n const builtins = nodeBuiltins()\n if (builtins && fsBaseDir) {\n const root = resolveFsBase(builtins, fsBaseDir, importMetaUrl)\n modules = fsLayout === 'nested' ? fsWalkNested(builtins, root) : fsWalkFlat(builtins, root)\n source = Object.keys(modules).length > 0 ? 'fs' : 'empty'\n } else {\n modules = {}\n source = 'empty'\n }\n }\n\n const entries: CorpusEntry[] = []\n for (const [rawKey, content] of Object.entries(modules)) {\n if (typeof content !== 'string') continue\n const key = normalizeKey(rawKey, anchor)\n if (skip && skip(key)) continue\n entries.push({ id: toCorpusId(key, anchor), key, content })\n }\n entries.sort((a, b) => a.id.localeCompare(b.id))\n return { source, entries }\n}\n\n/** Project corpus entries onto SDK file mounts at a relative path under\n * `<anchor>/`. Always-mounted: the corpus is the agent's baseline knowledge. */\nexport function corpusSkills(corpus: CorpusEntry[], anchor: string): AgentProfileFileMount[] {\n return corpus\n .map(\n (entry) =>\n ({\n path: `${anchor}/${entry.id}.md`,\n resource: inlineResource(`${anchor}-${entry.id}`, entry.content),\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/** Project the registry's free-tier (or `tier`-matched) entries onto SDK file\n * mounts at the harness skill-discovery path. Tier-gating is the registry's\n * only selection rule — paid skills are installed on demand, not at boot. */\nexport function registrySkills(\n registry: SkillEntry[],\n tier: string = 'free',\n): AgentProfileFileMount[] {\n return registry\n .filter((s) => s.tier === tier)\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: inlineResource(s.id, s.skillMd),\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/** Inputs to {@link composeShellResources}. Each channel is optional so a\n * product mounts only the systems it has — corpus-only, registry-only, or\n * both — without conflating them. */\nexport interface ComposeShellResourcesInput {\n /** Corpus mounts (always-mounted baseline). Pass the result of\n * {@link corpusSkills}, or a hand-built mount list. */\n skills?: AgentProfileFileMount[]\n /** Knowledge-corpus mounts (a second always-mounted corpus, e.g. a domain\n * knowledge pack distinct from the skills corpus). */\n knowledge?: AgentProfileFileMount[]\n /** Evolvable / learned-guidance mounts (single-file corpora). */\n evolvable?: AgentProfileFileMount[]\n /** Registry mounts (tier-gated). Pass the result of {@link registrySkills}. */\n registry?: AgentProfileFileMount[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n predicate?: (mount: AgentProfileFileMount) => boolean\n}\n\n/**\n * Compose every mount channel into one `resources.files`-ready array. Corpus\n * channels come first (baseline), the tier-gated registry last (so a registry\n * entry can override a corpus entry that mounts at the same path). The result\n * is exactly `AgentProfileFileMount[]` — assign it straight into\n * `profile.resources.files` with no cast.\n */\nexport function composeShellResources(input: ComposeShellResourcesInput): AgentProfileFileMount[] {\n const { skills = [], knowledge = [], evolvable = [], registry = [], predicate } = input\n const composed = [...skills, ...knowledge, ...evolvable, ...registry]\n return predicate ? composed.filter(predicate) : composed\n}\n\n// ─── Adoptable skills: one frontmatter parser, two delivery modes ─────────────\n\nexport { parseSkillFrontmatter, type ParsedSkill, type SkillFrontmatter } from './frontmatter'\n\n/** Build a {@link SkillEntry} from a raw `SKILL.md` body. `id` comes from\n * frontmatter, falling back to `fallbackId` (typically the corpus entry's\n * slug/filename); neither present throws. `name` defaults to `id`,\n * `description` to `''`, `tier` to `'free'`. `skillMd` is always the\n * untouched `raw` input — the full file, frontmatter included, is what a\n * `mounted` delivery writes to disk and what `renderInlineSkills` strips per\n * render. */\nexport function skillEntryFromMarkdown(raw: string, fallbackId?: string): SkillEntry {\n const { frontmatter } = parseSkillFrontmatter(raw)\n const id = frontmatter.id ?? fallbackId\n if (!id) {\n throw new Error(\n 'skillEntryFromMarkdown: no \"id\" in frontmatter and no fallbackId supplied — cannot build a SkillEntry',\n )\n }\n const entry: SkillEntry = {\n id,\n name: frontmatter.name ?? id,\n description: frontmatter.description ?? '',\n tier: frontmatter.tier ?? 'free',\n skillMd: raw,\n }\n if (frontmatter.author) entry.author = frontmatter.author\n if (frontmatter.source) entry.source = frontmatter.source\n if (frontmatter.category) entry.category = frontmatter.category\n if (frontmatter.tags) entry.tags = frontmatter.tags\n return entry\n}\n\n/** Map a loaded corpus (see {@link loadMarkdownCorpus}) onto `SkillEntry`s,\n * using each entry's `id` as the fallback when its `SKILL.md` carries no\n * frontmatter `id` of its own. */\nexport function parseCorpusSkills(corpus: CorpusEntry[]): SkillEntry[] {\n return corpus.map((entry) => skillEntryFromMarkdown(entry.content, entry.id))\n}\n\n/** Project skills onto the typed `resources.skills` channel\n * (`AgentProfileResourceRef[]`), tier-filtered (same `s.tier === tier`\n * semantics as {@link registrySkills}) when `opts.tier` is given, sorted by\n * id for determinism. `ref.name` MUST be the skill id: the platform\n * materializer writes each ref to `${skillDir}/${name}/SKILL.md`. */\nexport function skillRefs(\n skills: SkillEntry[],\n opts: { tier?: string } = {},\n): AgentProfileResourceRef[] {\n const filtered = opts.tier ? skills.filter((s) => s.tier === opts.tier) : skills\n return [...filtered]\n .sort((a, b) => a.id.localeCompare(b.id))\n .map((s) => inlineResource(s.id, s.skillMd))\n}\n\n/** Strip the frontmatter block from a skill's `skillMd` and render it as a\n * prompt subsection. The default `body` renderer {@link renderInlineSkills}\n * passes to itself. */\nfunction renderInlineSkillBody(skill: SkillEntry): string {\n const { body } = parseSkillFrontmatter(skill.skillMd)\n return `### ${skill.name}\\n\\n${body.trim()}`\n}\n\n/** Inputs to {@link renderInlineSkills}. */\nexport interface RenderInlineSkillsInput {\n skills: SkillEntry[]\n /** Section heading. Default `'## Skills'`. */\n heading?: string\n /** Tier filter — same semantics as {@link skillRefs}. */\n tier?: string\n /** Per-skill body renderer. Default strips frontmatter and emits\n * `### <name>\\n\\n<body>`. */\n body?: (skill: SkillEntry) => string\n}\n\n/**\n * Render every (tier-filtered) skill's full body inline into the prompt — the\n * `inline` delivery mode. Returns `''` when no skill survives the filter, else\n * a section starting `\\n\\n<heading>\\n\\n` with each skill's body joined by\n * `\\n\\n` — the same \"already carries its own leading `\\n\\n`\" shape\n * `assembleSystemPrompt` expects from every section it concatenates.\n */\nexport function renderInlineSkills(input: RenderInlineSkillsInput): string {\n const heading = input.heading ?? '## Skills'\n const filtered = input.tier ? input.skills.filter((s) => s.tier === input.tier) : input.skills\n if (filtered.length === 0) return ''\n const renderBody = input.body ?? renderInlineSkillBody\n return `\\n\\n${heading}\\n\\n${filtered.map(renderBody).join('\\n\\n')}`\n}\n\n/** Inputs to {@link renderSkillIndex}. */\nexport interface RenderSkillIndexInput {\n skills: SkillEntry[]\n /** cwd-relative directory the skills are mounted under (e.g.\n * `.opencode/skills`) — named in each index line so the agent knows where\n * to read the full `SKILL.md`. */\n skillDir: string\n /** Section heading. Default `'## Skills'`. */\n heading?: string\n /** Tier filter — same semantics as {@link skillRefs}. */\n tier?: string\n}\n\n/**\n * Render a one-line-per-skill INDEX (name, description, and the path to read\n * the full body) — the `mounted` delivery mode's prompt section, paired with\n * {@link skillRefs} putting the actual files on `resources.skills`. Same\n * empty/section shape as {@link renderInlineSkills}.\n */\nexport function renderSkillIndex(input: RenderSkillIndexInput): string {\n const heading = input.heading ?? '## Skills'\n const filtered = input.tier ? input.skills.filter((s) => s.tier === input.tier) : input.skills\n if (filtered.length === 0) return ''\n const lines = filtered.map(\n (s) => `- ${s.name}: ${s.description} (read ${input.skillDir}/${s.id}/SKILL.md)`,\n )\n return `\\n\\n${heading}\\n\\n${lines.join('\\n')}`\n}\n\n/** How a skill reaches the agent: `inline` renders its full body into the\n * system prompt; `mounted` puts it on the typed `resources.skills` channel\n * and renders only an index line. */\nexport type SkillDeliveryMode = 'inline' | 'mounted'\n\n/** The output of {@link composeSkills}: the refs to attach to\n * `resources.skills` (empty for `inline`) and the prompt section to fold into\n * the system prompt (already carries its own leading `\\n\\n`, or `''`).\n * `inlineIds`/`mountedIds` record which (tier-filtered) skills were delivered\n * in each mode so {@link mergeComposedSkills} can enforce the modes stay\n * disjoint when batches are combined. */\nexport interface ComposedSkills {\n refs: AgentProfileResourceRef[]\n promptSection: string\n inlineIds: string[]\n mountedIds: string[]\n}\n\n/** Inputs to {@link composeSkills}. */\nexport interface ComposeSkillsInput {\n skills: SkillEntry[]\n mode: SkillDeliveryMode\n tier?: string\n heading?: string\n /** REQUIRED (non-null) for `mode: 'mounted'` — the cwd-relative skill dir\n * the harness reads. Resolve it with `@tangle-network/agent-app/skills-placement`\n * rather than hardcoding it; omitted or `null` throws. */\n skillDir?: string | null\n}\n\n/**\n * Build the {@link ComposedSkills} for one delivery mode. `inline` never\n * touches `resources.skills` — `refs` is always `[]`. `mounted` requires a\n * non-null `skillDir` (the harness must have a native skill-discovery\n * directory); a null/absent one throws rather than silently falling back, so\n * the caller resolves the fallback deliberately (see\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`,\n * whose own default is to throw rather than choose `inline` for you — inline\n * is a narrow, explicitly opted-into fallback, not a co-equal mode).\n */\nexport function composeSkills(input: ComposeSkillsInput): ComposedSkills {\n const { skills, mode, tier, heading } = input\n const deliveredIds = (tier ? skills.filter((s) => s.tier === tier) : skills).map((s) => s.id)\n if (mode === 'inline') {\n return {\n refs: [],\n promptSection: renderInlineSkills({ skills, tier, heading }),\n inlineIds: deliveredIds,\n mountedIds: [],\n }\n }\n if (!input.skillDir) {\n throw new Error(\n 'composeSkills: mode \"mounted\" requires a non-null skillDir — this harness cannot receive ' +\n 'skill files at a cwd path; pass mode \"inline\" instead',\n )\n }\n return {\n refs: skillRefs(skills, { tier }),\n promptSection: renderSkillIndex({ skills, skillDir: input.skillDir, tier, heading }),\n inlineIds: [],\n mountedIds: deliveredIds,\n }\n}\n\n/** Throw when the same skill id is delivered both `inline` and `mounted` —\n * the agent would see it twice (once in the prompt body, once as a mounted\n * file it's told to go read), doubling prompt bytes and inviting drift\n * between the two copies. Lists every offending id in the message. */\nexport function assertSkillDeliveryDisjoint(\n inlineIds: Iterable<string>,\n mountedIds: Iterable<string>,\n): void {\n const inline = new Set(inlineIds)\n const overlap = [...new Set(mountedIds)].filter((id) => inline.has(id))\n if (overlap.length > 0) {\n throw new Error(\n `assertSkillDeliveryDisjoint: skill id(s) delivered both inline and mounted: ${overlap.join(', ')}`,\n )\n }\n}\n\n/**\n * Combine multiple {@link ComposedSkills} batches (e.g. a built-in corpus and\n * an installed catalog) into one, concatenating refs and prompt sections in\n * batch order. This is the seam where inline and mounted deliveries can first\n * collide, so it applies {@link assertSkillDeliveryDisjoint} — a skill id\n * delivered inline by one batch and mounted by another throws instead of\n * reaching the agent twice. Merge through this rather than spreading batch\n * fields by hand.\n */\nexport function mergeComposedSkills(batches: ComposedSkills[]): ComposedSkills {\n const merged: ComposedSkills = {\n refs: batches.flatMap((b) => b.refs),\n promptSection: batches.map((b) => b.promptSection).join(''),\n inlineIds: batches.flatMap((b) => b.inlineIds),\n mountedIds: batches.flatMap((b) => b.mountedIds),\n }\n assertSkillDeliveryDisjoint(merged.inlineIds, merged.mountedIds)\n return merged\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/skills/index.ts"],"sourcesContent":["/**\n * Unified skill + corpus mounter for agent products.\n *\n * Every agent product hand-rolls the same two file-mount systems and then\n * drifts on the seams between them. (1) An ALWAYS-MOUNTED markdown corpus —\n * (`skills/<slug>/SKILL.md`, `doctrine` and `knowledge` markdown trees) — discovered\n * by a Vite `?raw` glob in the Worker bundle and by Node `fs` under the eval\n * CLI, then projected into `resources.files`. (2) A TIER-GATED installable\n * registry — a hand-authored array of `SkillEntry` whose free tier mounts at\n * the harness skill-discovery path and whose paid tier is installed on demand.\n * Both ride the same `resources.files` channel but use different provenance\n * (file-backed vs inline), different mount paths (relative corpus path vs\n * `~/.claude/skills/<id>/SKILL.md`), and different selection rules. This module\n * makes both DATA: a corpus loader that accepts a Vite glob-result map (or an\n * fs fallback), a registry adapter that tier-gates, and a single\n * `composeShellResources` that projects either onto the SDK file-mount shape.\n *\n * A THIRD surface lives alongside those two: adoptable `SkillEntry`s sourced\n * from `SKILL.md` frontmatter rather than hand-authored fields.\n * `parseSkillFrontmatter` is the ONE frontmatter parser (hand-rolled, no YAML\n * dependency — fail loud on a malformed block rather than silently mis-reading\n * a field); `skillEntryFromMarkdown`/`parseCorpusSkills` turn raw markdown into\n * `SkillEntry`s. From there a skill reaches the agent by one of two DELIVERY\n * MODES: `inline` renders the skill body straight into the system prompt (every\n * harness can read it, at prompt-byte cost), or `mounted` projects it onto the\n * typed `resources.skills` channel (`AgentProfileResourceRef[]`) plus an index\n * section that just names the file, and lets the platform materializer place it\n * at the harness-native skill dir. `composeSkills` builds either shape;\n * `mergeComposedSkills` combines batches and throws (via\n * `assertSkillDeliveryDisjoint`) on a skill accidentally delivered both\n * ways. Picking WHICH harnesses can take `mounted` delivery is platform-bound\n * (see `@tangle-network/agent-app/skills-placement`) and deliberately kept out\n * of this substrate-free module.\n *\n * Storage-independent and exact over the profile boundary: the only inbound\n * seam is the glob-result map the consumer passes in (its call site keeps the\n * literal `import.meta.glob` Vite must static-analyze); the only outbound seam\n * is `@tangle-network/agent-interface`'s\n * `AgentProfileFileMount[]`/`AgentProfileResourceRef[]`,\n * the exact shapes `resources.files`/`resources.skills` consume. Node builtins\n * are resolved lazily via `process.getBuiltinModule` so a static `node:*`\n * import never reaches the Vite SSR bundle.\n */\n\nimport type { AgentProfileFileMount, AgentProfileResourceRef } from '@tangle-network/agent-interface'\nimport { parseSkillFrontmatter } from './frontmatter'\n\n/** Construct the inline arm of `AgentProfileResourceRef`. Inlined here so this\n * leaf subpath stays type-only over `@tangle-network/agent-interface`. */\nfunction inlineResource(name: string, content: string): AgentProfileResourceRef {\n return { kind: 'inline', name, content }\n}\n\n/** A Vite eager `?raw` glob result: glob key -> raw file body. The consumer\n * produces this by calling `import.meta.glob('<lit>', { eager: true, query:\n * '?raw', import: 'default' })` at its own call site — the literal must stay\n * literal so Vite can static-analyze it; passing the result here keeps that\n * constraint at the edge and the loader substrate-free. */\nexport type GlobModules = Record<string, string>\n\n/** One markdown document discovered from the corpus. */\nexport interface CorpusEntry {\n /** Slug derived from the glob key (folder slug for `SKILL.md` layouts, or the\n * normalized relative path for flat `*.md` layouts). */\n id: string\n /** Glob/fs key the entry was loaded from, normalized to a stable relative\n * form (leading `./` and absolute prefixes stripped). */\n key: string\n /** Raw markdown body (including any frontmatter). */\n content: string\n}\n\n/** A hand-authored, tier-gated installable skill. Mirrors the per-product\n * registry entry (gtm/insurance `SkillEntry`); the runtime's certified `skill`\n * artifact kind is unrelated. `skillMd` is the inline body — file provenance\n * does not apply to the registry. */\nexport interface SkillEntry {\n id: string\n name: string\n description: string\n author?: { name: string; url?: string }\n source?: string\n category?: string\n tags?: string[]\n /** Gate keyword. `composeShellResources`/`registrySkills` treat `free` as\n * always-mounted; everything else is install-on-demand. */\n tier: string\n skillMd: string\n}\n\n/** Harness skill-discovery path the Claude Code backend reads natively. The\n * registry mounts here; the corpus mounts at its relative path.\n *\n * @deprecated Hardcodes the claude-code path (`~/.claude/skills/<id>/SKILL.md`).\n * It is NOT a path other harnesses read — OpenCode discovers `.opencode/skills`,\n * not `~/.claude/skills` (the doc comment here previously claimed otherwise);\n * codex, kimi-code, and the rest each have their own dir or none at all. Use\n * {@link skillRefs} to put a skill on the typed `resources.skills` channel and\n * let the platform materializer place it correctly, or resolve the\n * harness-native dir directly via `@tangle-network/agent-app/skills-placement`.\n * Kept only for the pre-existing `registrySkills`/`userSkillMounts` callers;\n * do not add new call sites. */\nexport function skillMountPath(id: string): string {\n return `~/.claude/skills/${id}/SKILL.md`\n}\n\n/** Strip a glob/fs key down to a stable relative form: drop a leading `./`,\n * and for an absolute fs path keep only the tail from the last anchor segment\n * the pattern implies. We normalize on the trailing `<dir>/.../*.md` so the\n * Vite key (`./skills/x/SKILL.md`) and the fs key (`/abs/.../skills/x/SKILL.md`)\n * collapse to the same value. */\nfunction normalizeKey(key: string, anchor: string): string {\n const marker = `${anchor}/`\n const at = key.lastIndexOf(marker)\n if (at >= 0) return key.slice(at)\n return key.startsWith('./') ? key.slice(2) : key\n}\n\n/** Folder-slug for a `<anchor>/<slug>/SKILL.md` layout; falls back to the\n * normalized key (sans `<anchor>/` prefix, sans `.md`) for flat layouts. */\nfunction toCorpusId(normalizedKey: string, anchor: string): string {\n const nested = normalizedKey.match(new RegExp(`${anchor}/([^/]+)/SKILL\\\\.md$`))\n if (nested) return nested[1]!\n const flat = normalizedKey.match(new RegExp(`${anchor}/(.+)\\\\.md$`))\n if (flat) return flat[1]!\n return normalizedKey\n}\n\n/** Resolve Node builtins lazily. `process.getBuiltinModule` (Node 22+) is\n * absent in workerd, so this returns undefined there and the fs path is never\n * taken — Workers always reach the loader through the Vite glob map. A static\n * `import 'node:fs'` would break Vite SSR bundling in the consumer apps, so it\n * is deliberately avoided. */\nfunction nodeBuiltins():\n | { fs: typeof import('node:fs'); path: typeof import('node:path'); url: typeof import('node:url') }\n | undefined {\n const getBuiltin = (globalThis as { process?: { getBuiltinModule?: (id: string) => unknown } })\n .process?.getBuiltinModule\n if (typeof getBuiltin !== 'function') return undefined\n return {\n fs: getBuiltin('node:fs') as typeof import('node:fs'),\n path: getBuiltin('node:path') as typeof import('node:path'),\n url: getBuiltin('node:url') as typeof import('node:url'),\n }\n}\n\n/** Options for {@link loadMarkdownCorpus}. */\nexport interface LoadCorpusOptions {\n /** The anchor folder name that appears in both glob keys and fs paths\n * (`skills`, `doctrine`, `knowledge`). Used to normalize keys + derive ids. */\n anchor: string\n /** Vite glob-result map. When present and non-empty it is authoritative and\n * the fs path is skipped. Omit it (or pass an empty map) only outside Vite. */\n globModules?: GlobModules\n /** Absolute or `import.meta.url`-relative base dir the fs fallback walks when\n * `globModules` is empty. Required for the fs path to run; without it the fs\n * fallback returns no entries (Workers never need it). */\n fsBaseDir?: string\n /** Walk strategy for the fs fallback. `nested` finds `<dir>/<slug>/SKILL.md`\n * one level deep; `flat` recurses for every `*.md`. Default: `flat`. */\n fsLayout?: 'nested' | 'flat'\n /** Drop an entry by its normalized key after load. Covers the per-product\n * skip lists (corpus index/log files, scaffold templates, allow-lists). */\n skip?: (normalizedKey: string) => boolean\n}\n\n/** Outcome of {@link loadMarkdownCorpus}: the entries plus which path produced\n * them, so a caller can fail loud when both are empty rather than silently\n * mounting nothing. */\nexport interface CorpusLoadResult {\n source: 'vite' | 'fs' | 'empty'\n entries: CorpusEntry[]\n}\n\nfunction fsWalkFlat(\n builtins: NonNullable<ReturnType<typeof nodeBuiltins>>,\n root: string,\n): GlobModules {\n const { fs, path } = builtins\n const out: GlobModules = {}\n if (!fs.existsSync(root)) return out\n const walk = (dir: string) => {\n let entries: import('node:fs').Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name.startsWith('.')) continue\n const full = path.join(dir, entry.name)\n if (entry.isDirectory()) walk(full)\n else if (entry.isFile() && entry.name.endsWith('.md')) out[full] = fs.readFileSync(full, 'utf8')\n }\n }\n walk(root)\n return out\n}\n\nfunction fsWalkNested(\n builtins: NonNullable<ReturnType<typeof nodeBuiltins>>,\n root: string,\n): GlobModules {\n const { fs, path } = builtins\n const out: GlobModules = {}\n if (!fs.existsSync(root)) return out\n let entries: import('node:fs').Dirent[]\n try {\n entries = fs.readdirSync(root, { withFileTypes: true })\n } catch {\n return out\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const skillFile = path.join(root, entry.name, 'SKILL.md')\n if (!fs.existsSync(skillFile)) continue\n out[skillFile] = fs.readFileSync(skillFile, 'utf8')\n }\n return out\n}\n\n/** Resolve `fsBaseDir` against `import.meta.url` when relative-looking, so a\n * consumer can pass a bare folder name (`'skills'`) and have it land beside\n * the calling module. Absolute paths pass through. */\nfunction resolveFsBase(\n builtins: NonNullable<ReturnType<typeof nodeBuiltins>>,\n fsBaseDir: string,\n importMetaUrl?: string,\n): string {\n const { path, url } = builtins\n if (path.isAbsolute(fsBaseDir)) return fsBaseDir\n const here = importMetaUrl\n ? path.dirname(url.fileURLToPath(importMetaUrl))\n : process.cwd()\n return path.join(here, fsBaseDir)\n}\n\n/**\n * Load a markdown corpus, preferring a Vite glob-result map and falling back to\n * a Node fs walk. Selection is by non-empty glob result — never an env flag.\n * Entries are normalized, optionally skip-filtered, and sorted by id for\n * determinism. The `import.meta.glob` literal stays at the CONSUMER call site\n * (passed in as `globModules`); this loader never constructs a glob.\n */\nexport function loadMarkdownCorpus(\n options: LoadCorpusOptions,\n importMetaUrl?: string,\n): CorpusLoadResult {\n const { anchor, globModules, fsBaseDir, fsLayout = 'flat', skip } = options\n\n let modules: GlobModules\n let source: CorpusLoadResult['source']\n if (globModules && Object.keys(globModules).length > 0) {\n modules = globModules\n source = 'vite'\n } else {\n const builtins = nodeBuiltins()\n if (builtins && fsBaseDir) {\n const root = resolveFsBase(builtins, fsBaseDir, importMetaUrl)\n modules = fsLayout === 'nested' ? fsWalkNested(builtins, root) : fsWalkFlat(builtins, root)\n source = Object.keys(modules).length > 0 ? 'fs' : 'empty'\n } else {\n modules = {}\n source = 'empty'\n }\n }\n\n const entries: CorpusEntry[] = []\n for (const [rawKey, content] of Object.entries(modules)) {\n if (typeof content !== 'string') continue\n const key = normalizeKey(rawKey, anchor)\n if (skip && skip(key)) continue\n entries.push({ id: toCorpusId(key, anchor), key, content })\n }\n entries.sort((a, b) => a.id.localeCompare(b.id))\n return { source, entries }\n}\n\n/** Project corpus entries onto SDK file mounts at a relative path under\n * `<anchor>/`. Always-mounted: the corpus is the agent's baseline knowledge. */\nexport function corpusSkills(corpus: CorpusEntry[], anchor: string): AgentProfileFileMount[] {\n return corpus\n .map(\n (entry) =>\n ({\n path: `${anchor}/${entry.id}.md`,\n resource: inlineResource(`${anchor}-${entry.id}`, entry.content),\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/** Project the registry's free-tier (or `tier`-matched) entries onto SDK file\n * mounts at the harness skill-discovery path. Tier-gating is the registry's\n * only selection rule — paid skills are installed on demand, not at boot. */\nexport function registrySkills(\n registry: SkillEntry[],\n tier: string = 'free',\n): AgentProfileFileMount[] {\n return registry\n .filter((s) => s.tier === tier)\n .map(\n (s) =>\n ({\n path: skillMountPath(s.id),\n resource: inlineResource(s.id, s.skillMd),\n }) satisfies AgentProfileFileMount,\n )\n .sort((a, b) => a.path.localeCompare(b.path))\n}\n\n/** Inputs to {@link composeShellResources}. Each channel is optional so a\n * product mounts only the systems it has — corpus-only, registry-only, or\n * both — without conflating them. */\nexport interface ComposeShellResourcesInput {\n /** Corpus mounts (always-mounted baseline). Pass the result of\n * {@link corpusSkills}, or a hand-built mount list. */\n skills?: AgentProfileFileMount[]\n /** Knowledge-corpus mounts (a second always-mounted corpus, e.g. a domain\n * knowledge pack distinct from the skills corpus). */\n knowledge?: AgentProfileFileMount[]\n /** Evolvable / learned-guidance mounts (single-file corpora). */\n evolvable?: AgentProfileFileMount[]\n /** Registry mounts (tier-gated). Pass the result of {@link registrySkills}. */\n registry?: AgentProfileFileMount[]\n /** Final skip filter applied to the composed mount list by mount `path`. */\n predicate?: (mount: AgentProfileFileMount) => boolean\n}\n\n/**\n * Compose every mount channel into one `resources.files`-ready array. Corpus\n * channels come first (baseline), the tier-gated registry last (so a registry\n * entry can override a corpus entry that mounts at the same path). The result\n * is exactly `AgentProfileFileMount[]` — assign it straight into\n * `profile.resources.files` with no cast.\n */\nexport function composeShellResources(input: ComposeShellResourcesInput): AgentProfileFileMount[] {\n const { skills = [], knowledge = [], evolvable = [], registry = [], predicate } = input\n const composed = [...skills, ...knowledge, ...evolvable, ...registry]\n return predicate ? composed.filter(predicate) : composed\n}\n\n// ─── Adoptable skills: one frontmatter parser, two delivery modes ─────────────\n\nexport { parseSkillFrontmatter, type ParsedSkill, type SkillFrontmatter } from './frontmatter'\n\n/** Build a {@link SkillEntry} from a raw `SKILL.md` body. `id` comes from\n * frontmatter, falling back to `fallbackId` (typically the corpus entry's\n * slug/filename); neither present throws. `name` defaults to `id`,\n * `description` to `''`, `tier` to `'free'`. `skillMd` is always the\n * untouched `raw` input — the full file, frontmatter included, is what a\n * `mounted` delivery writes to disk and what `renderInlineSkills` strips per\n * render. */\nexport function skillEntryFromMarkdown(raw: string, fallbackId?: string): SkillEntry {\n const { frontmatter } = parseSkillFrontmatter(raw)\n const id = frontmatter.id ?? fallbackId\n if (!id) {\n throw new Error(\n 'skillEntryFromMarkdown: no \"id\" in frontmatter and no fallbackId supplied — cannot build a SkillEntry',\n )\n }\n const entry: SkillEntry = {\n id,\n name: frontmatter.name ?? id,\n description: frontmatter.description ?? '',\n tier: frontmatter.tier ?? 'free',\n skillMd: raw,\n }\n if (frontmatter.author) entry.author = frontmatter.author\n if (frontmatter.source) entry.source = frontmatter.source\n if (frontmatter.category) entry.category = frontmatter.category\n if (frontmatter.tags) entry.tags = frontmatter.tags\n return entry\n}\n\n/** Map a loaded corpus (see {@link loadMarkdownCorpus}) onto `SkillEntry`s,\n * using each entry's `id` as the fallback when its `SKILL.md` carries no\n * frontmatter `id` of its own. */\nexport function parseCorpusSkills(corpus: CorpusEntry[]): SkillEntry[] {\n return corpus.map((entry) => skillEntryFromMarkdown(entry.content, entry.id))\n}\n\n/** Project skills onto the typed `resources.skills` channel\n * (`AgentProfileResourceRef[]`), tier-filtered (same `s.tier === tier`\n * semantics as {@link registrySkills}) when `opts.tier` is given, sorted by\n * id for determinism. `ref.name` MUST be the skill id: the platform\n * materializer writes each ref to `${skillDir}/${name}/SKILL.md`. */\nexport function skillRefs(\n skills: SkillEntry[],\n opts: { tier?: string } = {},\n): AgentProfileResourceRef[] {\n const filtered = opts.tier ? skills.filter((s) => s.tier === opts.tier) : skills\n return [...filtered]\n .sort((a, b) => a.id.localeCompare(b.id))\n .map((s) => inlineResource(s.id, s.skillMd))\n}\n\n/** Strip the frontmatter block from a skill's `skillMd` and render it as a\n * prompt subsection. The default `body` renderer {@link renderInlineSkills}\n * passes to itself. */\nfunction renderInlineSkillBody(skill: SkillEntry): string {\n const { body } = parseSkillFrontmatter(skill.skillMd)\n return `### ${skill.name}\\n\\n${body.trim()}`\n}\n\n/** Inputs to {@link renderInlineSkills}. */\nexport interface RenderInlineSkillsInput {\n skills: SkillEntry[]\n /** Section heading. Default `'## Skills'`. */\n heading?: string\n /** Tier filter — same semantics as {@link skillRefs}. */\n tier?: string\n /** Per-skill body renderer. Default strips frontmatter and emits\n * `### <name>\\n\\n<body>`. */\n body?: (skill: SkillEntry) => string\n}\n\n/**\n * Render every (tier-filtered) skill's full body inline into the prompt — the\n * `inline` delivery mode. Returns `''` when no skill survives the filter, else\n * a section starting `\\n\\n<heading>\\n\\n` with each skill's body joined by\n * `\\n\\n` — the same \"already carries its own leading `\\n\\n`\" shape\n * `assembleSystemPrompt` expects from every section it concatenates.\n */\nexport function renderInlineSkills(input: RenderInlineSkillsInput): string {\n const heading = input.heading ?? '## Skills'\n const filtered = input.tier ? input.skills.filter((s) => s.tier === input.tier) : input.skills\n if (filtered.length === 0) return ''\n const renderBody = input.body ?? renderInlineSkillBody\n return `\\n\\n${heading}\\n\\n${filtered.map(renderBody).join('\\n\\n')}`\n}\n\n/** Inputs to {@link renderSkillIndex}. */\nexport interface RenderSkillIndexInput {\n skills: SkillEntry[]\n /** cwd-relative directory the skills are mounted under (e.g.\n * `.opencode/skills`) — named in each index line so the agent knows where\n * to read the full `SKILL.md`. */\n skillDir: string\n /** Section heading. Default `'## Skills'`. */\n heading?: string\n /** Tier filter — same semantics as {@link skillRefs}. */\n tier?: string\n}\n\n/**\n * Render a one-line-per-skill INDEX (name, description, and the path to read\n * the full body) — the `mounted` delivery mode's prompt section, paired with\n * {@link skillRefs} putting the actual files on `resources.skills`. Same\n * empty/section shape as {@link renderInlineSkills}.\n */\nexport function renderSkillIndex(input: RenderSkillIndexInput): string {\n const heading = input.heading ?? '## Skills'\n const filtered = input.tier ? input.skills.filter((s) => s.tier === input.tier) : input.skills\n if (filtered.length === 0) return ''\n const lines = filtered.map(\n (s) => `- ${s.name}: ${s.description} (read ${input.skillDir}/${s.id}/SKILL.md)`,\n )\n return `\\n\\n${heading}\\n\\n${lines.join('\\n')}`\n}\n\n/** How a skill reaches the agent: `inline` renders its full body into the\n * system prompt; `mounted` puts it on the typed `resources.skills` channel\n * and renders only an index line. */\nexport type SkillDeliveryMode = 'inline' | 'mounted'\n\n/** The output of {@link composeSkills}: the refs to attach to\n * `resources.skills` (empty for `inline`) and the prompt section to fold into\n * the system prompt (already carries its own leading `\\n\\n`, or `''`).\n * `inlineIds`/`mountedIds` record which (tier-filtered) skills were delivered\n * in each mode so {@link mergeComposedSkills} can enforce the modes stay\n * disjoint when batches are combined. */\nexport interface ComposedSkills {\n refs: AgentProfileResourceRef[]\n promptSection: string\n inlineIds: string[]\n mountedIds: string[]\n}\n\n/** Inputs to {@link composeSkills}. */\nexport interface ComposeSkillsInput {\n skills: SkillEntry[]\n mode: SkillDeliveryMode\n tier?: string\n heading?: string\n /** REQUIRED (non-null) for `mode: 'mounted'` — the cwd-relative skill dir\n * the harness reads. Resolve it with `@tangle-network/agent-app/skills-placement`\n * rather than hardcoding it; omitted or `null` throws. */\n skillDir?: string | null\n}\n\n/**\n * Build the {@link ComposedSkills} for one delivery mode. `inline` never\n * touches `resources.skills` — `refs` is always `[]`. `mounted` requires a\n * non-null `skillDir` (the harness must have a native skill-discovery\n * directory); a null/absent one throws rather than silently falling back, so\n * the caller resolves the fallback deliberately (see\n * `@tangle-network/agent-app/skills-placement`'s `composeSkillsForHarness`,\n * whose own default is to throw rather than choose `inline` for you — inline\n * is a narrow, explicitly opted-into fallback, not a co-equal mode).\n */\nexport function composeSkills(input: ComposeSkillsInput): ComposedSkills {\n const { skills, mode, tier, heading } = input\n const deliveredIds = (tier ? skills.filter((s) => s.tier === tier) : skills).map((s) => s.id)\n if (mode === 'inline') {\n return {\n refs: [],\n promptSection: renderInlineSkills({ skills, tier, heading }),\n inlineIds: deliveredIds,\n mountedIds: [],\n }\n }\n if (!input.skillDir) {\n throw new Error(\n 'composeSkills: mode \"mounted\" requires a non-null skillDir — this harness cannot receive ' +\n 'skill files at a cwd path; pass mode \"inline\" instead',\n )\n }\n return {\n refs: skillRefs(skills, { tier }),\n promptSection: renderSkillIndex({ skills, skillDir: input.skillDir, tier, heading }),\n inlineIds: [],\n mountedIds: deliveredIds,\n }\n}\n\n/** Throw when the same skill id is delivered both `inline` and `mounted` —\n * the agent would see it twice (once in the prompt body, once as a mounted\n * file it's told to go read), doubling prompt bytes and inviting drift\n * between the two copies. Lists every offending id in the message. */\nexport function assertSkillDeliveryDisjoint(\n inlineIds: Iterable<string>,\n mountedIds: Iterable<string>,\n): void {\n const inline = new Set(inlineIds)\n const overlap = [...new Set(mountedIds)].filter((id) => inline.has(id))\n if (overlap.length > 0) {\n throw new Error(\n `assertSkillDeliveryDisjoint: skill id(s) delivered both inline and mounted: ${overlap.join(', ')}`,\n )\n }\n}\n\n/**\n * Combine multiple {@link ComposedSkills} batches (e.g. a built-in corpus and\n * an installed catalog) into one, concatenating refs and prompt sections in\n * batch order. This is the seam where inline and mounted deliveries can first\n * collide, so it applies {@link assertSkillDeliveryDisjoint} — a skill id\n * delivered inline by one batch and mounted by another throws instead of\n * reaching the agent twice. Merge through this rather than spreading batch\n * fields by hand.\n */\nexport function mergeComposedSkills(batches: ComposedSkills[]): ComposedSkills {\n const merged: ComposedSkills = {\n refs: batches.flatMap((b) => b.refs),\n promptSection: batches.map((b) => b.promptSection).join(''),\n inlineIds: batches.flatMap((b) => b.inlineIds),\n mountedIds: batches.flatMap((b) => b.mountedIds),\n }\n assertSkillDeliveryDisjoint(merged.inlineIds, merged.mountedIds)\n return merged\n}\n\n// ── The cross-agent skill marketplace ─────────────────────────────────────\n//\n// A shared catalog contract on top of the self-mount verb (see\n// `./agent-profiles`): entries, validation, the local∪shared merge with\n// namespacing and local precedence, a discovery line, and read-only store\n// adapters. Mounting, caps and baseline protection stay in the verb.\nexport {\n MAX_MARKETPLACE_ENTRIES,\n createInMemoryMarketplaceStore,\n createStaticMarketplaceStore,\n createKVMarketplaceStore,\n marketplaceDiscoveryLine,\n marketplaceRowsFor,\n mergeSkillCatalogs,\n namespacedSkillId,\n parseNamespacedSkillId,\n validateMarketplaceCatalog,\n validateMarketplaceSkill,\n type MarketplaceSkill,\n type MarketplaceStore,\n type MarketplaceRow,\n type CatalogSkillEntry,\n} from './marketplace'\n"],"mappings":"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","names":[]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canTransitionInteractionStatus,
|
|
3
|
+
persistedPartToInteraction
|
|
4
|
+
} from "./chunk-6VQCFJWP.js";
|
|
1
5
|
import {
|
|
2
6
|
canTransitionPlanStatus,
|
|
3
7
|
persistedPartToPlan,
|
|
4
8
|
planPartKey,
|
|
5
9
|
planToPersistedPart
|
|
6
10
|
} from "./chunk-LQYIBL3G.js";
|
|
7
|
-
import {
|
|
8
|
-
canTransitionInteractionStatus,
|
|
9
|
-
persistedPartToInteraction
|
|
10
|
-
} from "./chunk-6VQCFJWP.js";
|
|
11
11
|
|
|
12
12
|
// src/stream/stream-normalizer.ts
|
|
13
13
|
function asRecord(value) {
|
|
@@ -406,4 +406,4 @@ export {
|
|
|
406
406
|
terminalizeDanglingAssistantToolUpdates,
|
|
407
407
|
encodeEvent
|
|
408
408
|
};
|
|
409
|
-
//# sourceMappingURL=chunk-
|
|
409
|
+
//# sourceMappingURL=chunk-ZGUXI5SI.js.map
|
|
@@ -84,15 +84,15 @@ import {
|
|
|
84
84
|
setPagePropsCommand,
|
|
85
85
|
ungroupElementCommand
|
|
86
86
|
} from "../chunk-6NYVAURR.js";
|
|
87
|
-
import {
|
|
88
|
-
DesignCanvasChromeLazy,
|
|
89
|
-
DesignCanvasLazy
|
|
90
|
-
} from "../chunk-IIE3K2Y5.js";
|
|
91
87
|
import {
|
|
92
88
|
assertSceneMediaSrc,
|
|
93
89
|
bleedAwareExportBounds,
|
|
94
90
|
scaleForPreset
|
|
95
91
|
} from "../chunk-OHWQUKBP.js";
|
|
92
|
+
import {
|
|
93
|
+
DesignCanvasChromeLazy,
|
|
94
|
+
DesignCanvasLazy
|
|
95
|
+
} from "../chunk-EOPFHXEA.js";
|
|
96
96
|
import "../chunk-UTAIRVOK.js";
|
|
97
97
|
import "../chunk-RN4WPTBH.js";
|
|
98
98
|
import "../chunk-3NCPXNBZ.js";
|