teamcopilot 0.0.3 → 0.1.1

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.
Files changed (40) hide show
  1. package/dist/frontend/assets/{cssMode-BBJnYJ3k.js → cssMode-DPbQk08M.js} +1 -1
  2. package/dist/frontend/assets/{freemarker2-D_OsaDj3.js → freemarker2-DFE4Z2hp.js} +1 -1
  3. package/dist/frontend/assets/{handlebars-OZHda73C.js → handlebars-Dw_PK21Y.js} +1 -1
  4. package/dist/frontend/assets/{html-CdGHj-sb.js → html-DxZRNRpl.js} +1 -1
  5. package/dist/frontend/assets/{htmlMode-DmOnHDtb.js → htmlMode-iGgmnmtJ.js} +1 -1
  6. package/dist/frontend/assets/{index-DNOGp3R4.js → index-CphuwuEr.js} +4 -4
  7. package/dist/frontend/assets/index-Ds8n3J4W.css +1 -0
  8. package/dist/frontend/assets/{javascript--es-Ez5N.js → javascript-Bw_u6aiq.js} +1 -1
  9. package/dist/frontend/assets/{jsonMode-C3d51Wej.js → jsonMode-iBiD9w9m.js} +1 -1
  10. package/dist/frontend/assets/{liquid-D2JYL9PO.js → liquid-nnkE2sbw.js} +1 -1
  11. package/dist/frontend/assets/{mdx-Ch04DugU.js → mdx-CHi7ETpD.js} +1 -1
  12. package/dist/frontend/assets/{python-D-iNySlZ.js → python--OTnmJv2.js} +1 -1
  13. package/dist/frontend/assets/{razor-Bq45_Fpy.js → razor-C6OgnQD9.js} +1 -1
  14. package/dist/frontend/assets/{tsMode-CC8kGhhz.js → tsMode-DzsKqqZD.js} +1 -1
  15. package/dist/frontend/assets/{typescript-CtgyIznM.js → typescript-BMZ0H3W7.js} +1 -1
  16. package/dist/frontend/assets/{xml-D5joow3e.js → xml-HTDEz_LP.js} +1 -1
  17. package/dist/frontend/assets/{yaml-BsRI11pd.js → yaml-CVa5ypgE.js} +1 -1
  18. package/dist/frontend/index.html +2 -2
  19. package/dist/opencode-server.js +3 -0
  20. package/dist/utils/approval-snapshot-common.js +29 -16
  21. package/dist/utils/resource-files.js +22 -21
  22. package/dist/utils/skill.js +39 -9
  23. package/dist/utils/workspace-sync.js +5 -7
  24. package/dist/workspace_files/.opencode/opencode.json +4 -1
  25. package/dist/workspace_files/.opencode/package.json +2 -1
  26. package/dist/workspace_files/.opencode/plugins/createSkill.ts +38 -7
  27. package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +34 -3
  28. package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +33 -2
  29. package/dist/workspace_files/.opencode/plugins/findSkill.ts +36 -5
  30. package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +34 -3
  31. package/dist/workspace_files/.opencode/plugins/honeytoken-protection.ts +1 -1
  32. package/dist/workspace_files/.opencode/plugins/listAvailableSkills.ts +33 -2
  33. package/dist/workspace_files/.opencode/plugins/listAvailableWorkflows.ts +33 -2
  34. package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +34 -3
  35. package/dist/workspace_files/.opencode/plugins/skill-command-guard.ts +138 -0
  36. package/dist/workspace_files/AGENTS.md +3 -3
  37. package/dist/workspace_files/package-lock.json +2 -1
  38. package/dist/workspace_files/package.json +3 -1
  39. package/package.json +1 -1
  40. package/dist/frontend/assets/index-CnBE4Efj.css +0 -1
