astrocode-workflow 0.0.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/LICENSE +1 -0
- package/README.md +85 -0
- package/dist/agents/commands.d.ts +9 -0
- package/dist/agents/commands.js +121 -0
- package/dist/agents/prompts.d.ts +2 -0
- package/dist/agents/prompts.js +27 -0
- package/dist/agents/registry.d.ts +6 -0
- package/dist/agents/registry.js +223 -0
- package/dist/agents/types.d.ts +14 -0
- package/dist/agents/types.js +8 -0
- package/dist/config/config-handler.d.ts +4 -0
- package/dist/config/config-handler.js +46 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.js +3 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.js +48 -0
- package/dist/config/schema.d.ts +176 -0
- package/dist/config/schema.js +198 -0
- package/dist/hooks/continuation-enforcer.d.ts +26 -0
- package/dist/hooks/continuation-enforcer.js +166 -0
- package/dist/hooks/tool-output-truncator.d.ts +17 -0
- package/dist/hooks/tool-output-truncator.js +56 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +108 -0
- package/dist/shared/deep-merge.d.ts +8 -0
- package/dist/shared/deep-merge.js +25 -0
- package/dist/shared/hash.d.ts +1 -0
- package/dist/shared/hash.js +4 -0
- package/dist/shared/log.d.ts +7 -0
- package/dist/shared/log.js +24 -0
- package/dist/shared/model-tuning.d.ts +9 -0
- package/dist/shared/model-tuning.js +28 -0
- package/dist/shared/paths.d.ts +19 -0
- package/dist/shared/paths.js +51 -0
- package/dist/shared/text.d.ts +4 -0
- package/dist/shared/text.js +19 -0
- package/dist/shared/time.d.ts +1 -0
- package/dist/shared/time.js +3 -0
- package/dist/state/adapters/index.d.ts +39 -0
- package/dist/state/adapters/index.js +119 -0
- package/dist/state/db.d.ts +17 -0
- package/dist/state/db.js +83 -0
- package/dist/state/ids.d.ts +8 -0
- package/dist/state/ids.js +25 -0
- package/dist/state/schema.d.ts +2 -0
- package/dist/state/schema.js +247 -0
- package/dist/state/types.d.ts +70 -0
- package/dist/state/types.js +1 -0
- package/dist/tools/artifacts.d.ts +18 -0
- package/dist/tools/artifacts.js +71 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +100 -0
- package/dist/tools/init.d.ts +8 -0
- package/dist/tools/init.js +41 -0
- package/dist/tools/injects.d.ts +23 -0
- package/dist/tools/injects.js +99 -0
- package/dist/tools/repair.d.ts +8 -0
- package/dist/tools/repair.js +25 -0
- package/dist/tools/run.d.ts +13 -0
- package/dist/tools/run.js +54 -0
- package/dist/tools/spec.d.ts +13 -0
- package/dist/tools/spec.js +41 -0
- package/dist/tools/stage.d.ts +23 -0
- package/dist/tools/stage.js +284 -0
- package/dist/tools/status.d.ts +8 -0
- package/dist/tools/status.js +107 -0
- package/dist/tools/story.d.ts +23 -0
- package/dist/tools/story.js +85 -0
- package/dist/tools/workflow.d.ts +8 -0
- package/dist/tools/workflow.js +197 -0
- package/dist/ui/inject.d.ts +5 -0
- package/dist/ui/inject.js +9 -0
- package/dist/ui/toasts.d.ts +13 -0
- package/dist/ui/toasts.js +39 -0
- package/dist/workflow/artifacts.d.ts +24 -0
- package/dist/workflow/artifacts.js +45 -0
- package/dist/workflow/baton.d.ts +66 -0
- package/dist/workflow/baton.js +101 -0
- package/dist/workflow/context.d.ts +12 -0
- package/dist/workflow/context.js +67 -0
- package/dist/workflow/directives.d.ts +37 -0
- package/dist/workflow/directives.js +111 -0
- package/dist/workflow/repair.d.ts +8 -0
- package/dist/workflow/repair.js +99 -0
- package/dist/workflow/state-machine.d.ts +43 -0
- package/dist/workflow/state-machine.js +127 -0
- package/dist/workflow/story-helpers.d.ts +9 -0
- package/dist/workflow/story-helpers.js +13 -0
- package/package.json +32 -0
- package/src/agents/commands.ts +137 -0
- package/src/agents/prompts.ts +28 -0
- package/src/agents/registry.ts +310 -0
- package/src/agents/types.ts +31 -0
- package/src/config/config-handler.ts +48 -0
- package/src/config/defaults.ts +4 -0
- package/src/config/loader.ts +55 -0
- package/src/config/schema.ts +236 -0
- package/src/hooks/continuation-enforcer.ts +217 -0
- package/src/hooks/tool-output-truncator.ts +82 -0
- package/src/index.ts +131 -0
- package/src/shared/deep-merge.ts +28 -0
- package/src/shared/hash.ts +5 -0
- package/src/shared/log.ts +30 -0
- package/src/shared/model-tuning.ts +48 -0
- package/src/shared/paths.ts +70 -0
- package/src/shared/text.ts +20 -0
- package/src/shared/time.ts +3 -0
- package/src/shims.node.d.ts +20 -0
- package/src/state/adapters/index.ts +155 -0
- package/src/state/db.ts +105 -0
- package/src/state/ids.ts +33 -0
- package/src/state/schema.ts +249 -0
- package/src/state/types.ts +76 -0
- package/src/tools/artifacts.ts +83 -0
- package/src/tools/index.ts +111 -0
- package/src/tools/init.ts +50 -0
- package/src/tools/injects.ts +108 -0
- package/src/tools/repair.ts +31 -0
- package/src/tools/run.ts +62 -0
- package/src/tools/spec.ts +50 -0
- package/src/tools/stage.ts +361 -0
- package/src/tools/status.ts +119 -0
- package/src/tools/story.ts +106 -0
- package/src/tools/workflow.ts +241 -0
- package/src/ui/inject.ts +13 -0
- package/src/ui/toasts.ts +48 -0
- package/src/workflow/artifacts.ts +69 -0
- package/src/workflow/baton.ts +141 -0
- package/src/workflow/context.ts +86 -0
- package/src/workflow/directives.ts +170 -0
- package/src/workflow/repair.ts +138 -0
- package/src/workflow/state-machine.ts +194 -0
- package/src/workflow/story-helpers.ts +18 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
2
|
+
|
|
3
|
+
export type CommandDefinition = {
|
|
4
|
+
description: string;
|
|
5
|
+
template: string;
|
|
6
|
+
argumentHint?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function createAstroCommands(_opts: { pluginConfig: AstrocodeConfig }): Record<string, CommandDefinition> {
|
|
10
|
+
// Keep templates very short: they just steer toward tools.
|
|
11
|
+
return {
|
|
12
|
+
"astro": {
|
|
13
|
+
description: "Astrocode help.",
|
|
14
|
+
argumentHint: "",
|
|
15
|
+
template: `<command-instruction>
|
|
16
|
+
You are using Astrocode vNext.
|
|
17
|
+
Show a short help message with the key commands:
|
|
18
|
+
- /astro-status
|
|
19
|
+
- /astro-init
|
|
20
|
+
- /astro-queue \"title\" --body=\"...\"
|
|
21
|
+
- /astro-approve S-123
|
|
22
|
+
- /astro-next
|
|
23
|
+
- /astro-loop --max-steps=50
|
|
24
|
+
- /astro-repair
|
|
25
|
+
</command-instruction>
|
|
26
|
+
|
|
27
|
+
<user-task>
|
|
28
|
+
$ARGUMENTS
|
|
29
|
+
</user-task>`,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
"astro-status": {
|
|
33
|
+
description: "Show Astrocode status: current run, stage, blockers, next action.",
|
|
34
|
+
argumentHint: "",
|
|
35
|
+
template: `<command-instruction>
|
|
36
|
+
Call tool astro_status.
|
|
37
|
+
Return a compact dashboard.
|
|
38
|
+
</command-instruction>
|
|
39
|
+
|
|
40
|
+
<user-task>
|
|
41
|
+
$ARGUMENTS
|
|
42
|
+
</user-task>`,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
"astro-init": {
|
|
46
|
+
description: "Initialize Astrocode (.astro directory + SQLite DB).",
|
|
47
|
+
argumentHint: "",
|
|
48
|
+
template: `<command-instruction>
|
|
49
|
+
Call tool astro_init.
|
|
50
|
+
Then call astro_status.
|
|
51
|
+
</command-instruction>
|
|
52
|
+
|
|
53
|
+
<user-task>
|
|
54
|
+
$ARGUMENTS
|
|
55
|
+
</user-task>`,
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
"astro-queue": {
|
|
59
|
+
description: "Create a queued story.",
|
|
60
|
+
argumentHint: "\"title\" --body=\"...\" --priority=50",
|
|
61
|
+
template: `<command-instruction>
|
|
62
|
+
Create a story using astro_story_queue.
|
|
63
|
+
Title is required. Body optional.
|
|
64
|
+
Return story_key.
|
|
65
|
+
</command-instruction>
|
|
66
|
+
|
|
67
|
+
<user-task>
|
|
68
|
+
$ARGUMENTS
|
|
69
|
+
</user-task>`,
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
"astro-approve": {
|
|
73
|
+
description: "Approve a story so it can be run.",
|
|
74
|
+
argumentHint: "S-123",
|
|
75
|
+
template: `<command-instruction>
|
|
76
|
+
Call astro_story_approve with story_key.
|
|
77
|
+
Then call astro_status.
|
|
78
|
+
</command-instruction>
|
|
79
|
+
|
|
80
|
+
<user-task>
|
|
81
|
+
$ARGUMENTS
|
|
82
|
+
</user-task>`,
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
"astro-next": {
|
|
86
|
+
description: "Advance Astrocode by one deterministic step.",
|
|
87
|
+
argumentHint: "",
|
|
88
|
+
template: `<command-instruction>
|
|
89
|
+
Call tool astro_workflow_proceed with mode=step.
|
|
90
|
+
If it injects a CONTINUE directive, obey it.
|
|
91
|
+
</command-instruction>
|
|
92
|
+
|
|
93
|
+
<user-task>
|
|
94
|
+
$ARGUMENTS
|
|
95
|
+
</user-task>`,
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
"astro-loop": {
|
|
99
|
+
description: "Run Astrocode loop for N steps (bounded).",
|
|
100
|
+
argumentHint: "--max-steps=N",
|
|
101
|
+
template: `<command-instruction>
|
|
102
|
+
Parse --max-steps (default 50).
|
|
103
|
+
Call tool astro_workflow_proceed with mode=loop and max_steps.
|
|
104
|
+
Stop if blocked.
|
|
105
|
+
</command-instruction>
|
|
106
|
+
|
|
107
|
+
<user-task>
|
|
108
|
+
$ARGUMENTS
|
|
109
|
+
</user-task>`,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
"astro-repair": {
|
|
113
|
+
description: "Repair invariants / recover from inconsistent state.",
|
|
114
|
+
argumentHint: "",
|
|
115
|
+
template: `<command-instruction>
|
|
116
|
+
Call astro_repair.
|
|
117
|
+
Return repair report summary + pointers.
|
|
118
|
+
</command-instruction>
|
|
119
|
+
|
|
120
|
+
<user-task>
|
|
121
|
+
$ARGUMENTS
|
|
122
|
+
</user-task>`,
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
"astro-board": {
|
|
126
|
+
description: "Show story board grouped by state.",
|
|
127
|
+
argumentHint: "",
|
|
128
|
+
template: `<command-instruction>
|
|
129
|
+
Call astro_story_board.
|
|
130
|
+
</command-instruction>
|
|
131
|
+
|
|
132
|
+
<user-task>
|
|
133
|
+
$ARGUMENTS
|
|
134
|
+
</user-task>`,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const BASE_ORCH_PROMPT = `You are Astro (Orchestrator) for Astrocode.
|
|
2
|
+
|
|
3
|
+
Mission:
|
|
4
|
+
- Advance a deterministic pipeline: frame → plan → spec → implement → review → verify → close.
|
|
5
|
+
- The SQLite DB is the source of truth. Prefer tools over prose.
|
|
6
|
+
- Never narrate what prompts you received.
|
|
7
|
+
- Keep outputs short; store large outputs as artifacts and reference paths.
|
|
8
|
+
|
|
9
|
+
Operating rules:
|
|
10
|
+
- Prefer calling astro_workflow_proceed (step/loop) and astro_status.
|
|
11
|
+
- Delegate stage work only to the stage subagent matching the current stage.
|
|
12
|
+
- If a stage subagent returns status=blocked, inject the BLOCKED directive and stop.
|
|
13
|
+
- Never delegate from subagents (enforced by permissions).
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
export const BASE_STAGE_PROMPT = `You are an Astro stage subagent.
|
|
17
|
+
|
|
18
|
+
Follow the latest [SYSTEM DIRECTIVE: ASTROCODE — STAGE_*] you receive.
|
|
19
|
+
|
|
20
|
+
Output exactly:
|
|
21
|
+
1) Baton markdown (short, structured)
|
|
22
|
+
2) Valid ASTRO JSON between markers:
|
|
23
|
+
<!-- ASTRO_JSON_BEGIN -->
|
|
24
|
+
{...}
|
|
25
|
+
<!-- ASTRO_JSON_END -->
|
|
26
|
+
|
|
27
|
+
Do not narrate. If blocked, ask exactly ONE question and stop.
|
|
28
|
+
`;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
2
|
+
import type { AstrocodeConfig } from "../config/schema";
|
|
3
|
+
import { deepMerge } from "../shared/deep-merge";
|
|
4
|
+
import { applyModelTuning } from "../shared/model-tuning";
|
|
5
|
+
import { BASE_ORCH_PROMPT, BASE_STAGE_PROMPT } from "./prompts";
|
|
6
|
+
|
|
7
|
+
type PermissionValue = "ask" | "allow" | "deny";
|
|
8
|
+
type PermissionMap = Record<string, PermissionValue>;
|
|
9
|
+
|
|
10
|
+
function denyAll(): PermissionMap {
|
|
11
|
+
return { "*": "deny" };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function orchestratorPermissions(): PermissionMap {
|
|
15
|
+
return {
|
|
16
|
+
"*": "deny",
|
|
17
|
+
|
|
18
|
+
// Common OpenCode tools:
|
|
19
|
+
"read": "allow",
|
|
20
|
+
"grep": "allow",
|
|
21
|
+
"bash": "allow",
|
|
22
|
+
"edit": "allow",
|
|
23
|
+
"write": "allow",
|
|
24
|
+
"question": "allow",
|
|
25
|
+
|
|
26
|
+
// Task delegation:
|
|
27
|
+
"task": "allow",
|
|
28
|
+
"delegate_task": "allow",
|
|
29
|
+
"slashcommand": "allow",
|
|
30
|
+
|
|
31
|
+
// Astro tools:
|
|
32
|
+
"astro_init": "allow",
|
|
33
|
+
"astro_status": "allow",
|
|
34
|
+
"astro_workflow_proceed": "allow",
|
|
35
|
+
"astro_repair": "allow",
|
|
36
|
+
|
|
37
|
+
"astro_spec_get": "allow",
|
|
38
|
+
"astro_spec_set": "allow",
|
|
39
|
+
|
|
40
|
+
"astro_story_queue": "allow",
|
|
41
|
+
"astro_story_approve": "allow",
|
|
42
|
+
"astro_story_board": "allow",
|
|
43
|
+
"astro_story_set_state": "allow",
|
|
44
|
+
|
|
45
|
+
"astro_run_get": "allow",
|
|
46
|
+
"astro_run_abort": "allow",
|
|
47
|
+
|
|
48
|
+
"astro_stage_start": "allow",
|
|
49
|
+
"astro_stage_complete": "allow",
|
|
50
|
+
"astro_stage_fail": "allow",
|
|
51
|
+
"astro_stage_reset": "allow",
|
|
52
|
+
|
|
53
|
+
"astro_artifact_put": "allow",
|
|
54
|
+
"astro_artifact_list": "allow",
|
|
55
|
+
"astro_artifact_get": "allow",
|
|
56
|
+
|
|
57
|
+
"astro_inject_put": "allow",
|
|
58
|
+
"astro_inject_list": "allow",
|
|
59
|
+
"astro_inject_search": "allow",
|
|
60
|
+
"astro_inject_get": "allow",
|
|
61
|
+
|
|
62
|
+
// Git helpers (optional)
|
|
63
|
+
"astro_git_status": "allow",
|
|
64
|
+
"astro_git_diff": "allow",
|
|
65
|
+
"astro_git_commit": "ask",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stageReadOnlyPermissions(): PermissionMap {
|
|
70
|
+
return {
|
|
71
|
+
"*": "deny",
|
|
72
|
+
"read": "allow",
|
|
73
|
+
"grep": "allow",
|
|
74
|
+
"question": "allow",
|
|
75
|
+
"task": "deny",
|
|
76
|
+
"delegate_task": "deny",
|
|
77
|
+
"slashcommand": "deny",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function stageImplementPermissions(): PermissionMap {
|
|
82
|
+
return {
|
|
83
|
+
"*": "deny",
|
|
84
|
+
"read": "allow",
|
|
85
|
+
"grep": "allow",
|
|
86
|
+
"edit": "allow",
|
|
87
|
+
"write": "allow",
|
|
88
|
+
"bash": "allow",
|
|
89
|
+
"question": "allow",
|
|
90
|
+
"task": "deny",
|
|
91
|
+
"delegate_task": "deny",
|
|
92
|
+
"slashcommand": "deny",
|
|
93
|
+
|
|
94
|
+
"astro_artifact_put": "allow",
|
|
95
|
+
"astro_artifact_list": "allow",
|
|
96
|
+
"astro_artifact_get": "allow",
|
|
97
|
+
"astro_git_diff": "allow",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stageVerifyPermissions(): PermissionMap {
|
|
102
|
+
return {
|
|
103
|
+
"*": "deny",
|
|
104
|
+
"read": "allow",
|
|
105
|
+
"grep": "allow",
|
|
106
|
+
"bash": "allow",
|
|
107
|
+
"question": "allow",
|
|
108
|
+
"task": "deny",
|
|
109
|
+
"delegate_task": "deny",
|
|
110
|
+
"slashcommand": "deny",
|
|
111
|
+
|
|
112
|
+
"astro_artifact_put": "allow",
|
|
113
|
+
"astro_artifact_list": "allow",
|
|
114
|
+
"astro_artifact_get": "allow",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createAstroAgents(opts: {
|
|
119
|
+
systemDefaultModel: string;
|
|
120
|
+
pluginConfig: AstrocodeConfig;
|
|
121
|
+
}): Record<string, AgentConfig> {
|
|
122
|
+
const { systemDefaultModel, pluginConfig } = opts;
|
|
123
|
+
|
|
124
|
+
// Ensure agents config exists with defaults
|
|
125
|
+
if (!pluginConfig.agents) {
|
|
126
|
+
pluginConfig.agents = {
|
|
127
|
+
orchestrator_name: "Astro",
|
|
128
|
+
stage_agent_names: {
|
|
129
|
+
frame: "AstroFrame",
|
|
130
|
+
plan: "AstroPlan",
|
|
131
|
+
spec: "AstroSpec",
|
|
132
|
+
implement: "AstroImplement",
|
|
133
|
+
review: "AstroReview",
|
|
134
|
+
verify: "AstroVerify",
|
|
135
|
+
close: "AstroClose"
|
|
136
|
+
},
|
|
137
|
+
librarian_name: "AstroLibrarian",
|
|
138
|
+
explore_name: "AstroExplore",
|
|
139
|
+
agent_variant_overrides: {}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const orchestratorName = pluginConfig.agents.orchestrator_name;
|
|
144
|
+
|
|
145
|
+
const modelFor = (agentName: string): string | undefined => {
|
|
146
|
+
const overrides = pluginConfig.agents?.agent_variant_overrides || {};
|
|
147
|
+
const override = overrides[agentName];
|
|
148
|
+
return override?.model;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const variantFor = (agentName: string): string | undefined => {
|
|
152
|
+
const overrides = pluginConfig.agents?.agent_variant_overrides || {};
|
|
153
|
+
const override = overrides[agentName];
|
|
154
|
+
return override?.variant;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const mk = (name: string, base: AgentConfig, preset: Parameters<typeof applyModelTuning>[1]): AgentConfig => {
|
|
158
|
+
const tuned = applyModelTuning(base, preset);
|
|
159
|
+
// Apply per-agent model/variant overrides from config:
|
|
160
|
+
const withOverrides: AgentConfig = {
|
|
161
|
+
...tuned,
|
|
162
|
+
model: modelFor(name),
|
|
163
|
+
variant: variantFor(name),
|
|
164
|
+
};
|
|
165
|
+
return withOverrides;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const agents: Record<string, AgentConfig> = {};
|
|
169
|
+
|
|
170
|
+
agents[orchestratorName] = mk(
|
|
171
|
+
orchestratorName,
|
|
172
|
+
{
|
|
173
|
+
description:
|
|
174
|
+
"Astrocode vNext orchestrator. DB-driven stage machine with continuation + artifact discipline.",
|
|
175
|
+
mode: "primary",
|
|
176
|
+
maxTokens: 32000,
|
|
177
|
+
prompt: BASE_ORCH_PROMPT,
|
|
178
|
+
permission: orchestratorPermissions(),
|
|
179
|
+
},
|
|
180
|
+
"orchestrator"
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Stage agents (hidden)
|
|
184
|
+
const stageAgents = pluginConfig.agents.stage_agent_names;
|
|
185
|
+
|
|
186
|
+
agents[stageAgents.frame] = mk(
|
|
187
|
+
stageAgents.frame,
|
|
188
|
+
{
|
|
189
|
+
description: "Stage agent: frame. Produces scope + DoD baton.",
|
|
190
|
+
mode: "subagent",
|
|
191
|
+
hidden: true,
|
|
192
|
+
temperature: 0.1,
|
|
193
|
+
prompt: BASE_STAGE_PROMPT,
|
|
194
|
+
permission: stageReadOnlyPermissions(),
|
|
195
|
+
},
|
|
196
|
+
"frame"
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
agents[stageAgents.plan] = mk(
|
|
200
|
+
stageAgents.plan,
|
|
201
|
+
{
|
|
202
|
+
description: "Stage agent: plan. Produces bounded tasks + files + tests.",
|
|
203
|
+
mode: "subagent",
|
|
204
|
+
hidden: true,
|
|
205
|
+
temperature: 0.1,
|
|
206
|
+
prompt: BASE_STAGE_PROMPT,
|
|
207
|
+
permission: stageReadOnlyPermissions(),
|
|
208
|
+
},
|
|
209
|
+
"plan"
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
agents[stageAgents.spec] = mk(
|
|
213
|
+
stageAgents.spec,
|
|
214
|
+
{
|
|
215
|
+
description: "Stage agent: spec. Produces implementation contracts (minimal).",
|
|
216
|
+
mode: "subagent",
|
|
217
|
+
hidden: true,
|
|
218
|
+
temperature: 0.1,
|
|
219
|
+
prompt: BASE_STAGE_PROMPT,
|
|
220
|
+
permission: stageReadOnlyPermissions(),
|
|
221
|
+
},
|
|
222
|
+
"spec"
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
agents[stageAgents.implement] = mk(
|
|
226
|
+
stageAgents.implement,
|
|
227
|
+
{
|
|
228
|
+
description: "Stage agent: implement. Makes code changes and references diff artifacts.",
|
|
229
|
+
mode: "subagent",
|
|
230
|
+
hidden: true,
|
|
231
|
+
temperature: 0.2,
|
|
232
|
+
prompt: BASE_STAGE_PROMPT,
|
|
233
|
+
permission: stageImplementPermissions(),
|
|
234
|
+
},
|
|
235
|
+
"implement"
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
agents[stageAgents.review] = mk(
|
|
239
|
+
stageAgents.review,
|
|
240
|
+
{
|
|
241
|
+
description: "Stage agent: review. Checks spec compliance and risks.",
|
|
242
|
+
mode: "subagent",
|
|
243
|
+
hidden: true,
|
|
244
|
+
temperature: 0.1,
|
|
245
|
+
prompt: BASE_STAGE_PROMPT,
|
|
246
|
+
permission: stageReadOnlyPermissions(),
|
|
247
|
+
},
|
|
248
|
+
"review"
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
agents[stageAgents.verify] = mk(
|
|
252
|
+
stageAgents.verify,
|
|
253
|
+
{
|
|
254
|
+
description: "Stage agent: verify. Runs checks and produces evidence artifacts.",
|
|
255
|
+
mode: "subagent",
|
|
256
|
+
hidden: true,
|
|
257
|
+
temperature: 0.1,
|
|
258
|
+
prompt: BASE_STAGE_PROMPT,
|
|
259
|
+
permission: stageReadOnlyPermissions(),
|
|
260
|
+
},
|
|
261
|
+
"verify"
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
agents[stageAgents.close] = mk(
|
|
265
|
+
stageAgents.close,
|
|
266
|
+
{
|
|
267
|
+
description: "Stage agent: close. Final run summary + acceptance confirmation.",
|
|
268
|
+
mode: "subagent",
|
|
269
|
+
hidden: true,
|
|
270
|
+
temperature: 0.1,
|
|
271
|
+
prompt: BASE_STAGE_PROMPT,
|
|
272
|
+
permission: stageReadOnlyPermissions(),
|
|
273
|
+
},
|
|
274
|
+
"close"
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Optional utility agents (hidden) — intentionally minimal prompt; prefer stage directives.
|
|
278
|
+
agents[pluginConfig.agents.librarian_name] = mk(
|
|
279
|
+
pluginConfig.agents.librarian_name,
|
|
280
|
+
{
|
|
281
|
+
description: "Utility agent: librarian. Fast repo exploration + notes as artifacts.",
|
|
282
|
+
mode: "subagent",
|
|
283
|
+
hidden: true,
|
|
284
|
+
temperature: 0.1,
|
|
285
|
+
prompt: BASE_STAGE_PROMPT,
|
|
286
|
+
permission: stageReadOnlyPermissions(),
|
|
287
|
+
},
|
|
288
|
+
"utility"
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
agents[pluginConfig.agents.explore_name] = mk(
|
|
292
|
+
pluginConfig.agents.explore_name,
|
|
293
|
+
{
|
|
294
|
+
description: "Utility agent: explore. Spikes / scans / code search.",
|
|
295
|
+
mode: "subagent",
|
|
296
|
+
hidden: true,
|
|
297
|
+
temperature: 0.1,
|
|
298
|
+
prompt: BASE_STAGE_PROMPT,
|
|
299
|
+
permission: stageReadOnlyPermissions(),
|
|
300
|
+
},
|
|
301
|
+
"utility"
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Allow user config to disable certain agents
|
|
305
|
+
for (const disabled of pluginConfig.disabled_agents) {
|
|
306
|
+
delete agents[disabled];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return agents;
|
|
310
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentConfig } from "@opencode-ai/sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Matches OMO-style heuristics:
|
|
5
|
+
* - OpenAI provider models: "openai/..."
|
|
6
|
+
* - GitHub Copilot GPT models: "github-copilot/gpt-..."
|
|
7
|
+
*/
|
|
8
|
+
export function isGptModel(model: string): boolean {
|
|
9
|
+
return model.startsWith("openai/") || model.startsWith("github-copilot/gpt-");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type AstroAgentName =
|
|
13
|
+
| "Astro (Orchestrator)"
|
|
14
|
+
| "astro_frame"
|
|
15
|
+
| "astro_plan"
|
|
16
|
+
| "astro_spec"
|
|
17
|
+
| "astro_implement"
|
|
18
|
+
| "astro_review"
|
|
19
|
+
| "astro_verify"
|
|
20
|
+
| "astro_close"
|
|
21
|
+
| "astro_librarian"
|
|
22
|
+
| "astro_explore";
|
|
23
|
+
|
|
24
|
+
export type PermissionValue = "ask" | "allow" | "deny";
|
|
25
|
+
|
|
26
|
+
export type AgentOverrideConfig = Partial<AgentConfig> & {
|
|
27
|
+
prompt_append?: string;
|
|
28
|
+
variant?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type AgentOverrides = Partial<Record<AstroAgentName, AgentOverrideConfig>>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AstrocodeConfig } from "./schema";
|
|
2
|
+
import { createAstroAgents } from "../agents/registry";
|
|
3
|
+
import { createAstroCommands } from "../agents/commands";
|
|
4
|
+
|
|
5
|
+
/** Best-effort extraction of system default model from an OpenCode config object. */
|
|
6
|
+
function detectSystemDefaultModel(config: Record<string, any>): string {
|
|
7
|
+
const direct = config?.model;
|
|
8
|
+
if (typeof direct === "string" && direct.trim()) return direct.trim();
|
|
9
|
+
|
|
10
|
+
const agentDefault = config?.agent?.default?.model;
|
|
11
|
+
if (typeof agentDefault === "string" && agentDefault.trim()) return agentDefault.trim();
|
|
12
|
+
|
|
13
|
+
const firstAgent = config?.agent && typeof config.agent === "object" ? Object.values(config.agent)[0] : null;
|
|
14
|
+
const firstModel = (firstAgent as any)?.model;
|
|
15
|
+
if (typeof firstModel === "string" && firstModel.trim()) return firstModel.trim();
|
|
16
|
+
|
|
17
|
+
return "openai/gpt-4o";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createConfigHandler(opts: { pluginConfig: AstrocodeConfig }) {
|
|
21
|
+
const { pluginConfig } = opts;
|
|
22
|
+
|
|
23
|
+
return async function configHandler(config: Record<string, any>): Promise<void> {
|
|
24
|
+
try {
|
|
25
|
+
const systemDefaultModel = detectSystemDefaultModel(config);
|
|
26
|
+
|
|
27
|
+
const agents = createAstroAgents({ systemDefaultModel, pluginConfig });
|
|
28
|
+
const commands = createAstroCommands({ pluginConfig });
|
|
29
|
+
|
|
30
|
+
// Merge agents (do not override user-defined agent configs with same key; user wins)
|
|
31
|
+
config.agent = config.agent ?? {};
|
|
32
|
+
for (const [name, agentCfg] of Object.entries(agents)) {
|
|
33
|
+
if (pluginConfig.disabled_agents?.includes(name)) continue;
|
|
34
|
+
if (!config.agent[name]) config.agent[name] = agentCfg;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Merge commands (user wins)
|
|
38
|
+
config.command = config.command ?? {};
|
|
39
|
+
for (const [name, cmd] of Object.entries(commands)) {
|
|
40
|
+
if (pluginConfig.disabled_commands?.includes(name)) continue;
|
|
41
|
+
if (!config.command[name]) config.command[name] = cmd;
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.warn("[astrocode] Config handler failed:", e);
|
|
45
|
+
// Don't crash OpenCode, just skip config modifications
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
4
|
+
import { AstrocodeConfigSchema, type AstrocodeConfig } from "./schema";
|
|
5
|
+
import { deepMerge } from "../shared/deep-merge";
|
|
6
|
+
import { info, warn } from "../shared/log";
|
|
7
|
+
|
|
8
|
+
export type ConfigFileDetection =
|
|
9
|
+
| { format: "jsonc" | "json"; path: string }
|
|
10
|
+
| { format: "none"; path: string };
|
|
11
|
+
|
|
12
|
+
export function detectConfigFile(basePathNoExt: string): ConfigFileDetection {
|
|
13
|
+
const jsoncPath = basePathNoExt + ".jsonc";
|
|
14
|
+
if (fs.existsSync(jsoncPath)) return { format: "jsonc", path: jsoncPath };
|
|
15
|
+
const jsonPath = basePathNoExt + ".json";
|
|
16
|
+
if (fs.existsSync(jsonPath)) return { format: "json", path: jsonPath };
|
|
17
|
+
return { format: "none", path: basePathNoExt + ".jsonc" };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadConfigFromPath(p: string): AstrocodeConfig | null {
|
|
21
|
+
if (!fs.existsSync(p)) return null;
|
|
22
|
+
try {
|
|
23
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
24
|
+
const parsed = parseJsonc(raw) as unknown;
|
|
25
|
+
const cfg = AstrocodeConfigSchema.parse(parsed);
|
|
26
|
+
return cfg;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
warn(`Failed to load astrocode config at ${p}; using defaults`, String(e));
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function loadAstrocodeConfig(repoRoot: string): AstrocodeConfig {
|
|
34
|
+
// project-level: prefer .astro/astrocode.config.jsonc; fallback astrocode.config.jsonc
|
|
35
|
+
const projectBase = path.join(repoRoot, ".astro", "astrocode.config");
|
|
36
|
+
const projectDetected = detectConfigFile(projectBase);
|
|
37
|
+
const projectCfg = loadConfigFromPath(projectDetected.path);
|
|
38
|
+
|
|
39
|
+
const legacyBase = path.join(repoRoot, "astrocode.config");
|
|
40
|
+
const legacyDetected = detectConfigFile(legacyBase);
|
|
41
|
+
const legacyCfg = loadConfigFromPath(legacyDetected.path);
|
|
42
|
+
|
|
43
|
+
// Start with defaults and merge configs
|
|
44
|
+
let cfg: AstrocodeConfig = AstrocodeConfigSchema.parse({});
|
|
45
|
+
|
|
46
|
+
if (legacyCfg) cfg = deepMerge(cfg, legacyCfg);
|
|
47
|
+
if (projectCfg) cfg = deepMerge(cfg, projectCfg);
|
|
48
|
+
|
|
49
|
+
// Ensure the final config is fully validated with all required defaults
|
|
50
|
+
cfg = AstrocodeConfigSchema.parse(cfg);
|
|
51
|
+
|
|
52
|
+
// Config loaded successfully (silent)
|
|
53
|
+
|
|
54
|
+
return cfg;
|
|
55
|
+
}
|