@openchambery/web 1.19.11 → 1.19.13-beta.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.
- package/dist/assets/{AgentAvatar-CX0a1rKE.js → AgentAvatar-CiQIIM0d.js} +1 -1
- package/dist/assets/AssistantView-YmeyxehG.js +2 -0
- package/dist/assets/{DiagramView-BQO1cBLs.js → DiagramView-BpZb-G9P.js} +1 -1
- package/dist/assets/{FadeInOnReveal-CTlNE4Hg.js → FadeInOnReveal-nf6-ZCrs.js} +1 -1
- package/dist/assets/{Icon-BYM02QyU.js → Icon-CeKHYyKZ.js} +1 -1
- package/dist/assets/MarkdownRenderer-wpeGdciL.js +2 -0
- package/dist/assets/{MarkdownRendererImpl--takeMXN.js → MarkdownRendererImpl-Peu0GVSQ.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-DGSqWcPN.js → MarkstreamRendererImpl-B-odd2Gz.js} +1 -1
- package/dist/assets/MobileDetailNavigation-CtYke_1C.js +1 -0
- package/dist/assets/{MobileShareBridge-D-BUjACe.js → MobileShareBridge-DIfOl8EZ.js} +3 -3
- package/dist/assets/MobileSurface-_KoX75Ff.js +2 -0
- package/dist/assets/{MultiRunWindow-BNR9ZWmD.js → MultiRunWindow-BT5Dwgzu.js} +1 -1
- package/dist/assets/{OnboardingScreen-DS2nugLX.js → OnboardingScreen-BPusUgFG.js} +1 -1
- package/dist/assets/{OverlayScrollbar-KUpvYHdv.js → OverlayScrollbar-DUghqFGg.js} +1 -1
- package/dist/assets/{SettingsView-DGerg-C2.js → SettingsView-BwQpbIZl.js} +11 -11
- package/dist/assets/{SettingsWindow-CSpd5JbJ.js → SettingsWindow-DfEGfyr8.js} +1 -1
- package/dist/assets/{TerminalView-DS-oUxJr.js → TerminalView-8402vpcO.js} +1 -1
- package/dist/assets/{ToolOutputDialog-Q1k9-IaY.js → ToolOutputDialog-B5RdkhsG.js} +1 -1
- package/dist/assets/{appThemeRegistry-CG7D-3-q.js → appThemeRegistry-CIZ-shMD.js} +1 -1
- package/dist/assets/{button-C0cth9tl.js → button-DFPOdZ0g.js} +1 -1
- package/dist/assets/{clipboard-DJ4nQEuY.js → clipboard-C05anBwU.js} +1 -1
- package/dist/assets/{collapsible-DFT9cE80.js → collapsible-oGbEvbe1.js} +1 -1
- package/dist/assets/{context-menu-CAp74NfV.js → context-menu-DGz4_2hs.js} +1 -1
- package/dist/assets/{desktopHosts-Dc2PS66M.js → desktopHosts-BVJXQq1S.js} +1 -1
- package/dist/assets/{desktopRelayRestore-T9y0orqa.js → desktopRelayRestore-RDfuj1M7.js} +1 -1
- package/dist/assets/{diagram-BryzMuta.js → diagram-Csk5GpDC.js} +1 -1
- package/dist/assets/{dialog-CgbV5ljg.js → dialog-rrqB-H6q.js} +1 -1
- package/dist/assets/{es-BHqnSjoZ.js → es-FE0sJ9EC.js} +1 -1
- package/dist/assets/{fileQueries-BY-9OWzp.js → fileQueries-BcdWOPlD.js} +1 -1
- package/dist/assets/{fr-BfrlJY9i.js → fr-DNDCSfGt.js} +1 -1
- package/dist/assets/{gitApi-Cr-joUEw.js → gitApi-_P0FsoXe.js} +1 -1
- package/dist/assets/{input-DFLpqjGR.js → input-BO3r20op.js} +1 -1
- package/dist/assets/{insertionBoundaries-X0xbikAA.js → insertionBoundaries-CWoioTdE.js} +1 -1
- package/dist/assets/{ja-DP-7qt-x.js → ja-BDwrk0cM.js} +1 -1
- package/dist/assets/{ko-E2B5vGQP.js → ko-BeeU2o4I.js} +1 -1
- package/dist/assets/{main-D0L7fIg3.js → main-CLj_WS1o.js} +2 -2
- package/dist/assets/{main-BI7fNQoi.js → main-CnSZ89pD.js} +4 -4
- package/dist/assets/{markdown-worker-CTsrtpPh.js → markdown-worker-CZWE1F1M.js} +1 -1
- package/dist/assets/{markdownHeightCache-CYDlisxr.js → markdownHeightCache-Cdd3hkMl.js} +1 -1
- package/dist/assets/{markdownTheme-DyJ4Gfcj.js → markdownTheme-DIB8WVmz.js} +1 -1
- package/dist/assets/miniChat-B9c3y2pD.js +2 -0
- package/dist/assets/{mobile-OcCu_0mG.js → mobile-DvbXUfNS.js} +2 -2
- package/dist/assets/{multirun-B0G_BEIN.js → multirun-CxhTsmRq.js} +1 -1
- package/dist/assets/{persistence-DdUac14I.js → persistence-CVTWFaqe.js} +2 -2
- package/dist/assets/{pl-r8X5gRM5.js → pl-BYuzpcI5.js} +1 -1
- package/dist/assets/{platform-B4KKLnZ5.js → platform-BY_Gtsqr.js} +1 -1
- package/dist/assets/{projectMeta-BNOzn1QS.js → projectMeta-Dl5iPCB6.js} +1 -1
- package/dist/assets/{pt-BR-DcpdXCf5.js → pt-BR-Bn7cwEzv.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-BREc3uhX.js → renderElectronMiniChatApp-CkJ51qCh.js} +2 -2
- package/dist/assets/renderMobileApp-DcjQPhWw.js +28 -0
- package/dist/assets/{runtimeConfig-P8nVMYzE.js → runtimeConfig-Bsk2kWsW.js} +1 -1
- package/dist/assets/runtimeEndpointReset-BBLFgl85.js +147 -0
- package/dist/assets/{sessionChangeSummary-hVby3sJk.js → sessionChangeSummary-DDVT7O5J.js} +1 -1
- package/dist/assets/{sessionLookup-C_9-9ggp.js → sessionLookup-C8VZn0gJ.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-C8_9kHqu.js → sortable-tabs-strip-s7UUxsa6.js} +1 -1
- package/dist/assets/src-BpHDMgud.css +2 -0
- package/dist/assets/{store-BDFdnk6l.js → store-n94dSb3M.js} +2 -2
- package/dist/assets/{terminalApi-947l7Ra3.js → terminalApi-C54_rKEK.js} +1 -1
- package/dist/assets/{textarea-Cgo_nWUU.js → textarea-BjX-KhMN.js} +1 -1
- package/dist/assets/{tooltip-BifLJ8o0.js → tooltip-CQGqGAG_.js} +1 -1
- package/dist/assets/{uk-Di0vl9V-.js → uk-CwpbHEa3.js} +1 -1
- package/dist/assets/{url-Pk6tQ1EZ.js → url-BMjqccCo.js} +1 -1
- package/dist/assets/{useAppFontEffects-CEmOsrr6.js → useAppFontEffects-BIFw36SA.js} +5 -5
- package/dist/assets/{useAssistantUIStore-me6s63b2.js → useAssistantUIStore-Cg9bsT4b.js} +1 -1
- package/dist/assets/{useDesktopWindowControlsLayout-CxaK-zXh.js → useDesktopWindowControlsLayout-DoygEcN4.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-BIwv5r5l.js → useEffectiveDirectory-DJSYYkVp.js} +1 -1
- package/dist/assets/{useFontPreferences-BCOBlaAd.js → useFontPreferences-B5_apbBC.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-DHm5kZPx.js → useMobileNavigationStore-DuTu0Mzh.js} +5 -5
- package/dist/assets/{useSessionAutoCleanup-CPLP9KWH.js → useSessionAutoCleanup-C-L9teva.js} +1 -1
- package/dist/assets/{useUIStore-CuaA8-Hp.js → useUIStore-DwpQTjLO.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-CZ4ZTWEH.js → useWorkerHighlightedLines-DxTfgdEi.js} +1 -1
- package/dist/assets/{utils-BiK3tBGM.js → utils-81nUI_xp.js} +1 -1
- package/dist/assets/{zh-CN-coFCfeQE.js → zh-CN-BpzFZ-LU.js} +1 -1
- package/dist/assets/{zh-TW-oDRZ1-yb.js → zh-TW-Bopgc5Eo.js} +1 -1
- package/dist/index.html +9 -9
- package/dist/mini-chat.html +9 -9
- package/dist/mobile.html +9 -9
- package/package.json +1 -1
- package/server/index.js +19 -0
- package/server/lib/assistants/DOCUMENTATION.md +21 -6
- package/server/lib/assistants/assign.js +455 -30
- package/server/lib/assistants/assign.test.js +351 -0
- package/server/lib/assistants/contact-active-turn.http.test.js +134 -0
- package/server/lib/assistants/contact-store.js +64 -0
- package/server/lib/assistants/contact-store.test.js +12 -0
- package/server/lib/assistants/contact-tools.js +216 -25
- package/server/lib/assistants/contact-tools.test.js +167 -0
- package/server/lib/assistants/contracts.js +2 -2
- package/server/lib/assistants/harness.js +20 -3
- package/server/lib/assistants/harness.test.js +120 -1
- package/server/lib/assistants/pi-tools.js +50 -3
- package/server/lib/assistants/pi-tools.test.js +25 -0
- package/server/lib/assistants/service.js +320 -27
- package/server/lib/assistants/service.test.js +581 -9
- package/server/lib/llm/DOCUMENTATION.md +31 -18
- package/server/lib/llm/catalog.js +91 -16
- package/server/lib/llm/catalog.test.js +152 -11
- package/server/lib/llm/generate.js +43 -7
- package/server/lib/llm/generate.test.js +98 -1
- package/server/lib/llm/temp-directory.js +34 -13
- package/server/lib/llm/temp-directory.test.js +57 -0
- package/server/lib/notifications/APNS.md +20 -4
- package/server/lib/notifications/DOCUMENTATION.md +5 -0
- package/server/lib/notifications/apns-runtime.js +136 -18
- package/server/lib/notifications/apns-runtime.test.js +128 -0
- package/server/lib/notifications/apns-titles.js +21 -1
- package/server/lib/notifications/live-activity-refresh-runtime.js +185 -0
- package/server/lib/notifications/live-activity-refresh-runtime.test.js +242 -0
- package/server/lib/notifications/runtime.js +93 -2
- package/server/lib/notifications/runtime.test.js +167 -4
- package/server/lib/opencode/settings-helpers.js +12 -0
- package/server/lib/opencode/settings-runtime.js +16 -0
- package/server/lib/opencode/settings-runtime.test.js +21 -0
- package/server/lib/scheduled-tasks/runtime.js +21 -0
- package/server/lib/scheduled-tasks/runtime.test.js +24 -0
- package/dist/assets/AssistantView-BwrcqJzu.js +0 -2
- package/dist/assets/MarkdownRenderer-C49bfcHx.js +0 -2
- package/dist/assets/MobileDetailNavigation-LZU2vFAs.js +0 -1
- package/dist/assets/MobileSurface-OAJjAQCZ.js +0 -2
- package/dist/assets/miniChat-BJ9AclZX.js +0 -2
- package/dist/assets/renderMobileApp-C4igZ42L.js +0 -28
- package/dist/assets/runtimeEndpointReset-6lVRspis.js +0 -147
- package/dist/assets/src-B-P5qIBc.css +0 -2
|
@@ -229,6 +229,10 @@ describe('runContactTurn', () => {
|
|
|
229
229
|
const createChatCompletion = vi.fn(async ({ body }) => {
|
|
230
230
|
expect(body.messages[0].content).toContain('assign_session')
|
|
231
231
|
expect(body.messages[0].content).toContain('docs')
|
|
232
|
+
expect(body.messages[0].content).toContain('arguments schema:')
|
|
233
|
+
expect(body.messages[0].content).toContain('"edits"')
|
|
234
|
+
expect(body.messages[0].content).toContain('"content"')
|
|
235
|
+
expect(body.messages[0].content).toContain('not OpenCode native tools and not MCP')
|
|
232
236
|
const content = replies[call++]
|
|
233
237
|
if (!content) throw new Error('Unexpected completion')
|
|
234
238
|
return { completion: { choices: [{ message: { content } }] } }
|
|
@@ -242,11 +246,126 @@ describe('runContactTurn', () => {
|
|
|
242
246
|
expect(result.tools.map((tool) => tool.name)).toEqual([...PI_CODING_TOOL_NAMES, ...tools.map((tool) => tool.name)])
|
|
243
247
|
expect(assignWork).toHaveBeenCalledTimes(1)
|
|
244
248
|
expect(result.cards).toEqual([expect.objectContaining({ sessionID: 'ses_mixed' })])
|
|
245
|
-
|
|
249
|
+
// read → text (miss) → missed-fence retry assign; assign terminates (no post-assign LLM).
|
|
250
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(3)
|
|
251
|
+
// Tool call/result association: after read, the next completion sees a named tool result.
|
|
252
|
+
const secondMessages = createChatCompletion.mock.calls[1][0].body.messages
|
|
253
|
+
const toolResultTurn = secondMessages.find((message) => (
|
|
254
|
+
message.role === 'user' && typeof message.content === 'string' && message.content.includes('tool result name=read')
|
|
255
|
+
))
|
|
256
|
+
expect(toolResultTurn).toBeTruthy()
|
|
257
|
+
expect(toolResultTurn.content).toMatch(/Read project docs|documentation/i)
|
|
246
258
|
} finally {
|
|
247
259
|
fs.rmSync(workspace, { recursive: true, force: true })
|
|
248
260
|
}
|
|
249
261
|
})
|
|
262
|
+
it('real Agent loop: looping assign fences create only one worker + one card (terminate)', async () => {
|
|
263
|
+
const assignWork = vi.fn(async () => ({
|
|
264
|
+
sessionID: 'ses_once',
|
|
265
|
+
directory: '/repo',
|
|
266
|
+
title: 'Once',
|
|
267
|
+
status: 'busy',
|
|
268
|
+
}))
|
|
269
|
+
const tools = createContactTools({ assignWork })
|
|
270
|
+
let completions = 0
|
|
271
|
+
const createChatCompletion = vi.fn(async () => {
|
|
272
|
+
completions += 1
|
|
273
|
+
// Safety: a non-terminating loop must not mint 37 sessions in tests.
|
|
274
|
+
if (completions > 8) {
|
|
275
|
+
throw new Error('harness safety stop: too many completions without terminate')
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
completion: {
|
|
279
|
+
choices: [{
|
|
280
|
+
message: {
|
|
281
|
+
content: '```openchamber-tool\n{"name":"assign_session","arguments":{"prompt":"Fix login","projectPath":"/repo"}}\n```',
|
|
282
|
+
},
|
|
283
|
+
}],
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
const result = await runContactTurn({
|
|
288
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
289
|
+
history: [],
|
|
290
|
+
userText: '建会话修 login',
|
|
291
|
+
tools,
|
|
292
|
+
projects: [{ id: 'p1', path: '/repo', label: 'Repo' }],
|
|
293
|
+
createChatCompletion,
|
|
294
|
+
})
|
|
295
|
+
expect(assignWork).toHaveBeenCalledTimes(1)
|
|
296
|
+
expect(result.cards).toEqual([expect.objectContaining({ sessionID: 'ses_once', cardType: 'session' })])
|
|
297
|
+
expect(result.cards).toHaveLength(1)
|
|
298
|
+
expect(result.bubbles).toContain('Opened a coding session.')
|
|
299
|
+
// First completion issues assign; terminate skips the auto follow-up LLM.
|
|
300
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
301
|
+
expect(completions).toBe(1)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('real Agent loop: list_projects then assign keeps prereq + one session (terminate after assign)', async () => {
|
|
305
|
+
const listProjects = vi.fn(async () => [{ id: 'p1', path: '/repo', label: 'Repo' }])
|
|
306
|
+
const assignWork = vi.fn(async () => ({
|
|
307
|
+
sessionID: 'ses_prereq',
|
|
308
|
+
directory: '/repo',
|
|
309
|
+
title: 'Prereq',
|
|
310
|
+
status: 'busy',
|
|
311
|
+
}))
|
|
312
|
+
const tools = createContactTools({ listProjects, assignWork })
|
|
313
|
+
let completions = 0
|
|
314
|
+
const createChatCompletion = vi.fn(async ({ body }) => {
|
|
315
|
+
completions += 1
|
|
316
|
+
if (completions > 6) {
|
|
317
|
+
throw new Error('harness safety stop: too many completions')
|
|
318
|
+
}
|
|
319
|
+
const last = body.messages.at(-1)
|
|
320
|
+
const lastText = typeof last?.content === 'string' ? last.content : ''
|
|
321
|
+
if (lastText.includes('tool result name=list_projects') || lastText.includes('Registered projects')) {
|
|
322
|
+
return {
|
|
323
|
+
completion: {
|
|
324
|
+
choices: [{
|
|
325
|
+
message: {
|
|
326
|
+
content: '```openchamber-tool\n{"name":"assign_session","arguments":{"prompt":"Fix","projectPath":"/repo"}}\n```',
|
|
327
|
+
},
|
|
328
|
+
}],
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (completions === 1) {
|
|
333
|
+
return {
|
|
334
|
+
completion: {
|
|
335
|
+
choices: [{
|
|
336
|
+
message: {
|
|
337
|
+
content: '```openchamber-tool\n{"name":"list_projects","arguments":{"query":"repo"}}\n```',
|
|
338
|
+
},
|
|
339
|
+
}],
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// If assign did not terminate, a looping model would keep assigning — fail closed.
|
|
344
|
+
return {
|
|
345
|
+
completion: {
|
|
346
|
+
choices: [{
|
|
347
|
+
message: {
|
|
348
|
+
content: '```openchamber-tool\n{"name":"assign_session","arguments":{"prompt":"Fix again","projectPath":"/repo"}}\n```',
|
|
349
|
+
},
|
|
350
|
+
}],
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
const result = await runContactTurn({
|
|
355
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
356
|
+
history: [],
|
|
357
|
+
userText: '找项目 repo 然后建会话修问题',
|
|
358
|
+
tools,
|
|
359
|
+
projects: [{ id: 'p1', path: '/repo', label: 'Repo' }],
|
|
360
|
+
createChatCompletion,
|
|
361
|
+
})
|
|
362
|
+
expect(listProjects).toHaveBeenCalledTimes(1)
|
|
363
|
+
expect(assignWork).toHaveBeenCalledTimes(1)
|
|
364
|
+
expect(result.cards).toEqual([expect.objectContaining({ sessionID: 'ses_prereq' })])
|
|
365
|
+
expect(result.cards).toHaveLength(1)
|
|
366
|
+
expect(createChatCompletion.mock.calls.length).toBeLessThanOrEqual(3)
|
|
367
|
+
})
|
|
368
|
+
|
|
250
369
|
it('retries assignment after an unrelated read result', async () => {
|
|
251
370
|
const prompts = []
|
|
252
371
|
function AgentImpl(options) {
|
|
@@ -70,21 +70,68 @@ const bindHarnessTool = (tool, context) => ({
|
|
|
70
70
|
),
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Serialize a tool parameter schema for the contact system prompt.
|
|
75
|
+
* Mirrors contact-tools.serializeToolParametersForPrompt without a circular import.
|
|
76
|
+
*/
|
|
77
|
+
const serializeParameters = (parameters) => {
|
|
78
|
+
if (parameters == null) return '{"type":"object","properties":{}}';
|
|
79
|
+
try {
|
|
80
|
+
const text = JSON.stringify(parameters, (_key, value) => {
|
|
81
|
+
if (typeof value === 'function' || typeof value === 'symbol') return undefined;
|
|
82
|
+
return value;
|
|
83
|
+
});
|
|
84
|
+
if (typeof text === 'string' && text.trim() && text !== 'undefined') return text;
|
|
85
|
+
} catch {
|
|
86
|
+
// Fall through.
|
|
87
|
+
}
|
|
88
|
+
return '{"type":"object","properties":{}}';
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const formatCodingToolEntry = (tool) => {
|
|
92
|
+
if (!tool || typeof tool.name !== 'string' || !tool.name.trim()) return '';
|
|
93
|
+
const name = tool.name.trim();
|
|
94
|
+
const description = typeof tool.description === 'string' && tool.description.trim()
|
|
95
|
+
? tool.description.trim()
|
|
96
|
+
: name;
|
|
97
|
+
return `- ${name}: ${description}\n arguments schema: ${serializeParameters(tool.parameters)}`;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export function formatPiCodingPrompt({ cwd, skillsPrompt = '', tools = [] } = {}) {
|
|
74
101
|
if (typeof cwd !== 'string' || !cwd.trim()) return '';
|
|
102
|
+
const codingTools = Array.isArray(tools)
|
|
103
|
+
? tools.filter((tool) => isPiCodingToolName(tool?.name))
|
|
104
|
+
: [];
|
|
105
|
+
const catalog = codingTools.length > 0
|
|
106
|
+
? codingTools.map(formatCodingToolEntry).filter(Boolean)
|
|
107
|
+
: [
|
|
108
|
+
'- bash: Execute a bash command in the working directory.\n arguments schema: {"type":"object","required":["command"],"properties":{"command":{"type":"string"},"timeout":{"type":"number"}}}',
|
|
109
|
+
'- read: Read a file (path required; optional offset/limit).\n arguments schema: {"type":"object","required":["path"],"properties":{"path":{"type":"string"},"offset":{"type":"number"},"limit":{"type":"number"}}}',
|
|
110
|
+
'- write: Write/create a file (path + content required).\n arguments schema: {"type":"object","required":["path","content"],"properties":{"path":{"type":"string"},"content":{"type":"string"}}}',
|
|
111
|
+
'- edit: Exact text replacement (path + edits[{oldText,newText}] required).\n arguments schema: {"type":"object","required":["path","edits"],"properties":{"path":{"type":"string"},"edits":{"type":"array","items":{"type":"object","required":["oldText","newText"],"properties":{"oldText":{"type":"string"},"newText":{"type":"string"}}}}}}',
|
|
112
|
+
];
|
|
75
113
|
return [
|
|
76
114
|
`Working directory: ${cwd}`,
|
|
77
|
-
'You have pi coding tools in this directory: read, write, edit, bash.',
|
|
115
|
+
'You have application-owned pi coding tools in this directory: read, write, edit, bash.',
|
|
116
|
+
'These are not OpenCode native tools and not MCP. Skill directory entries below are instructions only — load a matching skill by calling read on its path; never invent skill or MCP tool names (no context7, gh_grep, webfetch, etc.).',
|
|
78
117
|
'This is your working directory. Ignore any other cwd from the environment, including temporary generator workspaces under /var/folders or os.tmpdir.',
|
|
79
118
|
'When the user asks pwd, the current directory, or to look at files, call bash/read yourself. Never say you have no terminal or cannot read or write files.',
|
|
80
119
|
'Skills are merged from ~/.claude/skills, ~/.agents/skills, and this project\'s .claude/skills plus .agents/skills (project wins on name). Read a skill file when the task matches its description.',
|
|
81
|
-
'Call these tools with the same fenced JSON as other tools:',
|
|
120
|
+
'Call these tools with the same fenced JSON as other tools. Match the argument schemas exactly:',
|
|
82
121
|
'```openchamber-tool',
|
|
83
122
|
'{"name":"bash","arguments":{"command":"pwd"}}',
|
|
84
123
|
'```',
|
|
85
124
|
'```openchamber-tool',
|
|
86
125
|
'{"name":"read","arguments":{"path":".agents/skills/example/SKILL.md"}}',
|
|
87
126
|
'```',
|
|
127
|
+
'```openchamber-tool',
|
|
128
|
+
'{"name":"write","arguments":{"path":"notes.txt","content":"hello"}}',
|
|
129
|
+
'```',
|
|
130
|
+
'```openchamber-tool',
|
|
131
|
+
'{"name":"edit","arguments":{"path":"notes.txt","edits":[{"oldText":"hello","newText":"hello world"}]}}',
|
|
132
|
+
'```',
|
|
133
|
+
'Available pi coding tools (full argument schemas):',
|
|
134
|
+
...catalog,
|
|
88
135
|
typeof skillsPrompt === 'string' && skillsPrompt.trim() ? skillsPrompt.trim() : '',
|
|
89
136
|
].filter(Boolean).join('\n');
|
|
90
137
|
}
|
|
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from 'vitest';
|
|
|
5
5
|
import {
|
|
6
6
|
collectAssistantSkillDirectories,
|
|
7
7
|
createPiCodingRuntime,
|
|
8
|
+
formatPiCodingPrompt,
|
|
8
9
|
mergeSkillsByName,
|
|
9
10
|
PI_CODING_TOOL_NAMES,
|
|
10
11
|
resolveAssistantCwd,
|
|
@@ -78,6 +79,30 @@ describe('mergeSkillsByName', () => {
|
|
|
78
79
|
});
|
|
79
80
|
});
|
|
80
81
|
|
|
82
|
+
describe('formatPiCodingPrompt', () => {
|
|
83
|
+
it('includes full pi tool argument schemas and distinguishes skills from tools', async () => {
|
|
84
|
+
const cwd = temp();
|
|
85
|
+
const home = temp();
|
|
86
|
+
const runtime = await createPiCodingRuntime(cwd, { homeDir: home });
|
|
87
|
+
const prompt = formatPiCodingPrompt({
|
|
88
|
+
cwd: runtime.cwd,
|
|
89
|
+
skillsPrompt: runtime.skillsPrompt,
|
|
90
|
+
tools: runtime.tools,
|
|
91
|
+
});
|
|
92
|
+
expect(prompt).toContain('arguments schema:');
|
|
93
|
+
expect(prompt).toContain('"path"');
|
|
94
|
+
expect(prompt).toContain('"content"');
|
|
95
|
+
expect(prompt).toContain('"edits"');
|
|
96
|
+
expect(prompt).toContain('"oldText"');
|
|
97
|
+
expect(prompt).toContain('"newText"');
|
|
98
|
+
expect(prompt).toContain('"command"');
|
|
99
|
+
expect(prompt).toContain('not OpenCode native tools and not MCP');
|
|
100
|
+
expect(prompt).toContain('never invent skill or MCP tool names');
|
|
101
|
+
expect(prompt).toContain('context7');
|
|
102
|
+
await runtime.close();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
81
106
|
describe('createPiCodingRuntime', () => {
|
|
82
107
|
it('installs read/write/edit/bash and merges global plus project skills', async () => {
|
|
83
108
|
const home = temp();
|