@haoyiyin/workflow 0.2.11 → 0.3.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/src/agents/contracts/implementer.d.ts +29 -0
- package/dist/src/agents/contracts/implementer.d.ts.map +1 -0
- package/dist/src/agents/contracts/implementer.js +94 -0
- package/dist/src/agents/contracts/implementer.js.map +1 -0
- package/dist/src/agents/contracts/index.d.ts +11 -0
- package/dist/src/agents/contracts/index.d.ts.map +1 -0
- package/dist/src/agents/contracts/index.js +11 -0
- package/dist/src/agents/contracts/index.js.map +1 -0
- package/dist/src/agents/contracts/planner.d.ts +25 -0
- package/dist/src/agents/contracts/planner.d.ts.map +1 -0
- package/dist/src/agents/contracts/planner.js +107 -0
- package/dist/src/agents/contracts/planner.js.map +1 -0
- package/dist/src/agents/contracts/router.d.ts +24 -0
- package/dist/src/agents/contracts/router.d.ts.map +1 -0
- package/dist/src/agents/contracts/router.js +137 -0
- package/dist/src/agents/contracts/router.js.map +1 -0
- package/dist/src/agents/contracts/verifier.d.ts +27 -0
- package/dist/src/agents/contracts/verifier.d.ts.map +1 -0
- package/dist/src/agents/contracts/verifier.js +115 -0
- package/dist/src/agents/contracts/verifier.js.map +1 -0
- package/dist/src/agents/dispatcher.d.ts +94 -51
- package/dist/src/agents/dispatcher.d.ts.map +1 -1
- package/dist/src/agents/dispatcher.js +207 -164
- package/dist/src/agents/dispatcher.js.map +1 -1
- package/dist/src/persistence/index.d.ts +4 -2
- package/dist/src/persistence/index.d.ts.map +1 -1
- package/dist/src/persistence/index.js +4 -1
- package/dist/src/persistence/index.js.map +1 -1
- package/dist/src/persistence/plan-md.d.ts +3 -2
- package/dist/src/persistence/plan-md.d.ts.map +1 -1
- package/dist/src/persistence/plan-md.js +47 -15
- package/dist/src/persistence/plan-md.js.map +1 -1
- package/dist/src/persistence/state-md.d.ts +2 -0
- package/dist/src/persistence/state-md.d.ts.map +1 -1
- package/dist/src/persistence/state-md.js +40 -22
- package/dist/src/persistence/state-md.js.map +1 -1
- package/dist/src/persistence/types.d.ts +35 -39
- package/dist/src/persistence/types.d.ts.map +1 -1
- package/dist/src/pi-extension.d.ts +4 -3
- package/dist/src/pi-extension.d.ts.map +1 -1
- package/dist/src/pi-extension.js +36 -67
- package/dist/src/pi-extension.js.map +1 -1
- package/dist/src/router/namespace/core/intent-router.d.ts +24 -0
- package/dist/src/router/namespace/core/intent-router.d.ts.map +1 -0
- package/dist/src/router/namespace/core/intent-router.js +190 -0
- package/dist/src/router/namespace/core/intent-router.js.map +1 -0
- package/dist/src/router/namespace/core/lifecycle-router.d.ts +28 -0
- package/dist/src/router/namespace/core/lifecycle-router.d.ts.map +1 -0
- package/dist/src/router/namespace/core/lifecycle-router.js +132 -0
- package/dist/src/router/namespace/core/lifecycle-router.js.map +1 -0
- package/dist/src/router/namespace/core/state-router.d.ts +32 -0
- package/dist/src/router/namespace/core/state-router.d.ts.map +1 -0
- package/dist/src/router/namespace/core/state-router.js +157 -0
- package/dist/src/router/namespace/core/state-router.js.map +1 -0
- package/dist/src/router/namespace/domain/code-router.d.ts +26 -0
- package/dist/src/router/namespace/domain/code-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/code-router.js +171 -0
- package/dist/src/router/namespace/domain/code-router.js.map +1 -0
- package/dist/src/router/namespace/domain/debug-router.d.ts +25 -0
- package/dist/src/router/namespace/domain/debug-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/debug-router.js +139 -0
- package/dist/src/router/namespace/domain/debug-router.js.map +1 -0
- package/dist/src/router/namespace/domain/plan-router.d.ts +29 -0
- package/dist/src/router/namespace/domain/plan-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/plan-router.js +160 -0
- package/dist/src/router/namespace/domain/plan-router.js.map +1 -0
- package/dist/src/router/namespace/domain/review-router.d.ts +24 -0
- package/dist/src/router/namespace/domain/review-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/review-router.js +116 -0
- package/dist/src/router/namespace/domain/review-router.js.map +1 -0
- package/dist/src/router/namespace/index.d.ts +19 -0
- package/dist/src/router/namespace/index.d.ts.map +1 -0
- package/dist/src/router/namespace/index.js +22 -0
- package/dist/src/router/namespace/index.js.map +1 -0
- package/dist/src/router/namespace/registry.d.ts +67 -0
- package/dist/src/router/namespace/registry.d.ts.map +1 -0
- package/dist/src/router/namespace/registry.js +197 -0
- package/dist/src/router/namespace/registry.js.map +1 -0
- package/dist/src/router/namespace/types.d.ts +124 -0
- package/dist/src/router/namespace/types.d.ts.map +1 -0
- package/dist/src/router/namespace/types.js +20 -0
- package/dist/src/router/namespace/types.js.map +1 -0
- package/dist/src/router/namespace/utility/fallback-router.d.ts +28 -0
- package/dist/src/router/namespace/utility/fallback-router.d.ts.map +1 -0
- package/dist/src/router/namespace/utility/fallback-router.js +88 -0
- package/dist/src/router/namespace/utility/fallback-router.js.map +1 -0
- package/dist/src/router/namespace/utility/quick-task-router.d.ts +28 -0
- package/dist/src/router/namespace/utility/quick-task-router.d.ts.map +1 -0
- package/dist/src/router/namespace/utility/quick-task-router.js +99 -0
- package/dist/src/router/namespace/utility/quick-task-router.js.map +1 -0
- package/dist/src/router/namespace/utility/research-router.d.ts +24 -0
- package/dist/src/router/namespace/utility/research-router.d.ts.map +1 -0
- package/dist/src/router/namespace/utility/research-router.js +84 -0
- package/dist/src/router/namespace/utility/research-router.js.map +1 -0
- package/dist/src/skills/agents-md/index.js +2 -2
- package/dist/src/skills/agents-md/index.js.map +1 -1
- package/dist/src/skills/execute-plan/index.d.ts +45 -65
- package/dist/src/skills/execute-plan/index.d.ts.map +1 -1
- package/dist/src/skills/execute-plan/index.js +325 -551
- package/dist/src/skills/execute-plan/index.js.map +1 -1
- package/dist/src/skills/index.d.ts +1 -0
- package/dist/src/skills/index.d.ts.map +1 -1
- package/dist/src/skills/index.js +1 -0
- package/dist/src/skills/index.js.map +1 -1
- package/dist/src/skills/quick-task/index.d.ts +4 -4
- package/dist/src/skills/quick-task/index.js +1 -1
- package/dist/src/skills/quick-task/index.js.map +1 -1
- package/dist/src/skills/review-diff/index.d.ts +6 -6
- package/dist/src/skills/review-diff/index.js +1 -1
- package/dist/src/skills/review-diff/index.js.map +1 -1
- package/dist/src/skills/router/index.d.ts +101 -0
- package/dist/src/skills/router/index.d.ts.map +1 -0
- package/dist/src/skills/router/index.js +450 -0
- package/dist/src/skills/router/index.js.map +1 -0
- package/dist/src/skills/router/types.d.ts +79 -0
- package/dist/src/skills/router/types.d.ts.map +1 -0
- package/dist/src/skills/router/types.js +8 -0
- package/dist/src/skills/router/types.js.map +1 -0
- package/dist/src/skills/systematic-debugging/index.js +1 -1
- package/dist/src/skills/systematic-debugging/index.js.map +1 -1
- package/dist/src/skills/tdd/index.d.ts +14 -14
- package/dist/src/skills/tdd/index.js +1 -1
- package/dist/src/skills/tdd/index.js.map +1 -1
- package/dist/src/skills/to-plan/index-enhanced.d.ts +4 -4
- package/dist/src/skills/to-plan/index-enhanced.d.ts.map +1 -1
- package/dist/src/skills/to-plan/index-enhanced.js +3 -5
- package/dist/src/skills/to-plan/index-enhanced.js.map +1 -1
- package/dist/src/skills/to-plan/index.d.ts +24 -91
- package/dist/src/skills/to-plan/index.d.ts.map +1 -1
- package/dist/src/skills/to-plan/index.js +214 -409
- package/dist/src/skills/to-plan/index.js.map +1 -1
- package/package.json +3 -5
- package/scripts/postinstall.js +42 -47
- package/src/agents/contracts/implementer.ts +122 -0
- package/src/agents/contracts/index.ts +27 -0
- package/src/agents/contracts/planner.ts +129 -0
- package/src/agents/contracts/router.ts +168 -0
- package/src/agents/contracts/verifier.ts +137 -0
- package/src/agents/dispatcher.ts +387 -362
- package/src/persistence/index.ts +10 -4
- package/src/persistence/plan-md.ts +52 -18
- package/src/persistence/state-md.ts +45 -23
- package/src/persistence/types.ts +37 -40
- package/src/pi-extension.ts +38 -76
- package/src/router/namespace/README.md +127 -0
- package/src/router/namespace/core/intent-router.ts +221 -0
- package/src/router/namespace/core/lifecycle-router.ts +156 -0
- package/src/router/namespace/core/state-router.ts +192 -0
- package/src/router/namespace/domain/code-router.ts +202 -0
- package/src/router/namespace/domain/debug-router.ts +167 -0
- package/src/router/namespace/domain/plan-router.ts +196 -0
- package/src/router/namespace/domain/review-router.ts +142 -0
- package/src/router/namespace/index.ts +84 -0
- package/src/router/namespace/registry.ts +242 -0
- package/src/router/namespace/types.ts +182 -0
- package/src/router/namespace/utility/fallback-router.ts +107 -0
- package/src/router/namespace/utility/quick-task-router.ts +121 -0
- package/src/router/namespace/utility/research-router.ts +105 -0
- package/src/skills/agents-md/index.ts +2 -2
- package/src/skills/execute-plan/index.ts +419 -673
- package/src/skills/index.ts +1 -0
- package/src/skills/quick-task/index.ts +1 -1
- package/src/skills/review-diff/index.ts +1 -1
- package/src/skills/router/SKILL.md +81 -0
- package/src/skills/router/index.ts +577 -0
- package/src/skills/router/types.ts +90 -0
- package/src/skills/systematic-debugging/index.ts +1 -1
- package/src/skills/tdd/index.ts +1 -1
- package/src/skills/to-plan/index-enhanced.ts +3 -5
- package/src/skills/to-plan/index.ts +231 -502
- package/dist/src/extension/classifier.d.ts +0 -18
- package/dist/src/extension/classifier.d.ts.map +0 -1
- package/dist/src/extension/classifier.js +0 -143
- package/dist/src/extension/classifier.js.map +0 -1
- package/src/extension/classifier.ts +0 -160
package/src/skills/index.ts
CHANGED
|
@@ -11,3 +11,4 @@ export { reviewDiffSkill, ReviewDiffSkill } from './review-diff/index.js'
|
|
|
11
11
|
export { tddSkill, TDDSkill } from './tdd/index.js'
|
|
12
12
|
export { systematicDebuggingSkill, SystematicDebuggingSkill } from './systematic-debugging/index.js'
|
|
13
13
|
export { agentsMdSkill, AgentsMdSkill } from './agents-md/index.js'
|
|
14
|
+
export { routerSkill, RouterSkill } from './router/index.js'
|
|
@@ -134,7 +134,7 @@ export class QuickTaskSkill extends Skill<QuickTaskInput, QuickTaskOutput> {
|
|
|
134
134
|
context: SkillContext,
|
|
135
135
|
): Promise<QuickTaskOutput> {
|
|
136
136
|
const { config, logger } = context
|
|
137
|
-
const dispatcher = createDispatcher(logger)
|
|
137
|
+
const dispatcher = createDispatcher({ logger })
|
|
138
138
|
const guard = createMainAgentGuard({}, logger)
|
|
139
139
|
let totalTokens = 0
|
|
140
140
|
|
|
@@ -213,7 +213,7 @@ export class ReviewDiffSkill extends Skill<ReviewDiffInput, ReviewDiffOutput> {
|
|
|
213
213
|
context: SkillContext,
|
|
214
214
|
): Promise<ReviewDiffOutput> {
|
|
215
215
|
const { config, logger } = context
|
|
216
|
-
const dispatcher = createDispatcher(logger)
|
|
216
|
+
const dispatcher = createDispatcher({ logger })
|
|
217
217
|
const guard = createMainAgentGuard({}, logger)
|
|
218
218
|
|
|
219
219
|
guard.activateEmbargo()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router
|
|
3
|
+
description: |
|
|
4
|
+
Universal entry point for ALL user messages. Routes questions, commands, requests, tasks — anything the user says.
|
|
5
|
+
Examples: "fix typo", "plan feature", "what's the weather", "debug error", "review PR", "implement auth", "rename file", "hello", "help".
|
|
6
|
+
Handles: planning, coding, debugging, reviewing, researching, quick tasks, TDD, documentation.
|
|
7
|
+
Always trigger this skill for any chat message, question, or command.
|
|
8
|
+
requires: []
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
Router is the **mandatory entry point** for ALL user requests. It dispatches to the correct target skill via 5-layer GSD routing.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 5-Layer GSD Routing
|
|
18
|
+
|
|
19
|
+
### Layer 1: State Check
|
|
20
|
+
- Read `.pi/state/STATE.md` for current workflow state
|
|
21
|
+
- Phase: `idle`, `planning`, `executing`, `verifying`, `completed`, `failed`
|
|
22
|
+
|
|
23
|
+
### Layer 2: Lifecycle Gate
|
|
24
|
+
- Check PLAN.md existence and status
|
|
25
|
+
- If plan exists → execute-plan
|
|
26
|
+
- If no plan + complex request → to-plan
|
|
27
|
+
|
|
28
|
+
### Layer 3: Intent Classification
|
|
29
|
+
| Signal | Target |
|
|
30
|
+
|--------|--------|
|
|
31
|
+
| plan, design, architecture | to-plan |
|
|
32
|
+
| implement, execute, build | execute-plan |
|
|
33
|
+
| fix typo, rename, quick | quick-task |
|
|
34
|
+
| bug, error, failing | systematic-debugging |
|
|
35
|
+
| review, audit | review-diff |
|
|
36
|
+
| test, TDD | tdd |
|
|
37
|
+
| weather, time, price, search, question | quick-task |
|
|
38
|
+
|
|
39
|
+
### Layer 4: Semantic Analysis
|
|
40
|
+
- Trivial (<30 chars, single change) → quick-task
|
|
41
|
+
- Complex (multi-file, architectural) → to-plan
|
|
42
|
+
|
|
43
|
+
### Layer 5: Confidence
|
|
44
|
+
- >0.8 → route directly
|
|
45
|
+
- <0.5 → to-plan (exploration)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Target Skills
|
|
50
|
+
|
|
51
|
+
| Skill | When |
|
|
52
|
+
|-------|------|
|
|
53
|
+
| to-plan | Complex feature, unknown scope |
|
|
54
|
+
| execute-plan | Active plan exists |
|
|
55
|
+
| quick-task | Trivial change, questions, search |
|
|
56
|
+
| review-diff | Code review |
|
|
57
|
+
| systematic-debugging | Bug investigation |
|
|
58
|
+
| tdd | Test-first development |
|
|
59
|
+
| agents-md | Documentation |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Workflow
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
User Request → Router (this skill)
|
|
67
|
+
↓ reads STATE.md + PLAN.md
|
|
68
|
+
↓ 5-layer routing
|
|
69
|
+
↓ returns { targetSkill, confidence, reasoning }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Error Handling
|
|
75
|
+
|
|
76
|
+
| Error | Action |
|
|
77
|
+
|-------|--------|
|
|
78
|
+
| STATE.md missing | Assume idle phase |
|
|
79
|
+
| PLAN.md missing | Assume planning needed |
|
|
80
|
+
| Router fails | Default to quick-task |
|
|
81
|
+
| Confidence <0.3 | Route to to-plan |
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Skill - 5-Layer Routing Logic
|
|
3
|
+
*
|
|
4
|
+
* Layer 1: Parse & Classify - Extract intent and entities from user request
|
|
5
|
+
* Layer 2: State-Aware Scoring - Factor in project phase and execution state
|
|
6
|
+
* Layer 3: Skill Matching - Score each skill against request and state
|
|
7
|
+
* Layer 4: Confidence Calibration - Adjust scores based on history and context
|
|
8
|
+
* Layer 5: Selection & Output - Return structured routing decision
|
|
9
|
+
*
|
|
10
|
+
* This skill reads from STATE.md and PLAN.md to make context-aware decisions.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from 'zod'
|
|
14
|
+
import { Skill } from '../skill.js'
|
|
15
|
+
import type { SkillContext } from '../types.js'
|
|
16
|
+
import { StateMdManager } from '../../persistence/state-md.js'
|
|
17
|
+
import { PlanMdManager } from '../../persistence/plan-md.js'
|
|
18
|
+
import { createDispatcher } from '../../agents/dispatcher.js'
|
|
19
|
+
import type { SubagentConfig, SubagentContract, SubagentResult } from '../../agents/types.js'
|
|
20
|
+
import type { WorkflowState } from '../../persistence/types.js'
|
|
21
|
+
import type { RouterInput, RouterOutput, ProjectState, ProjectPhase, RoutingAlternative } from './types.js'
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Schemas
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const ConversationHistoryItemSchema = z.object({
|
|
28
|
+
role: z.enum(['user', 'assistant']),
|
|
29
|
+
content: z.string(),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const RouterInputSchema = z.object({
|
|
33
|
+
userRequest: z.string().min(1, 'User request is required'),
|
|
34
|
+
conversationHistory: z.array(ConversationHistoryItemSchema).optional(),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const RouterOutputSchema = z.object({
|
|
38
|
+
selectedSkill: z.string(),
|
|
39
|
+
confidence: z.number().min(0).max(1),
|
|
40
|
+
reasoning: z.string(),
|
|
41
|
+
projectState: z.object({
|
|
42
|
+
phase: z.enum(['discovery', 'planning', 'execution', 'verification', 'completed', 'idle']),
|
|
43
|
+
hasPlan: z.boolean(),
|
|
44
|
+
isExecuting: z.boolean(),
|
|
45
|
+
completedTasks: z.number(),
|
|
46
|
+
totalTasks: z.number(),
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
type RouterInputType = z.infer<typeof RouterInputSchema>
|
|
51
|
+
type RouterOutputType = z.infer<typeof RouterOutputSchema>
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// 5-Layer Router Types
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
interface Layer1Classification {
|
|
58
|
+
intent: string
|
|
59
|
+
entities: string[]
|
|
60
|
+
complexity: 'low' | 'medium' | 'high'
|
|
61
|
+
urgency: 'low' | 'medium' | 'high'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface Layer2StateFactors {
|
|
65
|
+
phaseWeight: number
|
|
66
|
+
executionWeight: number
|
|
67
|
+
completionWeight: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Layer3SkillScore {
|
|
71
|
+
skillName: string
|
|
72
|
+
baseScore: number
|
|
73
|
+
stateAdjustedScore: number
|
|
74
|
+
matches: string[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface Layer4Calibration {
|
|
78
|
+
confidenceAdjustment: number
|
|
79
|
+
historyBonus: number
|
|
80
|
+
contextPenalty: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface Layer5Decision {
|
|
84
|
+
selectedSkill: string
|
|
85
|
+
finalConfidence: number
|
|
86
|
+
reasoning: string
|
|
87
|
+
alternatives: RoutingAlternative[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Available Skills by Phase
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
const SKILLS_BY_PHASE: Record<ProjectPhase, string[]> = {
|
|
95
|
+
discovery: ['to-plan', 'research', 'agents-md'],
|
|
96
|
+
planning: ['to-plan', 'research', 'review-diff'],
|
|
97
|
+
execution: ['execute-plan', 'quick-task', 'tdd', 'systematic-debugging'],
|
|
98
|
+
verification: ['review-diff', 'systematic-debugging', 'agents-md'],
|
|
99
|
+
completed: ['review-diff', 'agents-md', 'research'],
|
|
100
|
+
idle: ['to-plan', 'research', 'quick-task', 'agents-md'],
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Prompt Builders for Subagent
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function buildLayer1Prompt(userRequest: string, history: Array<{ role: 'user' | 'assistant'; content: string }> | undefined): string {
|
|
108
|
+
const historyBlock = history && history.length > 0
|
|
109
|
+
? `## Conversation History\n${history.map(h => `${h.role}: ${h.content}`).join('\n')}\n`
|
|
110
|
+
: ''
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
'## Layer 1: Parse & Classify',
|
|
114
|
+
'',
|
|
115
|
+
'Analyze the user request and extract structured information.',
|
|
116
|
+
'',
|
|
117
|
+
'**User Request**:',
|
|
118
|
+
userRequest,
|
|
119
|
+
'',
|
|
120
|
+
historyBlock,
|
|
121
|
+
'## Output Format (JSON)',
|
|
122
|
+
'',
|
|
123
|
+
'```json',
|
|
124
|
+
'{',
|
|
125
|
+
' "intent": "primary intent category (plan, execute, debug, review, research, quick-task)",',
|
|
126
|
+
' "entities": ["file paths mentioned", "concepts", "technologies"],',
|
|
127
|
+
' "complexity": "low|medium|high",',
|
|
128
|
+
' "urgency": "low|medium|high"',
|
|
129
|
+
'}',
|
|
130
|
+
'```',
|
|
131
|
+
].join('\n')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildLayer3Prompt(
|
|
135
|
+
classification: Layer1Classification,
|
|
136
|
+
projectState: ProjectState,
|
|
137
|
+
availableSkills: string[],
|
|
138
|
+
): string {
|
|
139
|
+
return [
|
|
140
|
+
'## Layer 3: Skill Matching',
|
|
141
|
+
'',
|
|
142
|
+
'Score each available skill based on how well it matches the request and project state.',
|
|
143
|
+
'',
|
|
144
|
+
'**Request Classification**:',
|
|
145
|
+
`- Intent: ${classification.intent}`,
|
|
146
|
+
`- Entities: ${classification.entities.join(', ') || 'none'}`,
|
|
147
|
+
`- Complexity: ${classification.complexity}`,
|
|
148
|
+
`- Urgency: ${classification.urgency}`,
|
|
149
|
+
'',
|
|
150
|
+
'**Project State**:',
|
|
151
|
+
`- Phase: ${projectState.phase}`,
|
|
152
|
+
`- Has Plan: ${projectState.hasPlan}`,
|
|
153
|
+
`- Is Executing: ${projectState.isExecuting}`,
|
|
154
|
+
`- Progress: ${projectState.completedTasks}/${projectState.totalTasks} tasks`,
|
|
155
|
+
'',
|
|
156
|
+
'**Available Skills**:',
|
|
157
|
+
availableSkills.map(s => `- ${s}`).join('\n'),
|
|
158
|
+
'',
|
|
159
|
+
'## Output Format (JSON)',
|
|
160
|
+
'',
|
|
161
|
+
'```json',
|
|
162
|
+
'{',
|
|
163
|
+
' "scores": [',
|
|
164
|
+
' { "skillName": "skill-name", "baseScore": 0.85, "matches": ["reason1", "reason2"] }',
|
|
165
|
+
' ]',
|
|
166
|
+
'}',
|
|
167
|
+
'```',
|
|
168
|
+
].join('\n')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function buildLayer5Prompt(
|
|
172
|
+
calibratedScores: Layer3SkillScore[],
|
|
173
|
+
projectState: ProjectState,
|
|
174
|
+
): string {
|
|
175
|
+
return [
|
|
176
|
+
'## Layer 5: Selection & Output',
|
|
177
|
+
'',
|
|
178
|
+
'Make the final routing decision based on calibrated scores and project state.',
|
|
179
|
+
'',
|
|
180
|
+
'**Calibrated Scores**:',
|
|
181
|
+
calibratedScores.map(s => `- ${s.skillName}: ${s.stateAdjustedScore.toFixed(2)}`).join('\n'),
|
|
182
|
+
'',
|
|
183
|
+
'**Project State**:',
|
|
184
|
+
`- Phase: ${projectState.phase}`,
|
|
185
|
+
`- Has Plan: ${projectState.hasPlan}`,
|
|
186
|
+
`- Is Executing: ${projectState.isExecuting}`,
|
|
187
|
+
'',
|
|
188
|
+
'## Output Format (JSON)',
|
|
189
|
+
'',
|
|
190
|
+
'```json',
|
|
191
|
+
'{',
|
|
192
|
+
' "selectedSkill": "skill-name",',
|
|
193
|
+
' "finalConfidence": 0.92,',
|
|
194
|
+
' "reasoning": "Detailed explanation of why this skill was selected",',
|
|
195
|
+
' "alternatives": [',
|
|
196
|
+
' { "skill": "other-skill", "confidence": 0.65, "reasoning": "Why it was considered" }',
|
|
197
|
+
' ]',
|
|
198
|
+
'}',
|
|
199
|
+
'```',
|
|
200
|
+
].join('\n')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Parsing Helpers
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
function parseLayer1Output(output: string): Layer1Classification {
|
|
208
|
+
const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/)
|
|
209
|
+
if (jsonMatch) {
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(jsonMatch[1].trim())
|
|
212
|
+
return {
|
|
213
|
+
intent: parsed.intent || 'general',
|
|
214
|
+
entities: Array.isArray(parsed.entities) ? parsed.entities : [],
|
|
215
|
+
complexity: ['low', 'medium', 'high'].includes(parsed.complexity) ? parsed.complexity : 'medium',
|
|
216
|
+
urgency: ['low', 'medium', 'high'].includes(parsed.urgency) ? parsed.urgency : 'medium',
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// Fall through to defaults
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
intent: 'general',
|
|
224
|
+
entities: [],
|
|
225
|
+
complexity: 'medium',
|
|
226
|
+
urgency: 'medium',
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function parseLayer3Output(output: string): Layer3SkillScore[] {
|
|
231
|
+
const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/)
|
|
232
|
+
if (jsonMatch) {
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(jsonMatch[1].trim())
|
|
235
|
+
if (Array.isArray(parsed.scores)) {
|
|
236
|
+
return parsed.scores.map((s: { skillName?: string; baseScore?: number; matches?: string[] }) => ({
|
|
237
|
+
skillName: s.skillName || 'unknown',
|
|
238
|
+
baseScore: s.baseScore || 0,
|
|
239
|
+
stateAdjustedScore: s.baseScore || 0,
|
|
240
|
+
matches: Array.isArray(s.matches) ? s.matches : [],
|
|
241
|
+
}))
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
// Fall through
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return []
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function parseLayer5Output(output: string): Layer5Decision {
|
|
251
|
+
const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/)
|
|
252
|
+
if (jsonMatch) {
|
|
253
|
+
try {
|
|
254
|
+
const parsed = JSON.parse(jsonMatch[1].trim())
|
|
255
|
+
return {
|
|
256
|
+
selectedSkill: parsed.selectedSkill || 'quick-task',
|
|
257
|
+
finalConfidence: parsed.finalConfidence || 0.5,
|
|
258
|
+
reasoning: parsed.reasoning || 'No reasoning provided',
|
|
259
|
+
alternatives: Array.isArray(parsed.alternatives) ? parsed.alternatives : [],
|
|
260
|
+
}
|
|
261
|
+
} catch {
|
|
262
|
+
// Fall through to defaults
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
selectedSkill: 'quick-task',
|
|
267
|
+
finalConfidence: 0.5,
|
|
268
|
+
reasoning: 'Failed to parse decision, defaulting to quick-task',
|
|
269
|
+
alternatives: [],
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// State Mapping Helpers
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
function mapWorkflowPhaseToProjectPhase(phase: WorkflowState['phase']): ProjectPhase {
|
|
278
|
+
switch (phase) {
|
|
279
|
+
case 'planning':
|
|
280
|
+
return 'planning'
|
|
281
|
+
case 'executing':
|
|
282
|
+
return 'execution'
|
|
283
|
+
case 'verifying':
|
|
284
|
+
return 'verification'
|
|
285
|
+
case 'completed':
|
|
286
|
+
return 'completed'
|
|
287
|
+
default:
|
|
288
|
+
return 'idle'
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function buildProjectState(
|
|
293
|
+
workflowState: WorkflowState,
|
|
294
|
+
hasPlan: boolean,
|
|
295
|
+
): ProjectState {
|
|
296
|
+
return {
|
|
297
|
+
phase: mapWorkflowPhaseToProjectPhase(workflowState.phase),
|
|
298
|
+
hasPlan,
|
|
299
|
+
isExecuting: workflowState.phase === 'executing',
|
|
300
|
+
completedTasks: workflowState.completedTasks?.length || 0,
|
|
301
|
+
totalTasks: workflowState.totalTasks || 0,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
// Layer 2: State-Aware Scoring (Pure Function)
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
function calculateStateFactors(
|
|
310
|
+
classification: Layer1Classification,
|
|
311
|
+
projectState: ProjectState,
|
|
312
|
+
): Layer2StateFactors {
|
|
313
|
+
let phaseWeight = 1.0
|
|
314
|
+
let executionWeight = 1.0
|
|
315
|
+
let completionWeight = 1.0
|
|
316
|
+
|
|
317
|
+
// Adjust based on phase
|
|
318
|
+
switch (projectState.phase) {
|
|
319
|
+
case 'planning':
|
|
320
|
+
phaseWeight = classification.intent === 'plan' ? 1.5 : 0.8
|
|
321
|
+
break
|
|
322
|
+
case 'execution':
|
|
323
|
+
phaseWeight = classification.intent === 'execute' ? 1.5 : 0.9
|
|
324
|
+
break
|
|
325
|
+
case 'verification':
|
|
326
|
+
phaseWeight = classification.intent === 'review' ? 1.5 : 0.8
|
|
327
|
+
break
|
|
328
|
+
case 'completed':
|
|
329
|
+
phaseWeight = classification.intent === 'research' ? 1.2 : 1.0
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Adjust based on execution state
|
|
334
|
+
if (projectState.isExecuting && classification.intent === 'execute') {
|
|
335
|
+
executionWeight = 1.3
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Adjust based on completion progress
|
|
339
|
+
const progressRatio = projectState.totalTasks > 0
|
|
340
|
+
? projectState.completedTasks / projectState.totalTasks
|
|
341
|
+
: 0
|
|
342
|
+
completionWeight = 0.8 + (0.4 * progressRatio)
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
phaseWeight,
|
|
346
|
+
executionWeight,
|
|
347
|
+
completionWeight,
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
// Layer 4: Confidence Calibration (Pure Function)
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
function calibrateConfidence(
|
|
356
|
+
scores: Layer3SkillScore[],
|
|
357
|
+
stateFactors: Layer2StateFactors,
|
|
358
|
+
classification: Layer1Classification,
|
|
359
|
+
): Layer3SkillScore[] {
|
|
360
|
+
const totalWeight = stateFactors.phaseWeight * stateFactors.executionWeight * stateFactors.completionWeight
|
|
361
|
+
|
|
362
|
+
return scores.map((score) => {
|
|
363
|
+
// Apply state-based adjustment
|
|
364
|
+
let adjustedScore = score.baseScore * totalWeight
|
|
365
|
+
|
|
366
|
+
// Boost for high urgency
|
|
367
|
+
if (classification.urgency === 'high' && score.skillName === 'quick-task') {
|
|
368
|
+
adjustedScore *= 1.2
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Cap at 1.0
|
|
372
|
+
adjustedScore = Math.min(adjustedScore, 1.0)
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
...score,
|
|
376
|
+
stateAdjustedScore: adjustedScore,
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
// Router Skill Class
|
|
383
|
+
// ---------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
export class RouterSkill extends Skill<RouterInputType, RouterOutputType> {
|
|
386
|
+
private stateManager: StateMdManager
|
|
387
|
+
private planManager: PlanMdManager
|
|
388
|
+
|
|
389
|
+
constructor(statePath = '.pi/state/STATE.md', planPath = '.pi/state/PLAN.md') {
|
|
390
|
+
super({
|
|
391
|
+
name: 'router',
|
|
392
|
+
description: '5-layer routing skill that selects the appropriate skill based on user request and project state',
|
|
393
|
+
requires: [],
|
|
394
|
+
inputSchema: RouterInputSchema,
|
|
395
|
+
outputSchema: RouterOutputSchema,
|
|
396
|
+
})
|
|
397
|
+
this.stateManager = new StateMdManager(statePath)
|
|
398
|
+
this.planManager = new PlanMdManager(planPath)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get available skills for the current project phase
|
|
403
|
+
*/
|
|
404
|
+
getAvailableSkillsForPhase(phase: ProjectPhase): string[] {
|
|
405
|
+
return [...(SKILLS_BY_PHASE[phase] || SKILLS_BY_PHASE.idle)]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async execute(input: RouterInputType, context: SkillContext): Promise<RouterOutputType> {
|
|
409
|
+
const { logger, config } = context
|
|
410
|
+
const dispatcher = createDispatcher({ logger })
|
|
411
|
+
const model = config.defaultModel || 'glm-5'
|
|
412
|
+
|
|
413
|
+
logger.info('[router] Starting 5-layer routing process')
|
|
414
|
+
|
|
415
|
+
// -----------------------------------------------------------------------
|
|
416
|
+
// Read Project State (STATE.md and PLAN.md)
|
|
417
|
+
// -----------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
let workflowState: WorkflowState
|
|
420
|
+
let hasPlan = false
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
workflowState = await this.stateManager.readCurrentState()
|
|
424
|
+
logger.info(`[router] Read workflow state: phase=${workflowState.phase}`)
|
|
425
|
+
} catch (error) {
|
|
426
|
+
logger.warn('[router] No existing workflow state, using defaults')
|
|
427
|
+
workflowState = {
|
|
428
|
+
goal: '',
|
|
429
|
+
phase: 'planning',
|
|
430
|
+
startedAt: new Date().toISOString(),
|
|
431
|
+
lastUpdated: new Date().toISOString(),
|
|
432
|
+
completedTasks: [],
|
|
433
|
+
failedTasks: [],
|
|
434
|
+
totalTasks: 0,
|
|
435
|
+
tokenUsage: { used: 0, limit: 1000000, percentage: 0 },
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
await this.planManager.readPlan()
|
|
441
|
+
hasPlan = true
|
|
442
|
+
logger.info('[router] Plan file exists')
|
|
443
|
+
} catch {
|
|
444
|
+
logger.info('[router] No plan file found')
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const projectState = buildProjectState(workflowState, hasPlan)
|
|
448
|
+
const availableSkills = this.getAvailableSkillsForPhase(projectState.phase)
|
|
449
|
+
|
|
450
|
+
logger.info(`[router] Available skills for ${projectState.phase}: ${availableSkills.join(', ')}`)
|
|
451
|
+
|
|
452
|
+
// -----------------------------------------------------------------------
|
|
453
|
+
// Layer 1: Parse & Classify (Dispatch subagent)
|
|
454
|
+
// -----------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
logger.info('[router] Layer 1: Parsing and classifying request')
|
|
457
|
+
|
|
458
|
+
const layer1Config: SubagentConfig = {
|
|
459
|
+
role: 'explorer',
|
|
460
|
+
model,
|
|
461
|
+
tokenBudget: 8000,
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const layer1Contract: SubagentContract = {
|
|
465
|
+
permissions: {
|
|
466
|
+
readFiles: false,
|
|
467
|
+
searchCode: false,
|
|
468
|
+
runCommands: false,
|
|
469
|
+
writeFiles: false,
|
|
470
|
+
gitOperations: false,
|
|
471
|
+
},
|
|
472
|
+
prompt: buildLayer1Prompt(input.userRequest, (input.conversationHistory ?? []) as Array<{ role: 'user' | 'assistant'; content: string }>),
|
|
473
|
+
owns: [],
|
|
474
|
+
reads: [],
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const layer1Result = await dispatcher.dispatch(layer1Config, layer1Contract)
|
|
478
|
+
const classification = parseLayer1Output(layer1Result.output)
|
|
479
|
+
|
|
480
|
+
logger.info(`[router] Layer 1 complete: intent=${classification.intent}, complexity=${classification.complexity}`)
|
|
481
|
+
|
|
482
|
+
// -----------------------------------------------------------------------
|
|
483
|
+
// Layer 2: State-Aware Scoring (Pure function)
|
|
484
|
+
// -----------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
logger.info('[router] Layer 2: Calculating state factors')
|
|
487
|
+
|
|
488
|
+
const stateFactors = calculateStateFactors(classification, projectState)
|
|
489
|
+
|
|
490
|
+
logger.info(`[router] State factors: phase=${stateFactors.phaseWeight.toFixed(2)}, execution=${stateFactors.executionWeight.toFixed(2)}`)
|
|
491
|
+
|
|
492
|
+
// -----------------------------------------------------------------------
|
|
493
|
+
// Layer 3: Skill Matching (Dispatch subagent)
|
|
494
|
+
// -----------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
logger.info('[router] Layer 3: Matching skills')
|
|
497
|
+
|
|
498
|
+
const layer3Config: SubagentConfig = {
|
|
499
|
+
role: 'planner',
|
|
500
|
+
model,
|
|
501
|
+
tokenBudget: 10000,
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const layer3Contract: SubagentContract = {
|
|
505
|
+
permissions: {
|
|
506
|
+
readFiles: false,
|
|
507
|
+
searchCode: false,
|
|
508
|
+
runCommands: false,
|
|
509
|
+
writeFiles: false,
|
|
510
|
+
gitOperations: false,
|
|
511
|
+
},
|
|
512
|
+
prompt: buildLayer3Prompt(classification, projectState, availableSkills),
|
|
513
|
+
owns: [],
|
|
514
|
+
reads: [],
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const layer3Result = await dispatcher.dispatch(layer3Config, layer3Contract)
|
|
518
|
+
const skillScores = parseLayer3Output(layer3Result.output)
|
|
519
|
+
|
|
520
|
+
logger.info(`[router] Layer 3 complete: ${skillScores.length} skills scored`)
|
|
521
|
+
|
|
522
|
+
// -----------------------------------------------------------------------
|
|
523
|
+
// Layer 4: Confidence Calibration (Pure function)
|
|
524
|
+
// -----------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
logger.info('[router] Layer 4: Calibrating confidence')
|
|
527
|
+
|
|
528
|
+
const calibratedScores = calibrateConfidence(skillScores, stateFactors, classification)
|
|
529
|
+
|
|
530
|
+
logger.info(`[router] Layer 4 complete: top score = ${Math.max(...calibratedScores.map(s => s.stateAdjustedScore)).toFixed(2)}`)
|
|
531
|
+
|
|
532
|
+
// -----------------------------------------------------------------------
|
|
533
|
+
// Layer 5: Selection & Output (Dispatch subagent)
|
|
534
|
+
// -----------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
logger.info('[router] Layer 5: Making final selection')
|
|
537
|
+
|
|
538
|
+
const layer5Config: SubagentConfig = {
|
|
539
|
+
role: 'planner',
|
|
540
|
+
model,
|
|
541
|
+
tokenBudget: 8000,
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const layer5Contract: SubagentContract = {
|
|
545
|
+
permissions: {
|
|
546
|
+
readFiles: false,
|
|
547
|
+
searchCode: false,
|
|
548
|
+
runCommands: false,
|
|
549
|
+
writeFiles: false,
|
|
550
|
+
gitOperations: false,
|
|
551
|
+
},
|
|
552
|
+
prompt: buildLayer5Prompt(calibratedScores, projectState),
|
|
553
|
+
owns: [],
|
|
554
|
+
reads: [],
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const layer5Result = await dispatcher.dispatch(layer5Config, layer5Contract)
|
|
558
|
+
const decision = parseLayer5Output(layer5Result.output)
|
|
559
|
+
|
|
560
|
+
logger.info(`[router] Layer 5 complete: selected=${decision.selectedSkill}, confidence=${decision.finalConfidence.toFixed(2)}`)
|
|
561
|
+
|
|
562
|
+
// -----------------------------------------------------------------------
|
|
563
|
+
// Return Structured Output
|
|
564
|
+
// -----------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
selectedSkill: decision.selectedSkill,
|
|
568
|
+
confidence: decision.finalConfidence,
|
|
569
|
+
reasoning: decision.reasoning,
|
|
570
|
+
projectState,
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Singleton instance
|
|
576
|
+
export const routerSkill = new RouterSkill()
|
|
577
|
+
export default routerSkill
|