@plainconceptsplatform/agent-harness 2.3.0 → 2.4.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/README.md +23 -12
- package/{src → cli}/commands/join.js +3 -4
- package/{src → cli}/commands/migrate.js +1 -1
- package/{src → cli}/commands/wizard.js +1 -1
- package/{src → cli}/index.js +1 -1
- package/{src → cli}/presets/agents-content.json +4 -4
- package/cli/presets/browser.json +10 -0
- package/{src → cli}/presets/clean.json +21 -21
- package/{src → cli}/presets/optimization.json +37 -37
- package/{src → cli}/presets/platforms.json +76 -76
- package/{src → cli}/presets/quota.json +16 -16
- package/{src → cli}/presets/source.json +23 -23
- package/cli/steps/browser/index.js +48 -0
- package/{src → cli}/steps/clean/index.js +120 -120
- package/{src → cli}/steps/copy/agents.js +119 -119
- package/{src → cli}/steps/copy/commands.js +95 -95
- package/{src → cli}/steps/copy/fullstack-engineer.js +89 -88
- package/{src → cli}/steps/copy/index.js +1 -1
- package/{src → cli}/steps/copy/opencode-json.js +196 -161
- package/{src → cli}/steps/copy/skills.js +2 -2
- package/{src → cli}/steps/models/format.js +88 -88
- package/{src → cli}/steps/models/index.js +64 -64
- package/{src → cli}/steps/openspec/index.js +136 -136
- package/{src → cli}/steps/optimization/codegraph.js +127 -127
- package/{src → cli}/steps/optimization/detect.js +66 -66
- package/{src → cli}/steps/optimization/humanizer.js +17 -17
- package/{src → cli}/steps/optimization/index.js +163 -163
- package/{src → cli}/steps/optimization/memory.js +88 -88
- package/{src → cli}/steps/optimization/patch-guardrails.js +109 -109
- package/{src → cli}/steps/optimization/quota.js +119 -119
- package/{src → cli}/steps/optimization/simple-english.js +17 -17
- package/{src → cli}/steps/optimization/skills-lock.js +30 -30
- package/{src → cli}/steps/platform/index.js +109 -109
- package/{src → cli}/steps/source/index.js +123 -123
- package/{src → cli}/utils/agent-color.js +111 -83
- package/{src → cli}/utils/exec-spinner.js +47 -47
- package/{src → cli}/utils/exec.js +134 -134
- package/{src → cli}/utils/models-pricing.js +42 -42
- package/{src → cli}/utils/paths.js +1 -1
- package/{src → cli}/utils/process.js +3 -3
- package/{src → cli}/utils/terminal.js +6 -6
- package/harness/.agents/skills/browser-automation/SKILL.md +72 -0
- package/{src/content → harness}/.agents/skills/pc-make-engineer/SKILL.md +219 -219
- package/{src/content → harness}/.agents/skills/pc-make-engineer/template.md +80 -80
- package/{src/content → harness}/.agents/skills/pc-plan-archive/SKILL.md +3 -0
- package/{src/content → harness}/.agents/skills/pc-plan-explore/SKILL.md +3 -0
- package/{src/content → harness}/.agents/skills/pc-plan-goal/SKILL.md +5 -16
- package/{src/content → harness}/.agents/skills/pc-plan-goal/branching.md +1 -1
- package/{src/content → harness}/.agents/skills/pc-plan-goal/output.md +5 -8
- package/{src/content → harness}/.agents/skills/pc-plan-story/SKILL.md +3 -0
- package/{src/content → harness}/.agents/skills/pc-userstory-browser/SKILL.md +136 -132
- package/{src/content → harness}/.opencode/package.json +9 -10
- package/{src/content → harness}/.opencode/plugins/pc-subagent-tiers.js +368 -337
- package/{src/content → harness}/ARCHITECTURE.md +16 -16
- package/{src/content → harness}/DESIGN.md +16 -16
- package/{src/content → harness}/opencode.jsonc +47 -41
- package/{src/content → harness}/openspec/config.yaml +20 -20
- package/{src/content → harness}/skills-lock.json +17 -17
- package/package.json +7 -7
- package/src/content/.agents/skills/browser-automation/SKILL.md +0 -66
- package/src/presets/browser.json +0 -22
- package/src/steps/browser/index.js +0 -91
- /package/{src → cli}/commands/shared.js +0 -0
- /package/{src → cli}/commands/single.js +0 -0
- /package/{src → cli}/commands/update.js +0 -0
- /package/{src → cli}/fragments/archive/az.md +0 -0
- /package/{src → cli}/fragments/archive/gh.md +0 -0
- /package/{src → cli}/fragments/archive/gl.md +0 -0
- /package/{src → cli}/fragments/archive/none.md +0 -0
- /package/{src → cli}/fragments/guardrails/codegraph.md +0 -0
- /package/{src → cli}/fragments/guardrails/humanizer.md +0 -0
- /package/{src → cli}/fragments/guardrails/memory.md +0 -0
- /package/{src → cli}/fragments/guardrails/rtk.md +0 -0
- /package/{src → cli}/fragments/guardrails/simple-english.md +0 -0
- /package/{src → cli}/fragments/ops-backlog/az.md +0 -0
- /package/{src → cli}/fragments/ops-backlog/gh.md +0 -0
- /package/{src → cli}/fragments/ops-backlog/jira.md +0 -0
- /package/{src → cli}/fragments/ops-evidence/az.md +0 -0
- /package/{src → cli}/fragments/ops-evidence/gh.md +0 -0
- /package/{src → cli}/fragments/ops-evidence/jira.md +0 -0
- /package/{src → cli}/fragments/ops-review/az.md +0 -0
- /package/{src → cli}/fragments/ops-review/gh.md +0 -0
- /package/{src → cli}/fragments/ops-review/gl.md +0 -0
- /package/{src → cli}/fragments/ops-ship/az.md +0 -0
- /package/{src → cli}/fragments/ops-ship/gh.md +0 -0
- /package/{src → cli}/fragments/ops-ship/gl.md +0 -0
- /package/{src → cli}/presets/models.json +0 -0
- /package/{src → cli}/presets/openspec.json +0 -0
- /package/{src → cli}/steps/metadata/index.js +0 -0
- /package/{src → cli}/steps/models/write.js +0 -0
- /package/{src → cli}/utils/copy.js +0 -0
- /package/{src → cli}/utils/legacy-check.js +0 -0
- /package/{src → cli}/utils/models-cache.js +0 -0
- /package/{src → cli}/utils/update-manifest.js +0 -0
- /package/{src/content → harness}/.agents/skills/pc-guardrails-generic/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-guardrails-project/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-architecture/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-architecture/structure-template.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-design/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-engineer/signal-mapping.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-evidence-scaffold/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-evidence-scaffold/evidence-contract.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-guardrails/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-guardrails/category-reference.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-merge-risk-assess/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-merge-risk-assess/category-reference.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-make-user-model/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-ops-evidence/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-ops-ship/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-apply/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-apply/simple-mode.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-goal/failure-policy.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-goal/output-mode.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-propose/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-propose/task-annotation.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-plan-quick/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-audit/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-help/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-initialize/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-onboard/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-repo-verify/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-userstory-az/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-userstory-gh/SKILL.md +0 -0
- /package/{src/content → harness}/.agents/skills/pc-userstory-jira/SKILL.md +0 -0
- /package/{src/content → harness}/.opencode/_gitignore +0 -0
- /package/{src/content → harness}/.opencode/commands/init.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-architecture.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-design.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-engineer.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-evidence-scaffold.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-guardrails.md +0 -0
- /package/{src/content → harness}/.opencode/commands/make-user-model.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-backlog.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-evidence.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-review.md +0 -0
- /package/{src/content → harness}/.opencode/commands/ops-ship.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-apply.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-archive.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-explore.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-goal.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-propose.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-quick.md +0 -0
- /package/{src/content → harness}/.opencode/commands/plan-story.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-audit.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-help.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-initialize.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-onboard.md +0 -0
- /package/{src/content → harness}/.opencode/commands/repo-verify.md +0 -0
- /package/{src/content → harness}/.opencode/plugins/pc-subagent-monitor.js +0 -0
- /package/{src/content → harness}/.opencode/plugins/pc-system-reminders.js +0 -0
- /package/{src/content → harness}/.opencode/tui/pc-subagents.tsx +0 -0
- /package/{src/content → harness}/.opencode/tui.json +0 -0
- /package/{src/content → harness}/AGENTS.md +0 -0
- /package/{src/content → harness}/openspec/changes/archive/.gitkeep +0 -0
- /package/{src/content → harness}/openspec/specs/.gitkeep +0 -0
|
@@ -1,161 +1,196 @@
|
|
|
1
|
-
import fse from 'fs-extra'
|
|
2
|
-
import { applyEdits, modify, parse } from 'jsonc-parser'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { success } from '../../utils/exec.js'
|
|
5
|
-
|
|
6
|
-
// The reminder plugin loads every agent ability before work, including
|
|
7
|
-
// user-installed skills outside the pc-* and openspec-* namespaces.
|
|
8
|
-
const SHARED_PERMISSIONS = [
|
|
9
|
-
['question', 'allow'],
|
|
10
|
-
['todowrite', 'allow'],
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
parsed?.
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return { patched: true }
|
|
161
|
-
}
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import { applyEdits, modify, parse } from 'jsonc-parser'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { success } from '../../utils/exec.js'
|
|
5
|
+
|
|
6
|
+
// The reminder plugin loads every agent ability before work, including
|
|
7
|
+
// user-installed skills outside the pc-* and openspec-* namespaces.
|
|
8
|
+
const SHARED_PERMISSIONS = [
|
|
9
|
+
['question', 'allow'],
|
|
10
|
+
['todowrite', 'allow'],
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
// agent-browser replaced the @different-ai/opencode-browser plugin; any entry
|
|
14
|
+
// with that prefix in the plugin array is stale and gets stripped on update.
|
|
15
|
+
const STALE_BROWSER_PLUGIN_PREFIX = '@different-ai/opencode-browser'
|
|
16
|
+
const AGENT_BROWSER_MCP = {
|
|
17
|
+
type: 'local',
|
|
18
|
+
command: ['agent-browser', 'mcp', '--tools', 'core'],
|
|
19
|
+
enabled: true,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyModify(text, jsonPath, value) {
|
|
23
|
+
const edits = modify(text, jsonPath, value, {
|
|
24
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
25
|
+
})
|
|
26
|
+
return applyEdits(text, edits)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function patchOpencodeJson(cwd = process.cwd()) {
|
|
30
|
+
const opencodePath = path.join(cwd, 'opencode.jsonc')
|
|
31
|
+
|
|
32
|
+
let text
|
|
33
|
+
if (await fse.pathExists(opencodePath)) {
|
|
34
|
+
text = await fse.readFile(opencodePath, 'utf-8')
|
|
35
|
+
} else {
|
|
36
|
+
text = JSON.stringify({ $schema: 'https://opencode.ai/config.json' }, null, 2)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const errors = []
|
|
40
|
+
const parsed = parse(text, errors)
|
|
41
|
+
if (errors.length > 0 || typeof parsed !== 'object') {
|
|
42
|
+
return { patched: false, reason: 'parse error' }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// build and plan are overridden, not disabled. Earlier versions disabled them
|
|
46
|
+
// and pointed default_agent at fullstack-engineer; now they are the only two
|
|
47
|
+
// primaries and pc-subagent-tiers regenerates their agent files from
|
|
48
|
+
// fullstack-engineer.md. A repo patched by an earlier version still carries
|
|
49
|
+
// `disable: true`, which would hide both agents, so that flag is cleared here.
|
|
50
|
+
const hasStaleDisable = (
|
|
51
|
+
parsed?.agent?.build?.disable !== undefined ||
|
|
52
|
+
parsed?.agent?.plan?.disable !== undefined
|
|
53
|
+
)
|
|
54
|
+
const needsAgentOverride = hasStaleDisable || !(
|
|
55
|
+
parsed?.agent?.build?.mode === 'primary' &&
|
|
56
|
+
parsed?.agent?.plan?.mode === 'primary' &&
|
|
57
|
+
parsed?.agent?.plan?.permission?.edit === 'deny'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// default_agent pointing at fullstack-engineer is now invalid: it became a
|
|
61
|
+
// subagent when build and plan took over as the only primaries, so opencode
|
|
62
|
+
// has no valid default to open a session with. A deliberate choice of build
|
|
63
|
+
// or plan is left alone; anything else is repointed at plan, so a session
|
|
64
|
+
// starts read-only.
|
|
65
|
+
const currentDefault = parsed?.default_agent
|
|
66
|
+
const needsDefaultAgent = currentDefault !== 'plan' && currentDefault !== 'build'
|
|
67
|
+
const needsSkillPermission = parsed?.permission?.skill !== 'allow'
|
|
68
|
+
const missingSharedPermissions = SHARED_PERMISSIONS.filter(
|
|
69
|
+
([key, value]) => parsed?.permission?.[key] !== value,
|
|
70
|
+
)
|
|
71
|
+
// opencode only scans .opencode/skills/ by default, but we install to .agents/skills/
|
|
72
|
+
const needsSkillsPaths = !(
|
|
73
|
+
Array.isArray(parsed?.skills?.paths) &&
|
|
74
|
+
parsed.skills.paths.includes('.agents/skills')
|
|
75
|
+
)
|
|
76
|
+
const needsCompaction = !(
|
|
77
|
+
parsed?.compaction?.auto === true &&
|
|
78
|
+
parsed?.compaction?.prune === true
|
|
79
|
+
)
|
|
80
|
+
const staleBrowserPlugins = (Array.isArray(parsed?.plugin) ? parsed.plugin : [])
|
|
81
|
+
.filter(entry => typeof entry === 'string' && entry.startsWith(STALE_BROWSER_PLUGIN_PREFIX))
|
|
82
|
+
const needsBrowserMcp = JSON.stringify(parsed?.mcp?.['agent-browser']) !== JSON.stringify(AGENT_BROWSER_MCP)
|
|
83
|
+
|
|
84
|
+
if (!needsAgentOverride && !needsDefaultAgent && !needsSkillPermission && missingSharedPermissions.length === 0 && !needsSkillsPaths && !needsCompaction && staleBrowserPlugins.length === 0 && !needsBrowserMcp) {
|
|
85
|
+
return { patched: false }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Apply edits sequentially so offsets stay correct
|
|
89
|
+
if (needsAgentOverride) {
|
|
90
|
+
// undefined removes the key, clearing the disable left by earlier versions.
|
|
91
|
+
if (parsed?.agent?.build?.disable !== undefined) {
|
|
92
|
+
text = applyModify(text, ['agent', 'build', 'disable'], undefined)
|
|
93
|
+
}
|
|
94
|
+
if (parsed?.agent?.plan?.disable !== undefined) {
|
|
95
|
+
text = applyModify(text, ['agent', 'plan', 'disable'], undefined)
|
|
96
|
+
}
|
|
97
|
+
text = applyModify(text, ['agent', 'build', 'mode'], 'primary')
|
|
98
|
+
text = applyModify(text, ['agent', 'plan', 'mode'], 'primary')
|
|
99
|
+
text = applyModify(text, ['agent', 'plan', 'permission', 'edit'], 'deny')
|
|
100
|
+
}
|
|
101
|
+
if (needsDefaultAgent) {
|
|
102
|
+
text = applyModify(text, ['default_agent'], 'plan')
|
|
103
|
+
}
|
|
104
|
+
if (needsSkillPermission) text = applyModify(text, ['permission', 'skill'], 'allow')
|
|
105
|
+
for (const [key, value] of missingSharedPermissions) {
|
|
106
|
+
text = applyModify(text, ['permission', key], value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Ensure .agents/skills is in skills.paths so opencode discovers installed skills
|
|
110
|
+
if (needsSkillsPaths) {
|
|
111
|
+
const existingPaths = Array.isArray(parsed?.skills?.paths) ? parsed.skills.paths : []
|
|
112
|
+
if (!existingPaths.includes('.agents/skills')) {
|
|
113
|
+
const updatedPaths = [...existingPaths, '.agents/skills']
|
|
114
|
+
text = applyModify(text, ['skills', 'paths'], updatedPaths)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Enable context pruning to reduce token costs in long agent sessions
|
|
119
|
+
if (needsCompaction) {
|
|
120
|
+
text = applyModify(text, ['compaction', 'auto'], true)
|
|
121
|
+
text = applyModify(text, ['compaction', 'prune'], true)
|
|
122
|
+
text = applyModify(text, ['compaction', 'reserved'], 10000)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Swap the retired opencode-browser plugin for the agent-browser MCP server
|
|
126
|
+
if (staleBrowserPlugins.length > 0) {
|
|
127
|
+
const keptPlugins = parsed.plugin.filter(entry => !staleBrowserPlugins.includes(entry))
|
|
128
|
+
text = applyModify(text, ['plugin'], keptPlugins)
|
|
129
|
+
}
|
|
130
|
+
if (needsBrowserMcp) {
|
|
131
|
+
text = applyModify(text, ['mcp', 'agent-browser'], AGENT_BROWSER_MCP)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fse.writeFile(opencodePath, text, 'utf-8')
|
|
135
|
+
if (needsAgentOverride) {
|
|
136
|
+
success('Set build/plan as the primary agents in opencode.jsonc (plan is read-only)')
|
|
137
|
+
}
|
|
138
|
+
if (needsDefaultAgent) {
|
|
139
|
+
success(`default_agent -> plan${currentDefault ? ` (was ${currentDefault})` : ''}`)
|
|
140
|
+
}
|
|
141
|
+
if (needsSkillPermission) {
|
|
142
|
+
success('Allowed skill loading in opencode.jsonc')
|
|
143
|
+
}
|
|
144
|
+
if (missingSharedPermissions.length > 0) {
|
|
145
|
+
success('Allowed question/todowrite tools in opencode.jsonc')
|
|
146
|
+
}
|
|
147
|
+
if (needsSkillsPaths) {
|
|
148
|
+
success('Added .agents/skills to skills.paths in opencode.jsonc')
|
|
149
|
+
}
|
|
150
|
+
if (needsCompaction) {
|
|
151
|
+
success('Enabled context pruning (compaction.prune) in opencode.jsonc')
|
|
152
|
+
}
|
|
153
|
+
if (staleBrowserPlugins.length > 0) {
|
|
154
|
+
success(`Removed ${staleBrowserPlugins.length} stale opencode-browser plugin entr${staleBrowserPlugins.length === 1 ? 'y' : 'ies'} in opencode.jsonc`)
|
|
155
|
+
}
|
|
156
|
+
if (needsBrowserMcp) {
|
|
157
|
+
success('Configured agent-browser MCP server in opencode.jsonc')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { patched: true }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const OPENCODE_PACKAGE_DEPENDENCIES = {
|
|
164
|
+
'@opencode-ai/plugin': '1.18.19',
|
|
165
|
+
'@opentui/core': '0.5.6',
|
|
166
|
+
'@opentui/solid': '0.5.6',
|
|
167
|
+
'solid-js': '1.9.12',
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// agent-browser replaced this plugin; remove it from consumer installs on update
|
|
171
|
+
const STALE_PACKAGE_DEPENDENCIES = ['@different-ai/opencode-browser']
|
|
172
|
+
|
|
173
|
+
export async function patchOpencodePackage(cwd = process.cwd()) {
|
|
174
|
+
const packagePath = path.join(cwd, '.opencode', 'package.json')
|
|
175
|
+
if (!await fse.pathExists(packagePath)) return { patched: false }
|
|
176
|
+
|
|
177
|
+
const packageJson = await fse.readJson(packagePath).catch(() => null)
|
|
178
|
+
if (!packageJson?.dependencies) return { patched: false }
|
|
179
|
+
|
|
180
|
+
let patched = false
|
|
181
|
+
for (const [name, version] of Object.entries(OPENCODE_PACKAGE_DEPENDENCIES)) {
|
|
182
|
+
if (!(name in packageJson.dependencies) || packageJson.dependencies[name] === version) continue
|
|
183
|
+
packageJson.dependencies[name] = version
|
|
184
|
+
patched = true
|
|
185
|
+
}
|
|
186
|
+
for (const name of STALE_PACKAGE_DEPENDENCIES) {
|
|
187
|
+
if (!(name in packageJson.dependencies)) continue
|
|
188
|
+
delete packageJson.dependencies[name]
|
|
189
|
+
patched = true
|
|
190
|
+
}
|
|
191
|
+
if (!patched) return { patched: false }
|
|
192
|
+
|
|
193
|
+
await fse.writeJson(packagePath, packageJson, { spaces: 2 })
|
|
194
|
+
success('Updated OpenCode plugin dependencies in .opencode/package.json')
|
|
195
|
+
return { patched: true }
|
|
196
|
+
}
|
|
@@ -5,8 +5,8 @@ import { info, success } from '../../utils/exec.js'
|
|
|
5
5
|
import { canUpdateManagedFile, readUpdateManifest, recordManagedFile, writeUpdateManifest } from '../../utils/update-manifest.js'
|
|
6
6
|
|
|
7
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const CONTENT_SKILLS_DIR = path.resolve(__dirname, '
|
|
9
|
-
const CONTENT_SKILLS_LOCK = path.resolve(__dirname, '
|
|
8
|
+
const CONTENT_SKILLS_DIR = path.resolve(__dirname, '../../../harness/.agents/skills')
|
|
9
|
+
const CONTENT_SKILLS_LOCK = path.resolve(__dirname, '../../../harness/skills-lock.json')
|
|
10
10
|
|
|
11
11
|
// Userstory skills parse backlog work items: selected by backlogPlatform only.
|
|
12
12
|
// Mixing the two axes here installs the wrong variant on mixed setups, because
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { search, select } from '@inquirer/prompts'
|
|
2
|
-
import fse from 'fs-extra'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const MODELS_PRESET_PATH = path.resolve(__dirname, '../../presets/models.json');
|
|
8
|
-
const modelsPreset = await fse.readJson(MODELS_PRESET_PATH);
|
|
9
|
-
|
|
10
|
-
function costTier(input) {
|
|
11
|
-
if (input === undefined || input === null) return '';
|
|
12
|
-
const tier = modelsPreset.costTiers.find(t => t.max === undefined || input <= t.max);
|
|
13
|
-
return tier ? ` ${tier.label}` : '';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function costTierDisplay(cost, canonicalCost) {
|
|
17
|
-
return costTier(canonicalCost !== undefined ? canonicalCost : cost);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function formatPrice(price) {
|
|
21
|
-
if (price === undefined || price === null) return '?';
|
|
22
|
-
if (price === 0) return '$0 (subscription)';
|
|
23
|
-
return `$${price}/M`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function buildDisplayModels(rawModels) {
|
|
27
|
-
return rawModels.map(m => {
|
|
28
|
-
const priceStr = formatPrice(m.cost);
|
|
29
|
-
const canonicalNote = m.canonicalCost !== undefined
|
|
30
|
-
? ` · official price: ${formatPrice(m.canonicalCost)}/M`
|
|
31
|
-
: '';
|
|
32
|
-
const context = m.context ? `${m.context / 1000}k` : '?';
|
|
33
|
-
return {
|
|
34
|
-
...m,
|
|
35
|
-
label: `${m.name}${costTierDisplay(m.cost, m.canonicalCost)}, ${m.id}`,
|
|
36
|
-
description: `${priceStr}${canonicalNote} · context: ${context}`,
|
|
37
|
-
};
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function buildModelChoices(input, models) {
|
|
42
|
-
const q = (input || '').toLowerCase();
|
|
43
|
-
const filtered = q
|
|
44
|
-
? models.filter(m =>
|
|
45
|
-
m.label.toLowerCase().includes(q) ||
|
|
46
|
-
m.id.toLowerCase().includes(q)
|
|
47
|
-
)
|
|
48
|
-
: models;
|
|
49
|
-
|
|
50
|
-
return [
|
|
51
|
-
{
|
|
52
|
-
name: 'None',
|
|
53
|
-
value: null,
|
|
54
|
-
description: 'Leave this model unset',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: '← Change provider',
|
|
58
|
-
value: '__back__',
|
|
59
|
-
description: 'Go back to provider selection',
|
|
60
|
-
},
|
|
61
|
-
...filtered.slice(0, 50).map(m => ({
|
|
62
|
-
name: m.label,
|
|
63
|
-
value: m.id,
|
|
64
|
-
description: m.description,
|
|
65
|
-
})),
|
|
66
|
-
];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function pickModel(message, models) {
|
|
70
|
-
return search({
|
|
71
|
-
message,
|
|
72
|
-
source: input => buildModelChoices(input, models),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function filterModelsByProvider(models, provider) {
|
|
77
|
-
if (!provider) return models
|
|
78
|
-
return models.filter(m => m.id.startsWith(`${provider}/`))
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function pickProvider(message) {
|
|
82
|
-
return select({
|
|
83
|
-
message,
|
|
84
|
-
choices: modelsPreset.providers.map(p => ({ value: p.value, name: p.name })),
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export { modelsPreset };
|
|
1
|
+
import { search, select } from '@inquirer/prompts'
|
|
2
|
+
import fse from 'fs-extra'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const MODELS_PRESET_PATH = path.resolve(__dirname, '../../presets/models.json');
|
|
8
|
+
const modelsPreset = await fse.readJson(MODELS_PRESET_PATH);
|
|
9
|
+
|
|
10
|
+
function costTier(input) {
|
|
11
|
+
if (input === undefined || input === null) return '';
|
|
12
|
+
const tier = modelsPreset.costTiers.find(t => t.max === undefined || input <= t.max);
|
|
13
|
+
return tier ? ` ${tier.label}` : '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function costTierDisplay(cost, canonicalCost) {
|
|
17
|
+
return costTier(canonicalCost !== undefined ? canonicalCost : cost);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatPrice(price) {
|
|
21
|
+
if (price === undefined || price === null) return '?';
|
|
22
|
+
if (price === 0) return '$0 (subscription)';
|
|
23
|
+
return `$${price}/M`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildDisplayModels(rawModels) {
|
|
27
|
+
return rawModels.map(m => {
|
|
28
|
+
const priceStr = formatPrice(m.cost);
|
|
29
|
+
const canonicalNote = m.canonicalCost !== undefined
|
|
30
|
+
? ` · official price: ${formatPrice(m.canonicalCost)}/M`
|
|
31
|
+
: '';
|
|
32
|
+
const context = m.context ? `${m.context / 1000}k` : '?';
|
|
33
|
+
return {
|
|
34
|
+
...m,
|
|
35
|
+
label: `${m.name}${costTierDisplay(m.cost, m.canonicalCost)}, ${m.id}`,
|
|
36
|
+
description: `${priceStr}${canonicalNote} · context: ${context}`,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildModelChoices(input, models) {
|
|
42
|
+
const q = (input || '').toLowerCase();
|
|
43
|
+
const filtered = q
|
|
44
|
+
? models.filter(m =>
|
|
45
|
+
m.label.toLowerCase().includes(q) ||
|
|
46
|
+
m.id.toLowerCase().includes(q)
|
|
47
|
+
)
|
|
48
|
+
: models;
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
name: 'None',
|
|
53
|
+
value: null,
|
|
54
|
+
description: 'Leave this model unset',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: '← Change provider',
|
|
58
|
+
value: '__back__',
|
|
59
|
+
description: 'Go back to provider selection',
|
|
60
|
+
},
|
|
61
|
+
...filtered.slice(0, 50).map(m => ({
|
|
62
|
+
name: m.label,
|
|
63
|
+
value: m.id,
|
|
64
|
+
description: m.description,
|
|
65
|
+
})),
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function pickModel(message, models) {
|
|
70
|
+
return search({
|
|
71
|
+
message,
|
|
72
|
+
source: input => buildModelChoices(input, models),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function filterModelsByProvider(models, provider) {
|
|
77
|
+
if (!provider) return models
|
|
78
|
+
return models.filter(m => m.id.startsWith(`${provider}/`))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function pickProvider(message) {
|
|
82
|
+
return select({
|
|
83
|
+
message,
|
|
84
|
+
choices: modelsPreset.providers.map(p => ({ value: p.value, name: p.name })),
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { modelsPreset };
|