@@ -11,8 +11,22 @@ const redact_1 = require("./redact");
11
11
  function createResourceFileManager(options) {
12
12
  const { getResourcePath, resourceLabel, editorLabel } = options;
13
13
  const rootLabel = `${resourceLabel} root`;
14
- const symlinkRootError = `${editorLabel} editor does not support symlinked ${resourceLabel} directories`;
15
14
  const symlinkError = `${editorLabel} editor does not support symlinks`;
15
+ function getResolvedRootPaths(slug) {
16
+ const configuredRoot = getResourcePath(slug);
17
+ try {
18
+ return {
19
+ configuredRoot,
20
+ realRoot: fs_1.default.realpathSync(configuredRoot),
21
+ };
22
+ }
23
+ catch {
24
+ throw {
25
+ status: 404,
26
+ message: `${rootLabel} not found`
27
+ };
28
+ }
29
+ }
16
30
  function toEtag(buffer) {
17
31
  return crypto_1.default.createHash("sha256").update(buffer).digest("hex");
18
32
  }
@@ -64,21 +78,8 @@ function createResourceFileManager(options) {
64
78
  }
65
79
  return absolute;
66
80
  }
67
- function assertRootNotSymlink(slug) {
68
- const resourceRoot = getResourcePath(slug);
69
- if (!fs_1.default.existsSync(resourceRoot)) {
70
- return;
71
- }
72
- const rootLstat = fs_1.default.lstatSync(resourceRoot);
73
- if (rootLstat.isSymbolicLink()) {
74
- throw {
75
- status: 400,
76
- message: symlinkRootError
77
- };
78
- }
79
- }
80
81
  function assertRealPathWithinRoot(slug, absolutePath) {
81
- const realRoot = fs_1.default.realpathSync(getResourcePath(slug));
82
+ const { realRoot } = getResolvedRootPaths(slug);
82
83
  const realTarget = fs_1.default.realpathSync(absolutePath);
83
84
  if (realTarget !== realRoot && !realTarget.startsWith(`${realRoot}${path_1.default.sep}`)) {
84
85
  throw {
@@ -88,9 +89,14 @@ function createResourceFileManager(options) {
88
89
  }
89
90
  }
90
91
  function assertNoSymlinkInAncestors(slug, absolutePath) {
91
- const root = path_1.default.resolve(getResourcePath(slug));
92
+ const { configuredRoot: rawConfiguredRoot, realRoot: rawRealRoot } = getResolvedRootPaths(slug);
93
+ const configuredRoot = path_1.default.resolve(rawConfiguredRoot);
94
+ const realRoot = path_1.default.resolve(rawRealRoot);
92
95
  let current = path_1.default.resolve(absolutePath);
93
96
  while (true) {
97
+ if (current === configuredRoot || current === realRoot) {
98
+ return;
99
+ }
94
100
  const lstat = fs_1.default.lstatSync(current);
95
101
  if (lstat.isSymbolicLink()) {
96
102
  throw {
@@ -98,9 +104,6 @@ function createResourceFileManager(options) {
98
104
  message: symlinkError
99
105
  };
100
106
  }
101
- if (current === root) {
102
- return;
103
- }
104
107
  const parent = path_1.default.dirname(current);
105
108
  if (parent === current) {
106
109
  throw {
@@ -112,12 +115,10 @@ function createResourceFileManager(options) {
112
115
  }
113
116
  }
114
117
  function assertExistingPathIsSafe(slug, absolutePath) {
115
- assertRootNotSymlink(slug);
116
118
  assertRealPathWithinRoot(slug, absolutePath);
117
119
  assertNoSymlinkInAncestors(slug, absolutePath);
118
120
  }
119
121
  function assertParentDirectoryIsSafeForCreate(slug, parentAbsolutePath) {
120
- assertRootNotSymlink(slug);
121
122
  assertRealPathWithinRoot(slug, parentAbsolutePath);
122
123
  assertNoSymlinkInAncestors(slug, parentAbsolutePath);
123
124
  }
@@ -15,7 +15,7 @@ const client_1 = __importDefault(require("../prisma/client"));
15
15
  const workspace_sync_1 = require("./workspace-sync");
16
16
  const permission_common_1 = require("./permission-common");
17
17
  function getSkillsRootPath() {
18
- return path_1.default.join((0, workspace_sync_1.getWorkspaceDirFromEnv)(), "custom-skills");
18
+ return path_1.default.join((0, workspace_sync_1.getWorkspaceDirFromEnv)(), ".agents", "skills");
19
19
  }
20
20
  function getSkillPath(slug) {
21
21
  return path_1.default.join(getSkillsRootPath(), slug);
@@ -66,9 +66,19 @@ function listSkillSlugs() {
66
66
  const entries = fs_1.default.readdirSync(skillsDir, { withFileTypes: true });
67
67
  const slugs = [];
68
68
  for (const entry of entries) {
69
- if (!entry.isDirectory())
70
- continue;
71
- const canonicalManifestPath = path_1.default.join(skillsDir, entry.name, "SKILL.md");
69
+ const skillEntryPath = path_1.default.join(skillsDir, entry.name);
70
+ if (!entry.isDirectory()) {
71
+ if (!entry.isSymbolicLink())
72
+ continue;
73
+ try {
74
+ if (!fs_1.default.statSync(skillEntryPath).isDirectory())
75
+ continue;
76
+ }
77
+ catch {
78
+ continue;
79
+ }
80
+ }
81
+ const canonicalManifestPath = path_1.default.join(skillEntryPath, "SKILL.md");
72
82
  if (fs_1.default.existsSync(canonicalManifestPath)) {
73
83
  slugs.push(entry.name);
74
84
  }
@@ -76,12 +86,32 @@ function listSkillSlugs() {
76
86
  return slugs;
77
87
  }
78
88
  function extractFrontmatterValue(frontmatter, key) {
79
- const pattern = new RegExp(`^${key}\\s*:\\s*(.+)$`, "m");
80
- const match = frontmatter.match(pattern);
81
- if (!match) {
82
- return null;
89
+ const lines = frontmatter.split("\n");
90
+ for (let index = 0; index < lines.length; index += 1) {
91
+ const line = lines[index];
92
+ const match = line.match(new RegExp(`^${key}\\s*:\\s*(.*)$`));
93
+ if (!match) {
94
+ continue;
95
+ }
96
+ const rawValue = match[1]?.trim() ?? "";
97
+ if (rawValue === "|" || rawValue === ">") {
98
+ const blockLines = [];
99
+ for (let blockIndex = index + 1; blockIndex < lines.length; blockIndex += 1) {
100
+ const blockLine = lines[blockIndex] ?? "";
101
+ if (blockLine.length === 0) {
102
+ blockLines.push("");
103
+ continue;
104
+ }
105
+ if (!/^\s+/.test(blockLine)) {
106
+ break;
107
+ }
108
+ blockLines.push(blockLine.replace(/^\s+/, ""));
109
+ }
110
+ return blockLines.join(rawValue === ">" ? " " : "\n").trim();
111
+ }
112
+ return rawValue.replace(/^["']|["']$/g, "");
83
113
  }
84
- return match[1]?.trim().replace(/^["']|["']$/g, "") ?? null;
114
+ return null;
85
115
  }
86
116
  function readSkillManifest(slug) {
87
117
  const skillManifestPath = getSkillManifestPath(slug);
@@ -49,13 +49,11 @@ function ensureWorkspaceDatabaseDirectory() {
49
49
  function normalizeRelativePath(relativePath) {
50
50
  return relativePath.split(path_1.default.sep).join("/");
51
51
  }
52
- const MANAGED_WORKSPACE_DIRECTORIES = new Set([
53
- "workflows",
54
- "custom-skills",
55
- ]);
56
52
  function shouldSkipManagedDirectoryContent(relativePath) {
57
- const [topLevelSegment] = relativePath.split("/");
58
- if (MANAGED_WORKSPACE_DIRECTORIES.has(topLevelSegment)) {
53
+ if (relativePath === "workflows" || relativePath.startsWith("workflows/")) {
54
+ return true;
55
+ }
56
+ if (relativePath === ".agents/skills" || relativePath.startsWith(".agents/skills/")) {
59
57
  return true;
60
58
  }
61
59
  if (relativePath === ".opencode/xdg-data" || relativePath.startsWith(".opencode/xdg-data/")) {
@@ -217,7 +215,7 @@ async function initializeWorkspaceDirectory() {
217
215
  fs_1.default.mkdirSync(workspaceDir, { recursive: true });
218
216
  const workflowsDir = path_1.default.join(workspaceDir, "workflows");
219
217
  fs_1.default.mkdirSync(workflowsDir, { recursive: true });
220
- const skillsDir = path_1.default.join(workspaceDir, "custom-skills");
218
+ const skillsDir = path_1.default.join(workspaceDir, ".agents", "skills");
221
219
  fs_1.default.mkdirSync(skillsDir, { recursive: true });
222
220
  const honeytokenValue = `DO_NOT_EXPOSE:${HONEYTOKEN_UUID}\n`;
223
221
  fs_1.default.writeFileSync(path_1.default.join(workflowsDir, HONEYTOKEN_FILE_NAME), honeytokenValue, "utf-8");
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
+ "tools": {
4
+ "skill": false
5
+ },
3
6
  "mcp": {
4
7
  "brave-search": {
5
8
  "type": "local",
@@ -14,4 +17,4 @@
14
17
  }
15
18
  }
16
19
  }
17
- }
20
+ }
@@ -10,5 +10,6 @@
10
10
  "devDependencies": {
11
11
  "@types/node": "^20.0.0",
12
12
  "typescript": "^5.0.0"
13
- }
13
+ },
14
+ "scripts": {}
14
15
  }
@@ -24,6 +24,14 @@ interface PermissionResponse {
24
24
  approved: boolean
25
25
  }
26
26
 
27
+ interface SessionLookupResponse {
28
+ error?: unknown
29
+ data?: {
30
+ id?: string
31
+ parentID?: string
32
+ }
33
+ }
34
+
27
35
  async function readErrorMessageFromResponse(
28
36
  response: Response,
29
37
  fallbackMessage: string
@@ -191,7 +199,29 @@ function buildSkillMarkdown(
191
199
  return `---\nname: ${JSON.stringify(slug)}\ndescription: ${JSON.stringify(description)}\n---\n\n${body}\n`
192
200
  }
193
201
 
194
- export const CreateSkillPlugin: Plugin = async (_ctx) => {
202
+ export const CreateSkillPlugin: Plugin = async ({ client }) => {
203
+ async function resolveRootSessionID(sessionID: string): Promise<string> {
204
+ let currentSessionID = sessionID
205
+
206
+ while (true) {
207
+ const response = (await client.session.get({
208
+ path: {
209
+ id: currentSessionID,
210
+ },
211
+ })) as SessionLookupResponse
212
+ if (response.error) {
213
+ throw new Error(`Failed to resolve root session for ${currentSessionID}`)
214
+ }
215
+
216
+ const parentID = response.data?.parentID
217
+ if (!parentID) {
218
+ return currentSessionID
219
+ }
220
+
221
+ currentSessionID = parentID
222
+ }
223
+ }
224
+
195
225
  return {
196
226
  tool: {
197
227
  createSkill: tool({
@@ -210,6 +240,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
210
240
  },
211
241
  async execute(args, context) {
212
242
  const { directory, sessionID } = context
243
+ const authSessionID = await resolveRootSessionID(sessionID)
213
244
  const slug = args.slug.trim()
214
245
  const description = args.description.trim()
215
246
  const content = args.content.trim()
@@ -238,7 +269,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
238
269
  throw new Error("Could not determine call id from tool context.")
239
270
  }
240
271
 
241
- const skillsDir = path.join(directory, "custom-skills")
272
+ const skillsDir = path.join(directory, ".agents", "skills")
242
273
  const skillDir = path.join(skillsDir, slug)
243
274
 
244
275
  if (!isPathInside(skillDir, skillsDir)) {
@@ -250,7 +281,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
250
281
  }
251
282
 
252
283
  await requestCreationPermission(
253
- sessionID,
284
+ authSessionID,
254
285
  messageId,
255
286
  callId
256
287
  )
@@ -259,7 +290,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
259
290
  method: "POST",
260
291
  headers: {
261
292
  "Content-Type": "application/json",
262
- Authorization: `Bearer ${sessionID}`,
293
+ Authorization: `Bearer ${authSessionID}`,
263
294
  },
264
295
  body: JSON.stringify({
265
296
  name: slug,
@@ -278,7 +309,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
278
309
  `${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}/files/content?path=${encodeURIComponent("SKILL.md")}`,
279
310
  {
280
311
  headers: {
281
- Authorization: `Bearer ${sessionID}`,
312
+ Authorization: `Bearer ${authSessionID}`,
282
313
  },
283
314
  }
284
315
  )
@@ -302,7 +333,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
302
333
  method: "PUT",
303
334
  headers: {
304
335
  "Content-Type": "application/json",
305
- Authorization: `Bearer ${sessionID}`,
336
+ Authorization: `Bearer ${authSessionID}`,
306
337
  },
307
338
  body: JSON.stringify({
308
339
  path: "SKILL.md",
@@ -324,7 +355,7 @@ export const CreateSkillPlugin: Plugin = async (_ctx) => {
324
355
  skill: {
325
356
  slug,
326
357
  description,
327
- file_path: `custom-skills/${slug}/SKILL.md`,
358
+ file_path: `.agents/skills/${slug}/SKILL.md`,
328
359
  },
329
360
  },
330
361
  null,
@@ -37,6 +37,14 @@ interface PermissionResponse {
37
37
  approved: boolean
38
38
  }
39
39
 
40
+ interface SessionLookupResponse {
41
+ error?: unknown
42
+ data?: {
43
+ id?: string
44
+ parentID?: string
45
+ }
46
+ }
47
+
40
48
  // ============================================================================
41
49
  // Constants
42
50
  // ============================================================================
@@ -195,7 +203,29 @@ async function requestWorkflowPermission(
195
203
  // Plugin
196
204
  // ============================================================================
197
205
 
198
- export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
206
+ export const CreateWorkflowPlugin: Plugin = async ({ client }) => {
207
+ async function resolveRootSessionID(sessionID: string): Promise<string> {
208
+ let currentSessionID = sessionID
209
+
210
+ while (true) {
211
+ const response = (await client.session.get({
212
+ path: {
213
+ id: currentSessionID,
214
+ },
215
+ })) as SessionLookupResponse
216
+ if (response.error) {
217
+ throw new Error(`Failed to resolve root session for ${currentSessionID}`)
218
+ }
219
+
220
+ const parentID = response.data?.parentID
221
+ if (!parentID) {
222
+ return currentSessionID
223
+ }
224
+
225
+ currentSessionID = parentID
226
+ }
227
+ }
228
+
199
229
  return {
200
230
  tool: {
201
231
  createWorkflow: tool({
@@ -237,6 +267,7 @@ export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
237
267
  },
238
268
  async execute(args, context) {
239
269
  const { directory, sessionID } = context
270
+ const authSessionID = await resolveRootSessionID(sessionID)
240
271
  const { slug, intent_summary, inputs = {}, timeout_seconds = 300 } = args
241
272
  const messageId = extractMessageId(context)
242
273
  const callId = extractCallId(context)
@@ -278,7 +309,7 @@ export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
278
309
 
279
310
  // Request permission after basic validation and existence checks pass.
280
311
  await requestWorkflowPermission(
281
- sessionID,
312
+ authSessionID,
282
313
  messageId,
283
314
  callId
284
315
  )
@@ -316,7 +347,7 @@ export const CreateWorkflowPlugin: Plugin = async (_ctx) => {
316
347
  method: "POST",
317
348
  headers: {
318
349
  "Content-Type": "application/json",
319
- Authorization: `Bearer ${sessionID}`,
350
+ Authorization: `Bearer ${authSessionID}`,
320
351
  },
321
352
  }
322
353
  )
@@ -26,6 +26,14 @@ interface WorkflowSummary {
26
26
  intent_summary: string
27
27
  }
28
28
 
29
+ interface SessionLookupResponse {
30
+ error?: unknown
31
+ data?: {
32
+ id?: string
33
+ parentID?: string
34
+ }
35
+ }
36
+
29
37
  async function readErrorMessageFromResponse(
30
38
  response: Response,
31
39
  fallbackMessage: string
@@ -85,7 +93,29 @@ function cosineSimilarity(vecA: number[], vecB: number[]): number {
85
93
  // Plugin
86
94
  // ============================================================================
87
95
 
88
- export const FindSimilarWorkflowPlugin: Plugin = async (_ctx) => {
96
+ export const FindSimilarWorkflowPlugin: Plugin = async ({ client }) => {
97
+ async function resolveRootSessionID(sessionID: string): Promise<string> {
98
+ let currentSessionID = sessionID
99
+
100
+ while (true) {
101
+ const response = (await client.session.get({
102
+ path: {
103
+ id: currentSessionID,
104
+ },
105
+ })) as SessionLookupResponse
106
+ if (response.error) {
107
+ throw new Error(`Failed to resolve root session for ${currentSessionID}`)
108
+ }
109
+
110
+ const parentID = response.data?.parentID
111
+ if (!parentID) {
112
+ return currentSessionID
113
+ }
114
+
115
+ currentSessionID = parentID
116
+ }
117
+ }
118
+
89
119
  return {
90
120
  tool: {
91
121
  findSimilarWorkflow: tool({
@@ -105,11 +135,12 @@ export const FindSimilarWorkflowPlugin: Plugin = async (_ctx) => {
105
135
  },
106
136
  async execute(args, context) {
107
137
  const { sessionID } = context
138
+ const authSessionID = await resolveRootSessionID(sessionID)
108
139
  const { description, limit = 5 } = args
109
140
 
110
141
  const workflowsResponse = await fetch(`${getApiBaseUrl()}/api/workflows`, {
111
142
  headers: {
112
- Authorization: `Bearer ${sessionID}`,
143
+ Authorization: `Bearer ${authSessionID}`,
113
144
  },
114
145
  })
115
146
 
@@ -31,6 +31,14 @@ interface SkillMatch {
31
31
  similarity: number
32
32
  }
33
33
 
34
+ interface SessionLookupResponse {
35
+ error?: unknown
36
+ data?: {
37
+ id?: string
38
+ parentID?: string
39
+ }
40
+ }
41
+
34
42
  async function readErrorMessageFromResponse(
35
43
  response: Response,
36
44
  fallbackMessage: string
@@ -90,14 +98,14 @@ function cosineSimilarity(vecA: number[], vecB: number[]): number {
90
98
  }
91
99
 
92
100
  async function readSkillMarkdown(
93
- sessionID: string,
101
+ authSessionID: string,
94
102
  slug: string
95
103
  ): Promise<string> {
96
104
  const response = await fetch(
97
105
  `${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}/files/content?path=${encodeURIComponent("SKILL.md")}`,
98
106
  {
99
107
  headers: {
100
- Authorization: `Bearer ${sessionID}`,
108
+ Authorization: `Bearer ${authSessionID}`,
101
109
  },
102
110
  }
103
111
  )
@@ -118,7 +126,29 @@ async function readSkillMarkdown(
118
126
  return payload.content ?? ""
119
127
  }
120
128
 
121
- export const FindSkillPlugin: Plugin = async (_ctx) => {
129
+ export const FindSkillPlugin: Plugin = async ({ client, directory }) => {
130
+ async function resolveRootSessionID(sessionID: string): Promise<string> {
131
+ let currentSessionID = sessionID
132
+
133
+ while (true) {
134
+ const response = (await client.session.get({
135
+ path: {
136
+ id: currentSessionID,
137
+ },
138
+ })) as SessionLookupResponse
139
+ if (response.error) {
140
+ throw new Error(`Failed to resolve root session for ${currentSessionID}`)
141
+ }
142
+
143
+ const parentID = response.data?.parentID
144
+ if (!parentID) {
145
+ return currentSessionID
146
+ }
147
+
148
+ currentSessionID = parentID
149
+ }
150
+ }
151
+
122
152
  return {
123
153
  tool: {
124
154
  findSkill: tool({
@@ -143,9 +173,10 @@ export const FindSkillPlugin: Plugin = async (_ctx) => {
143
173
  throw new Error("description is required")
144
174
  }
145
175
 
176
+ const authSessionID = await resolveRootSessionID(sessionID)
146
177
  const skillsResponse = await fetch(`${getApiBaseUrl()}/api/skills`, {
147
178
  headers: {
148
- Authorization: `Bearer ${sessionID}`,
179
+ Authorization: `Bearer ${authSessionID}`,
149
180
  },
150
181
  })
151
182
 
@@ -180,7 +211,7 @@ export const FindSkillPlugin: Plugin = async (_ctx) => {
180
211
  const matches: SkillMatch[] = []
181
212
 
182
213
  for (const skill of candidateSkills) {
183
- const markdown = await readSkillMarkdown(sessionID, skill.slug)
214
+ const markdown = await readSkillMarkdown(authSessionID, skill.slug)
184
215
  const searchableText = `${skill.description}\n\n${stripLeadingFrontmatter(markdown)}`
185
216
  const skillEmbedding = await getEmbedding(searchableText)
186
217
  const similarity = cosineSimilarity(queryEmbedding, skillEmbedding)
@@ -23,6 +23,14 @@ interface SkillDetailsResponse {
23
23
  }
24
24
  }
25
25
 
26
+ interface SessionLookupResponse {
27
+ error?: unknown
28
+ data?: {
29
+ id?: string
30
+ parentID?: string
31
+ }
32
+ }
33
+
26
34
  async function readErrorMessageFromResponse(
27
35
  response: Response,
28
36
  fallbackMessage: string
@@ -45,7 +53,29 @@ async function readErrorMessageFromResponse(
45
53
  }
46
54
  }
47
55
 
48
- export const GetSkillContentPlugin: Plugin = async (_ctx) => {
56
+ export const GetSkillContentPlugin: Plugin = async ({ client }) => {
57
+ async function resolveRootSessionID(sessionID: string): Promise<string> {
58
+ let currentSessionID = sessionID
59
+
60
+ while (true) {
61
+ const response = (await client.session.get({
62
+ path: {
63
+ id: currentSessionID,
64
+ },
65
+ })) as SessionLookupResponse
66
+ if (response.error) {
67
+ throw new Error(`Failed to resolve root session for ${currentSessionID}`)
68
+ }
69
+
70
+ const parentID = response.data?.parentID
71
+ if (!parentID) {
72
+ return currentSessionID
73
+ }
74
+
75
+ currentSessionID = parentID
76
+ }
77
+ }
78
+
49
79
  return {
50
80
  tool: {
51
81
  getSkillContent: tool({
@@ -59,6 +89,7 @@ export const GetSkillContentPlugin: Plugin = async (_ctx) => {
59
89
  async execute(args, context) {
60
90
  const { sessionID } = context
61
91
  const slug = args.slug.trim()
92
+ const authSessionID = await resolveRootSessionID(sessionID)
62
93
 
63
94
  if (!SLUG_REGEX.test(slug)) {
64
95
  throw new Error(
@@ -70,7 +101,7 @@ export const GetSkillContentPlugin: Plugin = async (_ctx) => {
70
101
  `${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}`,
71
102
  {
72
103
  headers: {
73
- Authorization: `Bearer ${sessionID}`,
104
+ Authorization: `Bearer ${authSessionID}`,
74
105
  },
75
106
  }
76
107
  )
@@ -97,7 +128,7 @@ export const GetSkillContentPlugin: Plugin = async (_ctx) => {
97
128
  `${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}/files/content?path=${encodeURIComponent("SKILL.md")}`,
98
129
  {
99
130
  headers: {
100
- Authorization: `Bearer ${sessionID}`,
131
+ Authorization: `Bearer ${authSessionID}`,
101
132
  },
102
133
  }
103
134
  )
@@ -18,7 +18,7 @@ async function ensureHoneytokenFile(workspaceDirectory: string): Promise<void> {
18
18
  const workflowsHoneytokenPath = path.join(workflowsDirectory, HONEYTOKEN_FILE_NAME)
19
19
  await fs.writeFile(workflowsHoneytokenPath, honeytokenValue, "utf-8")
20
20
 
21
- const skillsDirectory = path.join(workspaceDirectory, "custom-skills")
21
+ const skillsDirectory = path.join(workspaceDirectory, ".agents", "skills")
22
22
  await fs.mkdir(skillsDirectory, { recursive: true })
23
23
  const honeytokenPath = path.join(skillsDirectory, HONEYTOKEN_FILE_NAME)
24
24
  await fs.writeFile(honeytokenPath, honeytokenValue, "utf-8")
@@ -18,6 +18,14 @@ interface SkillSummary {
18
18
  can_edit: boolean
19
19
  }
20
20
 
21
+ interface SessionLookupResponse {
22
+ error?: unknown
23
+ data?: {
24
+ id?: string
25
+ parentID?: string
26
+ }
27
+ }
28
+
21
29
  async function readErrorMessageFromResponse(
22
30
  response: Response,
23
31
  fallbackMessage: string
@@ -40,7 +48,29 @@ async function readErrorMessageFromResponse(
40
48
  }
41
49
  }
42
50
 
43
- export const ListAvailableSkillsPlugin: Plugin = async (_ctx) => {
51
+ export const ListAvailableSkillsPlugin: Plugin = async ({ client }) => {
52
+ async function resolveRootSessionID(sessionID: string): Promise<string> {
53
+ let currentSessionID = sessionID
54
+
55
+ while (true) {
56
+ const response = (await client.session.get({
57
+ path: {
58
+ id: currentSessionID,
59
+ },
60
+ })) as SessionLookupResponse
61
+ if (response.error) {
62
+ throw new Error(`Failed to resolve root session for ${currentSessionID}`)
63
+ }
64
+
65
+ const parentID = response.data?.parentID
66
+ if (!parentID) {
67
+ return currentSessionID
68
+ }
69
+
70
+ currentSessionID = parentID
71
+ }
72
+ }
73
+
44
74
  return {
45
75
  tool: {
46
76
  listAvailableSkills: tool({
@@ -49,10 +79,11 @@ export const ListAvailableSkillsPlugin: Plugin = async (_ctx) => {
49
79
  args: {},
50
80
  async execute(_args, context) {
51
81
  const { sessionID } = context
82
+ const authSessionID = await resolveRootSessionID(sessionID)
52
83
 
53
84
  const response = await fetch(`${getApiBaseUrl()}/api/skills`, {
54
85
  headers: {
55
- Authorization: `Bearer ${sessionID}`,
86
+ Authorization: `Bearer ${authSessionID}`,
56
87
  },
57
88
  })
58
89