@vibe-forge/workspace-assets 3.0.0 → 3.1.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/__snapshots__/workspace-assets-rich.snapshot.json +1 -1
- package/__tests__/adapter-asset-plan.spec.ts +2 -60
- package/__tests__/bundle.spec.ts +0 -238
- package/__tests__/prompt-builders.spec.ts +39 -19
- package/__tests__/skill-dependencies-cli.spec.ts +70 -128
- package/package.json +5 -5
- package/src/adapter-asset-plan.ts +2 -2
- package/src/bundle-internal.ts +48 -1
- package/src/plugin-skill-dependencies.ts +1 -0
- package/src/prompt-builders.ts +3 -3
- package/src/selection-internal.ts +9 -2
- package/src/skill-dependencies.ts +10 -108
- package/src/skills-cli-dependency.ts +26 -5
- package/src/task-tool-guidance.ts +24 -10
- package/src/workspace-prompt.ts +3 -3
|
@@ -467,7 +467,7 @@
|
|
|
467
467
|
]
|
|
468
468
|
},
|
|
469
469
|
"options": {
|
|
470
|
-
"systemPrompt": "<system-prompt>\nThe project system rules are:\n# review\n\n> 必须检查发布改动的回归风险。\n\n# demo/security\n\n> Use when: 插件安全规则\n> Rule file path: node_modules/@vibe-forge/plugin-demo/rules/security.md\n> Only read this rule file when the task matches the scenario above.\n</system-prompt>\n\n\n<system-prompt>\nThe following skill modules are loaded for the project:\n# research\n\n> Skill description: 检索资料\n> Skill file path: .ai/skills/research/SKILL.md\n> Resolve relative paths in the resource content relative to the directory containing this skill file.\n\n<skill-content>\n先阅读 README.md,再补充结论。\n</skill-content>\n</system-prompt>\n\n\n<system-prompt>\nThe project includes the following entities:\n - architect: 负责拆解方案的实体\n\nWhen solving user problems, you may
|
|
470
|
+
"systemPrompt": "<system-prompt>\nThe project system rules are:\n# review\n\n> 必须检查发布改动的回归风险。\n\n# demo/security\n\n> Use when: 插件安全规则\n> Rule file path: node_modules/@vibe-forge/plugin-demo/rules/security.md\n> Only read this rule file when the task matches the scenario above.\n</system-prompt>\n\n\n<system-prompt>\nThe following skill modules are loaded for the project:\n# research\n\n> Skill description: 检索资料\n> Skill file path: .ai/skills/research/SKILL.md\n> Resolve relative paths in the resource content relative to the directory containing this skill file.\n\n<skill-content>\n先阅读 README.md,再补充结论。\n</skill-content>\n</system-prompt>\n\n\n<system-prompt>\nThe project includes the following entities:\n - architect: 负责拆解方案的实体\n\nWhen solving user problems, you may start child runtime sessions with `vf run --input-format stream-json --output-format stream-json` and a `session.start` protocol envelope as needed; use `session.status`, `session.events`, and `wait` to track progress.\nAgent runtime guide:\n- Use unified CLI protocol mode, `vf run --input-format stream-json --output-format stream-json`, to start a child runtime session when the work should run in a separate entity or continue independently from the current turn.\n- Send typed runtime protocol envelopes such as `session.start`, `session.message`, `session.status`, `session.events`, `session.submit`, and `session.stop`; do not treat dedicated `vf agent ...` subcommands as the standard integration surface.\n- Ordinary new sessions stay session-scoped. A room is created or discovered only when a unified CLI runtime protocol start command launches a child runtime session from a server-managed host session and the server projects runtime store metadata/events.\n- Do not use MCP task tools, `vf agent ...`, legacy StartTasks, hand-written DB edits, or ad-hoc TS scripts as the task consumer surface. Use CLI protocol mode and the runtime protocol/store for start, status, events, follow-up messages, input submission, and cancellation.\n- Server-managed host sessions inject the current adapter, model, effort, and permission mode as runtime protocol defaults. Omit these fields to inherit the host selection, or set them explicitly only when a child task must use a different runtime profile.\n- Copyable JSONL example; write one `session.start` line per child task, and use multiple lines for multiple subtasks:\n```bash\ncat <<'JSONL' | vf run --input-format stream-json --output-format stream-json\n{\"commandId\":\"start-planner\",\"type\":\"session.start\",\"payload\":{\"title\":\"Plan Agent Room UI fix\",\"message\":\"Plan the frontend changes and tests for the Agent Room UI fix.\",\"entity\":\"dev-planner\",\"background\":true},\"title\":\"Plan Agent Room UI fix\",\"message\":\"Plan the frontend changes and tests for the Agent Room UI fix.\",\"entity\":\"dev-planner\",\"background\":true}\n{\"commandId\":\"start-reviewer\",\"type\":\"session.start\",\"payload\":{\"title\":\"Review Agent Room UI fix\",\"message\":\"Review the implemented Agent Room UI fix for regressions and missing tests.\",\"entity\":\"dev-reviewer\",\"background\":true},\"title\":\"Review Agent Room UI fix\",\"message\":\"Review the implemented Agent Room UI fix for regressions and missing tests.\",\"entity\":\"dev-reviewer\",\"background\":true}\nJSONL\n```\n- Keep `payload.title`, `payload.message`, `payload.entity`, and `payload.background: true` explicit in each start envelope. The mirrored top-level fields make the JSONL executable by the current `vf run` protocol reader.\n- Include a short `title` when the task prompt is long; it becomes the child session title and room run label. Put any room or workspace context in the title and initial message.\n- Read the returned `sessionId` and use it for follow-up protocol commands. Read the latest runtime snapshot from the runtime store or a `session.status` protocol command, and read progress from runtime events or a `session.events` protocol command.\n- Use a follow/read-events workflow when you need to watch progress instead of repeatedly restarting work.\n- Use a `session.message` protocol command to give an existing session another instruction. Running sessions continue immediately; completed or failed sessions resume the same conversation when the runtime allows resume.\n- When the chat UI sends a `[ROOM_TASK_MESSAGE] ... [/ROOM_TASK_MESSAGE]` block, treat it as a runtime relay envelope instead of ordinary prose. Parse the `sessionId` or legacy `taskId`, `message`, and optional `mode` / `request` fields. If the envelope indicates `mode: interaction`, or runtime status shows `waiting_input` / pending input, use a `session.submit` protocol command. Otherwise use `session.message`. Do not reply inline instead of routing the relay.\n- Use `session.submit` only when a runtime session is waiting for an explicit input or approval request. Do not use it for ordinary follow-up instructions.\n- Use a `session.stop` protocol command for graceful cancellation and set `mode` to `force` only when stop cannot recover the session.\n- Compatibility aliases such as `vf agent start/status/events/send/submit/stop` may exist for debugging or legacy scripts, but they are not the primary guidance for new agent workflows.\n- When a session is still making progress, use `wait` between checks and inspect status/events instead of starting a replacement session.\n</system-prompt>\n\n\n<system-prompt>\nYou are a professional project execution manager who can skillfully direct other entities to work toward your goal. Expectations:\n\n- Never complete code development work alone\n- You must coordinate other developers to complete tasks\n- You must keep them aligned with the goal and verify that their completion reports meet the requirements\n\nChoose the appropriate workflow based on the user's needs and the actual development goal, and use the workflow identifier to locate and load the corresponding definition.\n- Pass the identifier based on the actual need. This is not a path; use the standard workflow loading capability to resolve it.\n- Decide how to pass parameters based on their descriptions and actual usage scenarios.\nThe project includes the following workflows:\n- Workflow name: release\n - Description: 正式发布流程\n - Identifier: release\n - Parameters:\n - None\n\n- Workflow name: demo/release\n - Description: 插件发布流程\n - Identifier: demo/release\n - Parameters:\n - None\n\n</system-prompt>\n\n\n执行正式发布,并整理变更摘要。",
|
|
471
471
|
"tools": {
|
|
472
472
|
"include": [
|
|
473
473
|
"Edit",
|
|
@@ -280,64 +280,6 @@ describe('buildAdapterAssetPlan', () => {
|
|
|
280
280
|
]))
|
|
281
281
|
})
|
|
282
282
|
|
|
283
|
-
it('keeps explicit skills CLI dependencies ahead of preselected home-bridged skills in overlays', async () => {
|
|
284
|
-
const workspace = await createWorkspace()
|
|
285
|
-
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
286
|
-
const tempInstallDir = join(workspace, '.tmp-install-skills-cli-foo')
|
|
287
|
-
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'foo')
|
|
288
|
-
await writeDocument(
|
|
289
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
290
|
-
'---\nname: foo\ndescription: Registry foo\n---\nUse the registry definition.\n'
|
|
291
|
-
)
|
|
292
|
-
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
293
|
-
tempDir: tempInstallDir,
|
|
294
|
-
installedSkill: {
|
|
295
|
-
dirName: 'foo',
|
|
296
|
-
name: 'foo',
|
|
297
|
-
sourcePath: installedSkillDir
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
await writeDocument(
|
|
302
|
-
join(realHome!, '.agents/skills/foo/SKILL.md'),
|
|
303
|
-
'---\ndescription: Home foo\n---\nUse the home definition.\n'
|
|
304
|
-
)
|
|
305
|
-
await writeDocument(
|
|
306
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
307
|
-
[
|
|
308
|
-
'---',
|
|
309
|
-
'name: app-builder',
|
|
310
|
-
'description: Build apps',
|
|
311
|
-
'dependencies:',
|
|
312
|
-
' - anthropics/skills@foo',
|
|
313
|
-
'---',
|
|
314
|
-
'Build the app.'
|
|
315
|
-
].join('\n')
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
319
|
-
cwd: workspace,
|
|
320
|
-
configs: [undefined, undefined],
|
|
321
|
-
useDefaultVibeForgeMcpServer: false
|
|
322
|
-
})
|
|
323
|
-
const plan = await buildAdapterAssetPlan({
|
|
324
|
-
adapter: 'opencode',
|
|
325
|
-
bundle,
|
|
326
|
-
options: {
|
|
327
|
-
skills: {
|
|
328
|
-
include: ['foo', 'app-builder']
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
const fooOverlays = plan.overlays.filter(entry => entry.kind === 'skill' && entry.targetPath === 'skills/foo')
|
|
334
|
-
expect(fooOverlays).toHaveLength(1)
|
|
335
|
-
expect(fooOverlays[0]?.sourcePath).toContain(
|
|
336
|
-
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/foo'
|
|
337
|
-
)
|
|
338
|
-
expect(fooOverlays[0]?.sourcePath).not.toBe(join(realHome!, '.agents/skills/foo'))
|
|
339
|
-
})
|
|
340
|
-
|
|
341
283
|
it('prunes excluded skill dependency subtrees from selected native overlays', async () => {
|
|
342
284
|
const workspace = await createWorkspace()
|
|
343
285
|
|
|
@@ -394,7 +336,7 @@ describe('buildAdapterAssetPlan', () => {
|
|
|
394
336
|
])
|
|
395
337
|
})
|
|
396
338
|
|
|
397
|
-
it('builds copilot native skill overlays and
|
|
339
|
+
it('builds copilot native skill overlays and native hook diagnostics', async () => {
|
|
398
340
|
const workspace = await createWorkspace()
|
|
399
341
|
|
|
400
342
|
await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
|
|
@@ -489,7 +431,7 @@ describe('buildAdapterAssetPlan', () => {
|
|
|
489
431
|
expect.objectContaining({
|
|
490
432
|
assetId: loggerHookPluginId,
|
|
491
433
|
adapter: 'copilot',
|
|
492
|
-
status: '
|
|
434
|
+
status: 'native'
|
|
493
435
|
}),
|
|
494
436
|
expect.objectContaining({
|
|
495
437
|
assetId: docsMcpId,
|
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -386,79 +386,6 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
386
386
|
}))
|
|
387
387
|
})
|
|
388
388
|
|
|
389
|
-
it('installs selected missing skill dependencies from the skills CLI cache', async () => {
|
|
390
|
-
const workspace = await createWorkspace()
|
|
391
|
-
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
392
|
-
const tempInstallDir = join(workspace, '.tmp-install-skills-cli')
|
|
393
|
-
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'frontend-design')
|
|
394
|
-
await writeDocument(
|
|
395
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
396
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
397
|
-
)
|
|
398
|
-
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
399
|
-
tempDir: tempInstallDir,
|
|
400
|
-
installedSkill: {
|
|
401
|
-
dirName: 'frontend-design',
|
|
402
|
-
name: 'frontend-design',
|
|
403
|
-
sourcePath: installedSkillDir
|
|
404
|
-
}
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
await writeDocument(
|
|
408
|
-
join(realHome!, '.agents/skills/frontend-design/SKILL.md'),
|
|
409
|
-
'---\ndescription: home frontend design\n---\nUse the home definition.'
|
|
410
|
-
)
|
|
411
|
-
await writeDocument(
|
|
412
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
413
|
-
[
|
|
414
|
-
'---',
|
|
415
|
-
'name: app-builder',
|
|
416
|
-
'description: Build apps',
|
|
417
|
-
'dependencies:',
|
|
418
|
-
' - anthropics/skills@frontend-design',
|
|
419
|
-
'---',
|
|
420
|
-
'Build the app.'
|
|
421
|
-
].join('\n')
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
425
|
-
cwd: workspace,
|
|
426
|
-
configs: [undefined, undefined],
|
|
427
|
-
useDefaultVibeForgeMcpServer: false
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
431
|
-
expect(bundle.skills.find(asset => asset.name === 'frontend-design')).toEqual(expect.objectContaining({
|
|
432
|
-
resolvedBy: 'home-bridge',
|
|
433
|
-
sourcePath: join(realHome!, '.agents/skills/frontend-design/SKILL.md')
|
|
434
|
-
}))
|
|
435
|
-
|
|
436
|
-
await buildAdapterAssetPlan({
|
|
437
|
-
adapter: 'opencode',
|
|
438
|
-
bundle,
|
|
439
|
-
options: {
|
|
440
|
-
skills: {
|
|
441
|
-
include: ['app-builder']
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
447
|
-
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
448
|
-
expect(dependency?.sourcePath).toContain(
|
|
449
|
-
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/'
|
|
450
|
-
)
|
|
451
|
-
expect(bundle.skills.find(asset => (
|
|
452
|
-
asset.name === 'frontend-design' && asset.resolvedBy === 'home-bridge'
|
|
453
|
-
))).toBeUndefined()
|
|
454
|
-
expect(skillsCliMocks.findSkillsCli).not.toHaveBeenCalled()
|
|
455
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
|
|
456
|
-
config: undefined,
|
|
457
|
-
skill: 'frontend-design',
|
|
458
|
-
source: 'anthropics/skills'
|
|
459
|
-
})
|
|
460
|
-
})
|
|
461
|
-
|
|
462
389
|
it('installs configured project skills before bundle resolution and rewrites renamed skill names', async () => {
|
|
463
390
|
const workspace = await createWorkspace()
|
|
464
391
|
const tempInstallDir = join(workspace, '.tmp-configured-install')
|
|
@@ -564,171 +491,6 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
564
491
|
)
|
|
565
492
|
})
|
|
566
493
|
|
|
567
|
-
it('installs skill dependencies into the primary workspace shared cache', async () => {
|
|
568
|
-
const primary = await createWorkspace()
|
|
569
|
-
const worktree = await createWorkspace()
|
|
570
|
-
const previousPrimaryWorkspace = process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__
|
|
571
|
-
const tempInstallDir = join(worktree, '.tmp-install-skills-cli')
|
|
572
|
-
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'frontend-design')
|
|
573
|
-
await writeDocument(
|
|
574
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
575
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse primary cache.\n'
|
|
576
|
-
)
|
|
577
|
-
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
578
|
-
tempDir: tempInstallDir,
|
|
579
|
-
installedSkill: {
|
|
580
|
-
dirName: 'frontend-design',
|
|
581
|
-
name: 'frontend-design',
|
|
582
|
-
sourcePath: installedSkillDir
|
|
583
|
-
}
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
try {
|
|
587
|
-
process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__ = primary
|
|
588
|
-
await writeDocument(
|
|
589
|
-
join(worktree, '.ai/skills/app-builder/SKILL.md'),
|
|
590
|
-
[
|
|
591
|
-
'---',
|
|
592
|
-
'name: app-builder',
|
|
593
|
-
'description: Build apps',
|
|
594
|
-
'dependencies:',
|
|
595
|
-
' - anthropics/skills@frontend-design',
|
|
596
|
-
'---',
|
|
597
|
-
'Build the app.'
|
|
598
|
-
].join('\n')
|
|
599
|
-
)
|
|
600
|
-
|
|
601
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
602
|
-
cwd: worktree,
|
|
603
|
-
configs: [undefined, undefined],
|
|
604
|
-
useDefaultVibeForgeMcpServer: false
|
|
605
|
-
})
|
|
606
|
-
|
|
607
|
-
await buildAdapterAssetPlan({
|
|
608
|
-
adapter: 'opencode',
|
|
609
|
-
bundle,
|
|
610
|
-
options: {
|
|
611
|
-
skills: {
|
|
612
|
-
include: ['app-builder']
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
})
|
|
616
|
-
|
|
617
|
-
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
618
|
-
expect(dependency?.sourcePath).toContain(join(
|
|
619
|
-
primary,
|
|
620
|
-
'.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/'
|
|
621
|
-
))
|
|
622
|
-
expect(dependency?.sourcePath).not.toContain(join(worktree, '.ai/caches'))
|
|
623
|
-
} finally {
|
|
624
|
-
if (previousPrimaryWorkspace == null) {
|
|
625
|
-
delete process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__
|
|
626
|
-
} else {
|
|
627
|
-
process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__ = previousPrimaryWorkspace
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
})
|
|
631
|
-
|
|
632
|
-
it('reuses complete skill dependency caches without deleting or downloading them again', async () => {
|
|
633
|
-
const workspace = await createWorkspace()
|
|
634
|
-
|
|
635
|
-
const cachedSkillPath = join(
|
|
636
|
-
workspace,
|
|
637
|
-
'.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/SKILL.md'
|
|
638
|
-
)
|
|
639
|
-
await writeDocument(
|
|
640
|
-
cachedSkillPath,
|
|
641
|
-
'---\nname: frontend-design\ndescription: Cached UI guidance\n---\nUse the cached copy.\n'
|
|
642
|
-
)
|
|
643
|
-
await writeDocument(
|
|
644
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
645
|
-
[
|
|
646
|
-
'---',
|
|
647
|
-
'name: app-builder',
|
|
648
|
-
'description: Build apps',
|
|
649
|
-
'dependencies:',
|
|
650
|
-
' - anthropics/skills@frontend-design',
|
|
651
|
-
'---',
|
|
652
|
-
'Build the app.'
|
|
653
|
-
].join('\n')
|
|
654
|
-
)
|
|
655
|
-
|
|
656
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
657
|
-
cwd: workspace,
|
|
658
|
-
configs: [undefined, undefined],
|
|
659
|
-
useDefaultVibeForgeMcpServer: false
|
|
660
|
-
})
|
|
661
|
-
|
|
662
|
-
await buildAdapterAssetPlan({
|
|
663
|
-
adapter: 'opencode',
|
|
664
|
-
bundle,
|
|
665
|
-
options: {
|
|
666
|
-
skills: {
|
|
667
|
-
include: ['app-builder']
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
})
|
|
671
|
-
|
|
672
|
-
expect(skillsCliMocks.findSkillsCli).not.toHaveBeenCalled()
|
|
673
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
674
|
-
expect(await readFile(cachedSkillPath, 'utf8')).toContain('Use the cached copy.')
|
|
675
|
-
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
676
|
-
})
|
|
677
|
-
|
|
678
|
-
it('parses registry/source/version dependency specs and forwards them to the skills CLI installer', async () => {
|
|
679
|
-
const workspace = await createWorkspace()
|
|
680
|
-
const tempInstallDir = join(workspace, '.tmp-install-skills-cli')
|
|
681
|
-
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'frontend-design')
|
|
682
|
-
await writeDocument(
|
|
683
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
684
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse internal design tokens.\n'
|
|
685
|
-
)
|
|
686
|
-
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
687
|
-
tempDir: tempInstallDir,
|
|
688
|
-
installedSkill: {
|
|
689
|
-
dirName: 'frontend-design',
|
|
690
|
-
name: 'frontend-design',
|
|
691
|
-
sourcePath: installedSkillDir
|
|
692
|
-
}
|
|
693
|
-
})
|
|
694
|
-
|
|
695
|
-
await writeDocument(
|
|
696
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
697
|
-
[
|
|
698
|
-
'---',
|
|
699
|
-
'name: app-builder',
|
|
700
|
-
'description: Build apps',
|
|
701
|
-
'dependencies:',
|
|
702
|
-
' - https://registry.example.com@example-source/default/public@frontend-design@1.0.3',
|
|
703
|
-
'---',
|
|
704
|
-
'Build the app.'
|
|
705
|
-
].join('\n')
|
|
706
|
-
)
|
|
707
|
-
|
|
708
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
709
|
-
cwd: workspace,
|
|
710
|
-
configs: [undefined, undefined],
|
|
711
|
-
useDefaultVibeForgeMcpServer: false
|
|
712
|
-
})
|
|
713
|
-
|
|
714
|
-
await buildAdapterAssetPlan({
|
|
715
|
-
adapter: 'opencode',
|
|
716
|
-
bundle,
|
|
717
|
-
options: {
|
|
718
|
-
skills: {
|
|
719
|
-
include: ['app-builder']
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
})
|
|
723
|
-
|
|
724
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
|
|
725
|
-
registry: 'https://registry.example.com',
|
|
726
|
-
skill: 'frontend-design',
|
|
727
|
-
source: 'example-source/default/public',
|
|
728
|
-
version: '1.0.3'
|
|
729
|
-
})
|
|
730
|
-
})
|
|
731
|
-
|
|
732
494
|
it('loads workspace entities from the env-configured entities dir', async () => {
|
|
733
495
|
const workspace = await createWorkspace()
|
|
734
496
|
const previousEntitiesDir = process.env.__VF_PROJECT_AI_ENTITIES_DIR__
|
|
@@ -174,20 +174,35 @@ describe('workspace asset prompt builders', () => {
|
|
|
174
174
|
])
|
|
175
175
|
|
|
176
176
|
expect(prompt).toContain('reviewer: 负责代码审查')
|
|
177
|
-
expect(prompt).toContain('
|
|
178
|
-
expect(prompt).toContain('`
|
|
179
|
-
expect(prompt).toContain('
|
|
180
|
-
expect(prompt).toContain('
|
|
181
|
-
expect(prompt).toContain('
|
|
182
|
-
expect(prompt).toContain('
|
|
183
|
-
expect(prompt).toContain('
|
|
184
|
-
expect(prompt).toContain('
|
|
185
|
-
expect(prompt).toContain('`
|
|
186
|
-
expect(prompt).toContain('
|
|
177
|
+
expect(prompt).toContain('Agent runtime guide:')
|
|
178
|
+
expect(prompt).toContain('`vf run --input-format stream-json --output-format stream-json`')
|
|
179
|
+
expect(prompt).toContain("cat <<'JSONL' | vf run --input-format stream-json --output-format stream-json")
|
|
180
|
+
expect(prompt).toContain('"commandId":"start-planner"')
|
|
181
|
+
expect(prompt).toContain('"type":"session.start"')
|
|
182
|
+
expect(prompt).toContain('"payload":{"title":"Plan Agent Room UI fix"')
|
|
183
|
+
expect(prompt).toContain('"entity":"dev-planner"')
|
|
184
|
+
expect(prompt).toContain('"background":true')
|
|
185
|
+
expect(prompt).toContain('write one `session.start` line per child task')
|
|
186
|
+
expect(prompt).toContain('typed runtime protocol envelopes')
|
|
187
|
+
expect(prompt).toContain('do not treat dedicated `vf agent ...` subcommands as the standard integration surface')
|
|
188
|
+
expect(prompt).toContain('Read the returned `sessionId`')
|
|
189
|
+
expect(prompt).toContain('Ordinary new sessions stay session-scoped')
|
|
190
|
+
expect(prompt).toContain('Do not use MCP task tools, `vf agent ...`, legacy StartTasks')
|
|
191
|
+
expect(prompt).toContain('hand-written DB edits')
|
|
192
|
+
expect(prompt).toContain('ad-hoc TS scripts')
|
|
193
|
+
expect(prompt).toContain('server-managed host session')
|
|
194
|
+
expect(prompt).toContain('server projects runtime store metadata/events')
|
|
195
|
+
expect(prompt).toContain('`session.status` protocol command')
|
|
196
|
+
expect(prompt).toContain('`session.events` protocol command')
|
|
197
|
+
expect(prompt).toContain('`session.message` protocol command')
|
|
198
|
+
expect(prompt).toContain('[ROOM_TASK_MESSAGE]')
|
|
199
|
+
expect(prompt).toContain('`mode: interaction`')
|
|
200
|
+
expect(prompt).toContain('`waiting_input`')
|
|
201
|
+
expect(prompt).toContain('`session.submit` protocol command')
|
|
187
202
|
expect(prompt).toContain('Do not use it for ordinary follow-up instructions')
|
|
188
|
-
expect(prompt).toContain('completed or failed
|
|
189
|
-
expect(prompt).toContain('keep working in that same thread of execution')
|
|
203
|
+
expect(prompt).toContain('completed or failed sessions resume the same conversation')
|
|
190
204
|
expect(prompt).toContain('`wait`')
|
|
205
|
+
expect(prompt).not.toContain('VibeForge.StartTasks')
|
|
191
206
|
expect(prompt).not.toContain('run-tasks')
|
|
192
207
|
expect(prompt).not.toContain('需要关注变更风险')
|
|
193
208
|
expect(prompt).not.toContain('hidden')
|
|
@@ -207,14 +222,19 @@ describe('workspace asset prompt builders', () => {
|
|
|
207
222
|
|
|
208
223
|
expect(prompt).toContain('The project includes the following registered workspaces')
|
|
209
224
|
expect(prompt).toContain('Identifier: billing')
|
|
210
|
-
expect(prompt).toContain('
|
|
211
|
-
expect(prompt).toContain('
|
|
212
|
-
expect(prompt).toContain('
|
|
213
|
-
expect(prompt).toContain('
|
|
214
|
-
expect(prompt).toContain('
|
|
215
|
-
expect(prompt).toContain('
|
|
216
|
-
expect(prompt).toContain('
|
|
225
|
+
expect(prompt).toContain('workspace identifier and path')
|
|
226
|
+
expect(prompt).toContain('Agent runtime guide:')
|
|
227
|
+
expect(prompt).toContain('`vf run --input-format stream-json --output-format stream-json`')
|
|
228
|
+
expect(prompt).toContain('"payload":{"title":"Plan Agent Room UI fix"')
|
|
229
|
+
expect(prompt).toContain('"payload":{"title":"Review Agent Room UI fix"')
|
|
230
|
+
expect(prompt).toContain('payload.background: true')
|
|
231
|
+
expect(prompt).toContain('multiple subtasks')
|
|
232
|
+
expect(prompt).toContain('`session.status` protocol command')
|
|
233
|
+
expect(prompt).toContain('`session.events` protocol command')
|
|
234
|
+
expect(prompt).toContain('`session.message` protocol command')
|
|
235
|
+
expect(prompt).toContain('`session.submit`')
|
|
217
236
|
expect(prompt).toContain('Do not directly edit files inside a registered workspace')
|
|
237
|
+
expect(prompt).not.toContain('VibeForge.StartTasks')
|
|
218
238
|
})
|
|
219
239
|
|
|
220
240
|
it('builds skill route prompts without preloading content', () => {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/* eslint-disable import/first -- hoisted vitest mocks must be declared before importing the bundle entrypoint */
|
|
2
|
-
import {
|
|
3
|
-
import os from 'node:os'
|
|
4
|
-
import path, { join } from 'node:path'
|
|
2
|
+
import { join } from 'node:path'
|
|
5
3
|
|
|
6
|
-
import {
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
5
|
|
|
8
6
|
const mocks = vi.hoisted(() => ({
|
|
9
7
|
findSkillsCli: vi.fn(),
|
|
@@ -23,54 +21,15 @@ vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
|
23
21
|
|
|
24
22
|
import { buildAdapterAssetPlan, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
25
23
|
|
|
26
|
-
import { createWorkspace, writeDocument } from './test-helpers'
|
|
24
|
+
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
27
25
|
|
|
28
|
-
describe('
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const pathExists = async (targetPath: string) => {
|
|
32
|
-
try {
|
|
33
|
-
await access(targetPath)
|
|
34
|
-
return true
|
|
35
|
-
} catch {
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
beforeEach(async () => {
|
|
41
|
-
installWorkspace = await mkdtemp(path.join(os.tmpdir(), 'vf-skills-cli-dependency-'))
|
|
26
|
+
describe('materialized skill dependency resolution', () => {
|
|
27
|
+
beforeEach(() => {
|
|
42
28
|
vi.clearAllMocks()
|
|
43
29
|
})
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
await rm(installWorkspace, { recursive: true, force: true })
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('installs missing bare-name dependencies through skills CLI by default', async () => {
|
|
31
|
+
it('fails missing dependencies without downloading during runtime preparation', async () => {
|
|
50
32
|
const workspace = await createWorkspace()
|
|
51
|
-
const installedSkillDir = join(installWorkspace, '.agents', 'skills', 'frontend-design')
|
|
52
|
-
await mkdir(installedSkillDir, { recursive: true })
|
|
53
|
-
await writeFile(
|
|
54
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
55
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
mocks.findSkillsCli.mockResolvedValue([
|
|
59
|
-
{
|
|
60
|
-
installRef: 'anthropics/skills@frontend-design',
|
|
61
|
-
source: 'anthropics/skills',
|
|
62
|
-
skill: 'frontend-design'
|
|
63
|
-
}
|
|
64
|
-
])
|
|
65
|
-
mocks.installSkillsCliRefToTemp.mockResolvedValue({
|
|
66
|
-
tempDir: installWorkspace,
|
|
67
|
-
installedSkill: {
|
|
68
|
-
dirName: 'frontend-design',
|
|
69
|
-
name: 'frontend-design',
|
|
70
|
-
sourcePath: installedSkillDir
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
33
|
await writeDocument(
|
|
75
34
|
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
76
35
|
[
|
|
@@ -90,7 +49,7 @@ describe('skills CLI dependency resolution', () => {
|
|
|
90
49
|
useDefaultVibeForgeMcpServer: false
|
|
91
50
|
})
|
|
92
51
|
|
|
93
|
-
await buildAdapterAssetPlan({
|
|
52
|
+
await expect(buildAdapterAssetPlan({
|
|
94
53
|
adapter: 'opencode',
|
|
95
54
|
bundle,
|
|
96
55
|
options: {
|
|
@@ -98,39 +57,15 @@ describe('skills CLI dependency resolution', () => {
|
|
|
98
57
|
include: ['app-builder']
|
|
99
58
|
}
|
|
100
59
|
}
|
|
101
|
-
})
|
|
60
|
+
})).rejects.toThrow('Run vf skills install or vf skills update')
|
|
102
61
|
|
|
103
|
-
|
|
104
|
-
expect(
|
|
105
|
-
expect(
|
|
106
|
-
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/'
|
|
107
|
-
)
|
|
108
|
-
expect(mocks.findSkillsCli).toHaveBeenCalledWith({
|
|
109
|
-
query: 'frontend-design'
|
|
110
|
-
})
|
|
111
|
-
expect(mocks.installSkillsCliRefToTemp).toHaveBeenCalledWith({
|
|
112
|
-
installRef: 'anthropics/skills@frontend-design'
|
|
113
|
-
})
|
|
62
|
+
expect(mocks.findSkillsCli).not.toHaveBeenCalled()
|
|
63
|
+
expect(mocks.installSkillsCliRefToTemp).not.toHaveBeenCalled()
|
|
64
|
+
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
114
65
|
})
|
|
115
66
|
|
|
116
|
-
it('
|
|
67
|
+
it('uses project-materialized dependencies from .ai/skills', async () => {
|
|
117
68
|
const workspace = await createWorkspace()
|
|
118
|
-
const installedSkillDir = join(installWorkspace, '.agents', 'skills', 'frontend-design')
|
|
119
|
-
await mkdir(installedSkillDir, { recursive: true })
|
|
120
|
-
await writeFile(
|
|
121
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
122
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse internal design system.\n'
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
mocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
126
|
-
tempDir: installWorkspace,
|
|
127
|
-
installedSkill: {
|
|
128
|
-
dirName: 'frontend-design',
|
|
129
|
-
name: 'frontend-design',
|
|
130
|
-
sourcePath: installedSkillDir
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
|
|
134
69
|
await writeDocument(
|
|
135
70
|
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
136
71
|
[
|
|
@@ -138,19 +73,22 @@ describe('skills CLI dependency resolution', () => {
|
|
|
138
73
|
'name: app-builder',
|
|
139
74
|
'description: Build apps',
|
|
140
75
|
'dependencies:',
|
|
141
|
-
' -
|
|
76
|
+
' - frontend-design',
|
|
142
77
|
'---',
|
|
143
78
|
'Build the app.'
|
|
144
79
|
].join('\n')
|
|
145
80
|
)
|
|
81
|
+
await writeDocument(
|
|
82
|
+
join(workspace, '.ai/skills/frontend-design/SKILL.md'),
|
|
83
|
+
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
84
|
+
)
|
|
146
85
|
|
|
147
86
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
148
87
|
cwd: workspace,
|
|
149
88
|
configs: [undefined, undefined],
|
|
150
89
|
useDefaultVibeForgeMcpServer: false
|
|
151
90
|
})
|
|
152
|
-
|
|
153
|
-
await buildAdapterAssetPlan({
|
|
91
|
+
const plan = await buildAdapterAssetPlan({
|
|
154
92
|
adapter: 'opencode',
|
|
155
93
|
bundle,
|
|
156
94
|
options: {
|
|
@@ -160,75 +98,79 @@ describe('skills CLI dependency resolution', () => {
|
|
|
160
98
|
}
|
|
161
99
|
})
|
|
162
100
|
|
|
163
|
-
expect(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
})
|
|
101
|
+
expect(plan.overlays.filter(entry => entry.kind === 'skill').map(entry => entry.targetPath).sort()).toEqual([
|
|
102
|
+
'skills/app-builder',
|
|
103
|
+
'skills/frontend-design'
|
|
104
|
+
])
|
|
105
|
+
expect(mocks.findSkillsCli).not.toHaveBeenCalled()
|
|
169
106
|
})
|
|
170
107
|
|
|
171
|
-
it('
|
|
108
|
+
it('loads plugin dependencies from .ai/skills/.plugins through the lockfile', async () => {
|
|
172
109
|
const workspace = await createWorkspace()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
177
|
-
'---\nname: lynx-cat\ndescription: Lynx helper\n---\nDebug Lynx apps.\n'
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
mocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
181
|
-
tempDir: installWorkspace,
|
|
182
|
-
installedSkill: {
|
|
183
|
-
dirName: 'lynx-cat',
|
|
184
|
-
name: 'lynx-cat',
|
|
185
|
-
sourcePath: installedSkillDir
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
await writeDocument(
|
|
190
|
-
join(workspace, '.ai/skills/lynx-miniapp/SKILL.md'),
|
|
191
|
-
[
|
|
110
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-review', {
|
|
111
|
+
'package.json': JSON.stringify({ name: '@vibe-forge/plugin-review', version: '1.0.0' }, null, 2),
|
|
112
|
+
'skills/review-helper/SKILL.md': [
|
|
192
113
|
'---',
|
|
193
|
-
'name:
|
|
194
|
-
'description:
|
|
114
|
+
'name: review-helper',
|
|
115
|
+
'description: Review helper',
|
|
195
116
|
'dependencies:',
|
|
196
|
-
' -
|
|
117
|
+
' - shared-runtime',
|
|
197
118
|
'---',
|
|
198
|
-
'
|
|
119
|
+
'Review code.'
|
|
199
120
|
].join('\n')
|
|
121
|
+
})
|
|
122
|
+
await writeDocument(
|
|
123
|
+
join(workspace, '.ai/skills/.plugins/review/shared-runtime/SKILL.md'),
|
|
124
|
+
'---\nname: shared-runtime\ndescription: Shared runtime\n---\nShared plugin dependency.\n'
|
|
200
125
|
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
126
|
+
await writeDocument(
|
|
127
|
+
join(workspace, '.ai/skills.lock.yaml'),
|
|
128
|
+
[
|
|
129
|
+
'version: 1',
|
|
130
|
+
'pluginSkills:',
|
|
131
|
+
' review/shared-runtime:',
|
|
132
|
+
' name: shared-runtime',
|
|
133
|
+
' requested: false',
|
|
134
|
+
' pluginInstance: review',
|
|
135
|
+
' pluginInstancePath: "0"',
|
|
136
|
+
' installPath: .ai/skills/.plugins/review/shared-runtime',
|
|
137
|
+
' dependencyOf:',
|
|
138
|
+
' - plugin:review/review-helper',
|
|
139
|
+
' source: vendor/shared-skills',
|
|
140
|
+
' version: 1.0.0',
|
|
141
|
+
' hash: sha256:test',
|
|
142
|
+
' installedAt: "2026-05-13T00:00:00.000Z"'
|
|
143
|
+
].join('\n')
|
|
205
144
|
)
|
|
206
|
-
await mkdir(lockDir, { recursive: true })
|
|
207
|
-
await utimes(lockDir, new Date(Date.now() - 120_000), new Date(Date.now() - 120_000))
|
|
208
145
|
|
|
209
146
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
210
147
|
cwd: workspace,
|
|
211
|
-
configs: [
|
|
148
|
+
configs: [{
|
|
149
|
+
plugins: [{
|
|
150
|
+
id: '@vibe-forge/plugin-review',
|
|
151
|
+
scope: 'review'
|
|
152
|
+
}]
|
|
153
|
+
}, undefined],
|
|
212
154
|
useDefaultVibeForgeMcpServer: false
|
|
213
155
|
})
|
|
214
|
-
|
|
215
|
-
await buildAdapterAssetPlan({
|
|
156
|
+
const plan = await buildAdapterAssetPlan({
|
|
216
157
|
adapter: 'opencode',
|
|
217
158
|
bundle,
|
|
218
159
|
options: {
|
|
219
160
|
skills: {
|
|
220
|
-
include: ['
|
|
161
|
+
include: ['review/review-helper']
|
|
221
162
|
}
|
|
222
163
|
}
|
|
223
164
|
})
|
|
224
165
|
|
|
225
|
-
expect(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
166
|
+
expect(bundle.skills.map(asset => asset.displayName).sort()).toEqual([
|
|
167
|
+
'review/review-helper',
|
|
168
|
+
'review/shared-runtime'
|
|
169
|
+
])
|
|
170
|
+
expect(plan.overlays.filter(entry => entry.kind === 'skill').map(entry => entry.targetPath).sort()).toEqual([
|
|
171
|
+
'skills/review__review-helper',
|
|
172
|
+
'skills/review__shared-runtime'
|
|
173
|
+
])
|
|
174
|
+
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
233
175
|
})
|
|
234
176
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-forge/workspace-assets",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1-alpha.0",
|
|
4
4
|
"description": "Workspace asset resolution and adapter asset planning for Vibe Forge",
|
|
5
5
|
"imports": {
|
|
6
6
|
"#~/*.js": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"fast-glob": "^3.3.3",
|
|
30
30
|
"front-matter": "^4.0.2",
|
|
31
31
|
"js-yaml": "^4.1.1",
|
|
32
|
-
"@vibe-forge/
|
|
33
|
-
"@vibe-forge/definition-core": "3.
|
|
34
|
-
"@vibe-forge/
|
|
35
|
-
"@vibe-forge/
|
|
32
|
+
"@vibe-forge/config": "3.1.5",
|
|
33
|
+
"@vibe-forge/definition-core": "3.1.0",
|
|
34
|
+
"@vibe-forge/types": "3.1.6-alpha.0",
|
|
35
|
+
"@vibe-forge/utils": "3.1.6-alpha.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/js-yaml": "^4.0.9"
|
|
@@ -78,7 +78,7 @@ export async function buildAdapterAssetPlan(params: {
|
|
|
78
78
|
params.bundle.hookPlugins.forEach((asset) => {
|
|
79
79
|
pushDiagnostic(asset, {
|
|
80
80
|
adapter: params.adapter,
|
|
81
|
-
status:
|
|
81
|
+
status: 'native',
|
|
82
82
|
reason: params.adapter === 'claude-code'
|
|
83
83
|
? 'Mapped into the Claude Code native hooks bridge.'
|
|
84
84
|
: params.adapter === 'codex'
|
|
@@ -86,7 +86,7 @@ export async function buildAdapterAssetPlan(params: {
|
|
|
86
86
|
: params.adapter === 'gemini'
|
|
87
87
|
? 'Mapped into the Gemini native hooks bridge.'
|
|
88
88
|
: params.adapter === 'copilot'
|
|
89
|
-
? '
|
|
89
|
+
? 'Mapped into the Copilot CLI native hooks bridge.'
|
|
90
90
|
: params.adapter === 'kimi'
|
|
91
91
|
? 'Mapped into the Kimi native hooks bridge.'
|
|
92
92
|
: 'Mapped into the OpenCode native hooks bridge.'
|
package/src/bundle-internal.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
1
|
+
import { access, readFile } from 'node:fs/promises'
|
|
2
2
|
import { basename, dirname, extname, isAbsolute, resolve } from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import type { Config, Definition, Entity, PluginConfig, WorkspaceAsset, WorkspaceAssetKind } from '@vibe-forge/types'
|
|
12
12
|
import {
|
|
13
13
|
isLegacySkillsConfig,
|
|
14
|
+
readProjectSkillsLockfile,
|
|
14
15
|
resolveProjectAiBaseDir,
|
|
15
16
|
resolveProjectAiEntitiesDir,
|
|
16
17
|
resolveRelativePath
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
} from '@vibe-forge/definition-core'
|
|
36
37
|
import { ensureConfiguredProjectSkills } from './configured-skills'
|
|
37
38
|
import { HOME_BRIDGE_RESOLVED_BY } from './home-bridge'
|
|
39
|
+
import { PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY } from './plugin-skill-dependencies'
|
|
38
40
|
import { resolveConfiguredWorkspaceAssets } from './workspaces'
|
|
39
41
|
|
|
40
42
|
type DocumentAssetKind = Extract<WorkspaceAssetKind, 'rule' | 'spec' | 'entity' | 'skill'>
|
|
@@ -440,6 +442,40 @@ const scanInstanceDocuments = async (instance: ResolvedPluginInstance) => {
|
|
|
440
442
|
}
|
|
441
443
|
}
|
|
442
444
|
|
|
445
|
+
const pathExists = async (path: string) => {
|
|
446
|
+
try {
|
|
447
|
+
await access(path)
|
|
448
|
+
return true
|
|
449
|
+
} catch {
|
|
450
|
+
return false
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const scanPluginDependencySkillDocuments = async (
|
|
455
|
+
cwd: string,
|
|
456
|
+
instances: ResolvedPluginInstance[]
|
|
457
|
+
) => {
|
|
458
|
+
const lockfile = await readProjectSkillsLockfile(cwd)
|
|
459
|
+
const documents: Array<{ instance: ResolvedPluginInstance; path: string }> = []
|
|
460
|
+
for (const entry of Object.values(lockfile.pluginSkills ?? {})) {
|
|
461
|
+
const instance = instances.find(candidate => (
|
|
462
|
+
candidate.instancePath === entry.pluginInstancePath ||
|
|
463
|
+
candidate.scope === entry.pluginInstance
|
|
464
|
+
))
|
|
465
|
+
if (instance == null) continue
|
|
466
|
+
|
|
467
|
+
const skillPath = resolve(cwd, entry.installPath, 'SKILL.md')
|
|
468
|
+
if (await pathExists(skillPath)) {
|
|
469
|
+
documents.push({
|
|
470
|
+
instance,
|
|
471
|
+
path: skillPath
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return documents
|
|
477
|
+
}
|
|
478
|
+
|
|
443
479
|
const toOpenCodeOverlayEntries = (
|
|
444
480
|
kind: OpenCodeOverlayKind,
|
|
445
481
|
targetDir: 'agents' | 'commands' | 'modes' | 'plugins',
|
|
@@ -578,6 +614,7 @@ export async function collectWorkspaceAssets(params: {
|
|
|
578
614
|
])
|
|
579
615
|
const flattenedPluginInstances = flattenPluginInstances(pluginInstances)
|
|
580
616
|
const pluginScans = await Promise.all(flattenedPluginInstances.map(instance => scanInstanceDocuments(instance)))
|
|
617
|
+
const pluginDependencySkillDocs = await scanPluginDependencySkillDocuments(params.cwd, flattenedPluginInstances)
|
|
581
618
|
const pluginOverlayScans = await Promise.all(
|
|
582
619
|
flattenedPluginInstances.map(instance => scanInstanceOpenCodeOverlays(instance))
|
|
583
620
|
)
|
|
@@ -631,6 +668,16 @@ export async function collectWorkspaceAssets(params: {
|
|
|
631
668
|
await pushDocumentAssets('entity', scan.entityDocPaths, 'plugin', instance, parseEntityMarkdownDocument)
|
|
632
669
|
await pushDocumentAssets('entity', scan.entityJsonPaths, 'plugin', instance, parseEntityIndexJson)
|
|
633
670
|
}
|
|
671
|
+
for (const entry of pluginDependencySkillDocs) {
|
|
672
|
+
await pushDocumentAssets(
|
|
673
|
+
'skill',
|
|
674
|
+
[entry.path],
|
|
675
|
+
'plugin',
|
|
676
|
+
entry.instance,
|
|
677
|
+
undefined,
|
|
678
|
+
PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY
|
|
679
|
+
)
|
|
680
|
+
}
|
|
634
681
|
await pushDocumentAssets('skill', homeSkillPaths, 'workspace', undefined, undefined, HOME_BRIDGE_RESOLVED_BY)
|
|
635
682
|
|
|
636
683
|
const skills = mergeSkillAssets(skillAssets)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY = 'plugin-skill-dependency-lock'
|
package/src/prompt-builders.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveSpecIdentifier
|
|
6
6
|
} from '@vibe-forge/definition-core'
|
|
7
7
|
import type { Definition, Entity, Rule, Skill, Spec } from '@vibe-forge/types'
|
|
8
|
-
import {
|
|
8
|
+
import { resolvePromptPath } from '@vibe-forge/utils'
|
|
9
9
|
|
|
10
10
|
import { buildManagedTaskToolGuidance } from './task-tool-guidance'
|
|
11
11
|
|
|
@@ -167,7 +167,7 @@ export const generateSpecRoutePrompt = (
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export const generateEntitiesRoutePrompt = (entities: Definition<Entity>[]) => {
|
|
170
|
-
const taskToolGuidance = buildManagedTaskToolGuidance(
|
|
170
|
+
const taskToolGuidance = buildManagedTaskToolGuidance()
|
|
171
171
|
return (
|
|
172
172
|
'<system-prompt>\n' +
|
|
173
173
|
'The project includes the following entities:\n' +
|
|
@@ -181,7 +181,7 @@ export const generateEntitiesRoutePrompt = (entities: Definition<Entity>[]) => {
|
|
|
181
181
|
})
|
|
182
182
|
.join('')
|
|
183
183
|
}\n` +
|
|
184
|
-
|
|
184
|
+
'When solving user problems, you may start child runtime sessions with `vf run --input-format stream-json --output-format stream-json` and a `session.start` protocol envelope as needed; use `session.status`, `session.events`, and `wait` to track progress.\n' +
|
|
185
185
|
`${taskToolGuidance}\n` +
|
|
186
186
|
'</system-prompt>\n'
|
|
187
187
|
)
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
isRemoteRuleReference,
|
|
27
27
|
parseScopedReference
|
|
28
28
|
} from '@vibe-forge/definition-core'
|
|
29
|
+
import { PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY } from './plugin-skill-dependencies'
|
|
29
30
|
import { expandSkillAssetDependencies, expandSkillAssetDependenciesWithRemoteResolution } from './skill-dependencies'
|
|
30
31
|
|
|
31
32
|
type DocumentAssetKind = Extract<WorkspaceAssetKind, 'rule' | 'spec' | 'entity' | 'skill'>
|
|
@@ -359,6 +360,10 @@ const mergeEntityDefinitions = (
|
|
|
359
360
|
|
|
360
361
|
const uniqueAssetIds = (values: string[]) => toUniqueValues(values, value => value)
|
|
361
362
|
|
|
363
|
+
const isPluginSkillDependencyAsset = (asset: Extract<WorkspaceAsset, { kind: 'skill' }>) => (
|
|
364
|
+
asset.resolvedBy === PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY
|
|
365
|
+
)
|
|
366
|
+
|
|
362
367
|
const formatEntityCycle = (stack: EntityAsset[], asset: EntityAsset) => (
|
|
363
368
|
[...stack.slice(stack.findIndex(item => item.id === asset.id)), asset]
|
|
364
369
|
.map(item => item.displayName)
|
|
@@ -517,9 +522,10 @@ export const resolveSelectedSkillAssets = (
|
|
|
517
522
|
): Array<Extract<WorkspaceAsset, { kind: 'skill' }>> => {
|
|
518
523
|
if (selection == null) return assets
|
|
519
524
|
|
|
525
|
+
const rootAssets = assets.filter(asset => !isPluginSkillDependencyAsset(asset))
|
|
520
526
|
const included = selection.include != null && selection.include.length > 0
|
|
521
527
|
? resolveNamedAssets(assets, selection.include)
|
|
522
|
-
:
|
|
528
|
+
: rootAssets
|
|
523
529
|
const excluded = new Set(
|
|
524
530
|
resolveNamedAssets(assets, selection.exclude).map(asset => asset.id)
|
|
525
531
|
)
|
|
@@ -532,9 +538,10 @@ export const resolveSelectedSkillAssetsWithDependencies = async (
|
|
|
532
538
|
bundle: WorkspaceAssetBundle,
|
|
533
539
|
selection?: WorkspaceSkillSelection
|
|
534
540
|
): Promise<Array<Extract<WorkspaceAsset, { kind: 'skill' }>>> => {
|
|
541
|
+
const rootAssets = bundle.skills.filter(asset => !isPluginSkillDependencyAsset(asset))
|
|
535
542
|
const included = selection?.include != null && selection.include.length > 0
|
|
536
543
|
? resolveNamedAssets(bundle.skills, selection.include)
|
|
537
|
-
:
|
|
544
|
+
: rootAssets
|
|
538
545
|
const excluded = new Set(
|
|
539
546
|
resolveNamedAssets(bundle.skills, selection?.exclude).map(asset => asset.id)
|
|
540
547
|
)
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/* eslint-disable max-lines -- dependency normalization and graph expansion share the same local helpers */
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import { parseScopedReference, resolveSkillIdentifier } from '@vibe-forge/definition-core'
|
|
7
|
-
import type { Config, Definition, Skill, WorkspaceAsset } from '@vibe-forge/types'
|
|
8
|
-
import { formatSkillsSpec, parseSkillsSpec, resolveRelativePath } from '@vibe-forge/utils'
|
|
2
|
+
import { parseScopedReference } from '@vibe-forge/definition-core'
|
|
3
|
+
import type { Config, Skill, WorkspaceAsset } from '@vibe-forge/types'
|
|
4
|
+
import { formatSkillsSpec, parseSkillsSpec } from '@vibe-forge/utils'
|
|
9
5
|
|
|
10
6
|
import { HOME_BRIDGE_RESOLVED_BY } from './home-bridge'
|
|
11
|
-
import { installSkillsCliDependency } from './skills-cli-dependency'
|
|
12
7
|
|
|
13
8
|
type SkillAsset = Extract<WorkspaceAsset, { kind: 'skill' }>
|
|
14
9
|
|
|
@@ -106,39 +101,6 @@ const findSkillAssetByRef = (
|
|
|
106
101
|
return resolveUniqueSkillByName(searchableAssets, ref)
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
const resolveDisplayName = (name: string, scope?: string) => (
|
|
110
|
-
scope != null && scope.trim() !== '' ? `${scope}/${name}` : name
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
const parseFrontmatterSkill = async (path: string): Promise<Definition<Skill>> => {
|
|
114
|
-
const content = await readFile(path, 'utf-8')
|
|
115
|
-
const { body, attributes } = fm<Skill>(content)
|
|
116
|
-
return {
|
|
117
|
-
path,
|
|
118
|
-
body,
|
|
119
|
-
attributes
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const createResolvedSkillAsset = (params: {
|
|
124
|
-
cwd: string
|
|
125
|
-
definition: Definition<Skill>
|
|
126
|
-
}) => {
|
|
127
|
-
const name = resolveSkillIdentifier(params.definition.path, params.definition.attributes.name)
|
|
128
|
-
const displayName = resolveDisplayName(name)
|
|
129
|
-
return {
|
|
130
|
-
id: `skill:workspace:workspace:${displayName}:${resolveRelativePath(params.cwd, params.definition.path)}`,
|
|
131
|
-
kind: 'skill',
|
|
132
|
-
name,
|
|
133
|
-
displayName,
|
|
134
|
-
origin: 'workspace',
|
|
135
|
-
sourcePath: params.definition.path,
|
|
136
|
-
payload: {
|
|
137
|
-
definition: params.definition
|
|
138
|
-
}
|
|
139
|
-
} satisfies SkillAsset
|
|
140
|
-
}
|
|
141
|
-
|
|
142
104
|
export const normalizeSkillDependency = (value: unknown): NormalizedSkillDependency | undefined => {
|
|
143
105
|
const stringValue = asNonEmptyString(value)
|
|
144
106
|
if (stringValue != null) return parseSkillsSpec(stringValue)
|
|
@@ -230,8 +192,6 @@ export const expandSkillAssetDependenciesWithRemoteResolution = async (
|
|
|
230
192
|
) => {
|
|
231
193
|
const selected: SkillAsset[] = []
|
|
232
194
|
const seen = new Set<string>()
|
|
233
|
-
const fetchedDependencyRefs = new Set<string>()
|
|
234
|
-
|
|
235
195
|
const removeSupersededHomeBridgeSkill = (displayName: string) => {
|
|
236
196
|
removeHomeBridgeSkillDuplicates(params.allAssets, displayName)
|
|
237
197
|
removeHomeBridgeSkillDuplicates(params.skillAssets, displayName)
|
|
@@ -239,54 +199,6 @@ export const expandSkillAssetDependenciesWithRemoteResolution = async (
|
|
|
239
199
|
removeHomeBridgeSkillDuplicates(selected, displayName)
|
|
240
200
|
}
|
|
241
201
|
|
|
242
|
-
const installDependencyAsset = async (
|
|
243
|
-
dependency: NormalizedSkillDependency,
|
|
244
|
-
currentInstancePath?: string
|
|
245
|
-
) => {
|
|
246
|
-
const fetchKey = dependency.ref
|
|
247
|
-
if (!fetchedDependencyRefs.has(fetchKey)) {
|
|
248
|
-
fetchedDependencyRefs.add(fetchKey)
|
|
249
|
-
const installed = await installSkillsCliDependency({
|
|
250
|
-
cwd: params.cwd,
|
|
251
|
-
configs: params.configs,
|
|
252
|
-
dependency
|
|
253
|
-
})
|
|
254
|
-
const definition = await parseFrontmatterSkill(installed.skillPath)
|
|
255
|
-
const dependencyAsset = createResolvedSkillAsset({
|
|
256
|
-
cwd: params.cwd,
|
|
257
|
-
definition
|
|
258
|
-
})
|
|
259
|
-
const existingAsset = findSkillDependencyAsset(
|
|
260
|
-
params.skillAssets,
|
|
261
|
-
dependency,
|
|
262
|
-
currentInstancePath,
|
|
263
|
-
{ includeHomeBridge: false }
|
|
264
|
-
) ??
|
|
265
|
-
params.skillAssets.find(existing => (
|
|
266
|
-
existing.resolvedBy !== HOME_BRIDGE_RESOLVED_BY &&
|
|
267
|
-
existing.displayName === dependencyAsset.displayName
|
|
268
|
-
))
|
|
269
|
-
if (existingAsset != null) {
|
|
270
|
-
removeSupersededHomeBridgeSkill(existingAsset.displayName)
|
|
271
|
-
return existingAsset
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
removeSupersededHomeBridgeSkill(dependencyAsset.displayName)
|
|
275
|
-
params.allAssets.push(dependencyAsset)
|
|
276
|
-
params.skillAssets.push(dependencyAsset)
|
|
277
|
-
return dependencyAsset
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// After the first fetch attempt, reuse whichever asset is now visible in the
|
|
281
|
-
// skill set: a newly installed registry skill, or a home-bridge fallback
|
|
282
|
-
// that was accepted by an earlier plain-name dependency resolution.
|
|
283
|
-
const resolvedAsset = findSkillDependencyAsset(params.skillAssets, dependency, currentInstancePath)
|
|
284
|
-
if (resolvedAsset != null && resolvedAsset.resolvedBy !== HOME_BRIDGE_RESOLVED_BY) {
|
|
285
|
-
removeSupersededHomeBridgeSkill(resolvedAsset.displayName)
|
|
286
|
-
}
|
|
287
|
-
return resolvedAsset
|
|
288
|
-
}
|
|
289
|
-
|
|
290
202
|
const addAsset = async (asset: SkillAsset): Promise<void> => {
|
|
291
203
|
if (params.excludedIds?.has(asset.id)) return
|
|
292
204
|
if (seen.has(asset.id)) return
|
|
@@ -311,25 +223,15 @@ export const expandSkillAssetDependenciesWithRemoteResolution = async (
|
|
|
311
223
|
continue
|
|
312
224
|
}
|
|
313
225
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const dependencyAsset = await installDependencyAsset(dependency, asset.instancePath).catch((error: unknown) => {
|
|
318
|
-
if (
|
|
319
|
-
localOrBridgedDependency != null &&
|
|
320
|
-
dependency.source == null
|
|
321
|
-
) {
|
|
322
|
-
return localOrBridgedDependency
|
|
323
|
-
}
|
|
324
|
-
throw error
|
|
325
|
-
}) ?? (
|
|
326
|
-
dependency.source == null
|
|
327
|
-
? localOrBridgedDependency
|
|
328
|
-
: undefined
|
|
329
|
-
)
|
|
226
|
+
const dependencyAsset = dependency.source == null
|
|
227
|
+
? localOrBridgedDependency
|
|
228
|
+
: undefined
|
|
330
229
|
|
|
331
230
|
if (dependencyAsset == null) {
|
|
332
|
-
throw new Error(
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Skill dependency ${dependency.ref} declared by ${asset.displayName} is missing. ` +
|
|
233
|
+
'Run vf skills install or vf skills update to materialize project skill dependencies.'
|
|
234
|
+
)
|
|
333
235
|
}
|
|
334
236
|
await addAsset(dependencyAsset)
|
|
335
237
|
}
|
|
@@ -14,17 +14,33 @@ import {
|
|
|
14
14
|
withInstallLock
|
|
15
15
|
} from './skills-cli-dependency-helpers'
|
|
16
16
|
|
|
17
|
+
const resolveAutoDownloadDependenciesEnabled = (
|
|
18
|
+
projectConfig: Config | undefined,
|
|
19
|
+
userConfig: Config | undefined
|
|
20
|
+
) => userConfig?.skills?.autoDownloadDependencies ?? projectConfig?.skills?.autoDownloadDependencies ?? true
|
|
21
|
+
|
|
17
22
|
export const installSkillsCliDependency = async (params: {
|
|
18
23
|
cwd: string
|
|
19
24
|
configs: [Config?, Config?]
|
|
20
25
|
dependency: NormalizedSkillDependency
|
|
21
26
|
}) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const [projectConfig, userConfig] = params.configs
|
|
28
|
+
const autoDownloadDependenciesEnabled = resolveAutoDownloadDependenciesEnabled(projectConfig, userConfig)
|
|
29
|
+
const resolvedTarget = await (async () => {
|
|
30
|
+
if (params.dependency.source != null) {
|
|
31
|
+
return {
|
|
32
|
+
skill: params.dependency.name,
|
|
33
|
+
source: params.dependency.source
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!autoDownloadDependenciesEnabled) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Skill dependency automatic downloads are disabled; cannot resolve ${params.dependency.ref} without a source`
|
|
40
|
+
)
|
|
26
41
|
}
|
|
27
|
-
|
|
42
|
+
|
|
43
|
+
return await (async () => {
|
|
28
44
|
const searchResults = await findSkillsCli({
|
|
29
45
|
registry: params.dependency.registry,
|
|
30
46
|
query: params.dependency.name
|
|
@@ -40,6 +56,7 @@ export const installSkillsCliDependency = async (params: {
|
|
|
40
56
|
source: selected.source
|
|
41
57
|
}
|
|
42
58
|
})()
|
|
59
|
+
})()
|
|
43
60
|
|
|
44
61
|
const installDir = buildInstallDir({
|
|
45
62
|
cwd: params.cwd,
|
|
@@ -58,6 +75,10 @@ export const installSkillsCliDependency = async (params: {
|
|
|
58
75
|
}
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
if (!autoDownloadDependenciesEnabled) {
|
|
79
|
+
throw new Error(`Skill dependency automatic downloads are disabled; cache not found for ${params.dependency.ref}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
const tempInstallDir = `${installDir}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
62
83
|
await rm(tempInstallDir, { recursive: true, force: true })
|
|
63
84
|
await mkdir(tempInstallDir, { recursive: true })
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
export const buildManagedTaskToolGuidance = (
|
|
1
|
+
export const buildManagedTaskToolGuidance = () => {
|
|
2
2
|
return [
|
|
3
|
-
'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
'-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'
|
|
3
|
+
'Agent runtime guide:',
|
|
4
|
+
'- Use unified CLI protocol mode, `vf run --input-format stream-json --output-format stream-json`, to start a child runtime session when the work should run in a separate entity or continue independently from the current turn.',
|
|
5
|
+
'- Send typed runtime protocol envelopes such as `session.start`, `session.message`, `session.status`, `session.events`, `session.submit`, and `session.stop`; do not treat dedicated `vf agent ...` subcommands as the standard integration surface.',
|
|
6
|
+
'- Ordinary new sessions stay session-scoped. A room is created or discovered only when a unified CLI runtime protocol start command launches a child runtime session from a server-managed host session and the server projects runtime store metadata/events.',
|
|
7
|
+
'- Do not use MCP task tools, `vf agent ...`, legacy StartTasks, hand-written DB edits, or ad-hoc TS scripts as the task consumer surface. Use CLI protocol mode and the runtime protocol/store for start, status, events, follow-up messages, input submission, and cancellation.',
|
|
8
|
+
'- Server-managed host sessions inject the current adapter, model, effort, and permission mode as runtime protocol defaults. Omit these fields to inherit the host selection, or set them explicitly only when a child task must use a different runtime profile.',
|
|
9
|
+
'- Copyable JSONL example; write one `session.start` line per child task, and use multiple lines for multiple subtasks:',
|
|
10
|
+
'```bash',
|
|
11
|
+
"cat <<'JSONL' | vf run --input-format stream-json --output-format stream-json",
|
|
12
|
+
'{"commandId":"start-planner","type":"session.start","payload":{"title":"Plan Agent Room UI fix","message":"Plan the frontend changes and tests for the Agent Room UI fix.","entity":"dev-planner","background":true},"title":"Plan Agent Room UI fix","message":"Plan the frontend changes and tests for the Agent Room UI fix.","entity":"dev-planner","background":true}',
|
|
13
|
+
'{"commandId":"start-reviewer","type":"session.start","payload":{"title":"Review Agent Room UI fix","message":"Review the implemented Agent Room UI fix for regressions and missing tests.","entity":"dev-reviewer","background":true},"title":"Review Agent Room UI fix","message":"Review the implemented Agent Room UI fix for regressions and missing tests.","entity":"dev-reviewer","background":true}',
|
|
14
|
+
'JSONL',
|
|
15
|
+
'```',
|
|
16
|
+
'- Keep `payload.title`, `payload.message`, `payload.entity`, and `payload.background: true` explicit in each start envelope. The mirrored top-level fields make the JSONL executable by the current `vf run` protocol reader.',
|
|
17
|
+
'- Include a short `title` when the task prompt is long; it becomes the child session title and room run label. Put any room or workspace context in the title and initial message.',
|
|
18
|
+
'- Read the returned `sessionId` and use it for follow-up protocol commands. Read the latest runtime snapshot from the runtime store or a `session.status` protocol command, and read progress from runtime events or a `session.events` protocol command.',
|
|
19
|
+
'- Use a follow/read-events workflow when you need to watch progress instead of repeatedly restarting work.',
|
|
20
|
+
'- Use a `session.message` protocol command to give an existing session another instruction. Running sessions continue immediately; completed or failed sessions resume the same conversation when the runtime allows resume.',
|
|
21
|
+
'- When the chat UI sends a `[ROOM_TASK_MESSAGE] ... [/ROOM_TASK_MESSAGE]` block, treat it as a runtime relay envelope instead of ordinary prose. Parse the `sessionId` or legacy `taskId`, `message`, and optional `mode` / `request` fields. If the envelope indicates `mode: interaction`, or runtime status shows `waiting_input` / pending input, use a `session.submit` protocol command. Otherwise use `session.message`. Do not reply inline instead of routing the relay.',
|
|
22
|
+
'- Use `session.submit` only when a runtime session is waiting for an explicit input or approval request. Do not use it for ordinary follow-up instructions.',
|
|
23
|
+
'- Use a `session.stop` protocol command for graceful cancellation and set `mode` to `force` only when stop cannot recover the session.',
|
|
24
|
+
'- Compatibility aliases such as `vf agent start/status/events/send/submit/stop` may exist for debugging or legacy scripts, but they are not the primary guidance for new agent workflows.',
|
|
25
|
+
'- When a session is still making progress, use `wait` between checks and inspect status/events instead of starting a replacement session.'
|
|
12
26
|
].join('\n')
|
|
13
27
|
}
|
package/src/workspace-prompt.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WorkspaceDefinitionPayload } from '@vibe-forge/types'
|
|
2
|
-
import {
|
|
2
|
+
import { resolvePromptPath } from '@vibe-forge/utils'
|
|
3
3
|
|
|
4
4
|
import { buildManagedTaskToolGuidance } from './task-tool-guidance'
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ export const generateWorkspaceRoutePrompt = (
|
|
|
8
8
|
workspaces: WorkspaceDefinitionPayload[]
|
|
9
9
|
) => {
|
|
10
10
|
if (workspaces.length === 0) return ''
|
|
11
|
-
const taskToolGuidance = buildManagedTaskToolGuidance(
|
|
11
|
+
const taskToolGuidance = buildManagedTaskToolGuidance()
|
|
12
12
|
|
|
13
13
|
const workspaceList = workspaces
|
|
14
14
|
.map((workspace) => {
|
|
@@ -25,7 +25,7 @@ export const generateWorkspaceRoutePrompt = (
|
|
|
25
25
|
'<system-prompt>\n' +
|
|
26
26
|
'The project includes the following registered workspaces:\n' +
|
|
27
27
|
`${workspaceList}\n` +
|
|
28
|
-
|
|
28
|
+
'When a user request targets one of these workspaces, start a child runtime session with `vf run --input-format stream-json --output-format stream-json` and a `session.start` envelope; include the workspace identifier and path in the title and message. ' +
|
|
29
29
|
'Do not directly edit files inside a registered workspace from the current session unless the user explicitly asks this session to work in that directory.\n' +
|
|
30
30
|
`${taskToolGuidance}\n` +
|
|
31
31
|
'</system-prompt>\n'
|