docks-kit 0.17.0 → 0.17.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/AGENTS.md +1 -1
- package/README.md +1 -0
- package/cli/docs/omp-models.md +26 -5
- package/cli/src/commands/omp.ts +78 -8
- package/cli/src/engine-native/ompOverlay.ts +37 -0
- package/cli/src/generated/sotPayload.ts +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -75,7 +75,7 @@ omp SoT notes:
|
|
|
75
75
|
- Sync installs `pi-intercom` at the verified version from `SoT/toolchain.json`.
|
|
76
76
|
- The omp CLI is upstream-owned and self-updating through `omp update`. Sync never installs or upgrades the CLI.
|
|
77
77
|
- `docks-kit omp [--model <selector>|--pick] [args...]` starts one interactive omp session on a single free model. It renders a run overlay to `~/.cache/docks-kit/omp-free-<model>-<digest>.yml` (mode 0600) and passes it through omp's repeatable `--config` flag. `ompOverlay.ts overlayFileName` gives each model its own file, because omp can re-read the overlay during a live session and a second launcher on another model must not rewrite it. Deployed `~/.omp/agent/` files stay untouched, so the next plain `omp` run uses the paid configuration again. Remaining arguments forward verbatim to omp.
|
|
78
|
-
- The session model persists per machine in `~/.docks-kit/state.json` under `ompSession`, next to `harnesses`. The default is `opencode-zen/muse-spark-1.3-contributor-free` at `xhigh`. `ompOverlay.ts ladderCeiling, advisorLevelFor` derive both the
|
|
78
|
+
- The session model persists per machine in `~/.docks-kit/state.json` under `ompSession`, next to `harnesses`. The default is `opencode-zen/muse-spark-1.3-contributor-free` at `xhigh`. `ompOverlay.ts ladderCeiling, advisorLevelFor` derive both levels from the ladder of the chosen model for the `--model` path, and `ompOverlay.ts planEffortChoice` drives the `--pick` wizard, which asks whether every role shares one level and otherwise takes one level for the main roles and one for the advisor from that model's own ladder. Levels never come from a fixed list: free ladders are not uniform, three free models publish no `medium`, and two publish no ladder, which the overlay renders as bare selectors. The picker lists only zero-cost catalog models.
|
|
79
79
|
|
|
80
80
|
For per-tool SoT layouts (`SoT/.claude/`, `SoT/.codex/`, `SoT/.omp/`), see the matching SoT directory.
|
|
81
81
|
|
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ docks-kit harnesses view or change this machine's se
|
|
|
48
48
|
docks-kit update [--no-sync] self-update the kit (autodetects checkout vs global install), then sync
|
|
49
49
|
docks-kit model <claude|codex> [value] get/set the DEPLOYED model (TTY picker)
|
|
50
50
|
docks-kit models [claude|codex] model catalogs (`--json`)
|
|
51
|
+
docks-kit omp [--model <m>|--pick] [args...] one omp session on a free model, nothing deployed changes
|
|
51
52
|
docks-kit toolchain [check|ensure <tool>] verified-version floors for external tools
|
|
52
53
|
docks-kit status [--json] deployed-vs-SoT drift + toolchain + counts
|
|
53
54
|
docks-kit plugins list [--json] enabledPlugins tri-state vs installed
|
package/cli/docs/omp-models.md
CHANGED
|
@@ -254,21 +254,42 @@ fail when the session starts.
|
|
|
254
254
|
|
|
255
255
|
The choice persists per machine in `~/.docks-kit/state.json` under
|
|
256
256
|
`ompSession`, next to `harnesses`. It survives across sessions. It is never
|
|
257
|
-
committed. `--model <selector>` records one selector
|
|
258
|
-
interactive
|
|
257
|
+
committed. `--model <selector>` records one selector and derives both levels
|
|
258
|
+
from the catalog row. `--pick` opens an interactive wizard.
|
|
259
259
|
|
|
260
260
|
The picker lists only models the live `omp models --json` catalog reports at
|
|
261
261
|
zero input and output cost (26 entries on 2026-09-11). This path cannot start
|
|
262
262
|
a paid session. The catalog can advertise a free model that the account
|
|
263
263
|
cannot call; omp reports that provider error unchanged.
|
|
264
264
|
|
|
265
|
+
After the model, the wizard asks about thinking levels. `ompOverlay.ts
|
|
266
|
+
planEffortChoice` decides which questions apply, because omp accepts a
|
|
267
|
+
`:level` suffix only for a level the chosen model publishes:
|
|
268
|
+
|
|
269
|
+
- No ladder: the wizard asks nothing and the overlay omits every level.
|
|
270
|
+
- One level: the wizard states that level and asks nothing.
|
|
271
|
+
- Two or more levels: the wizard asks whether every role uses the same
|
|
272
|
+
level. Yes takes the highest level the model offers. No asks one level for
|
|
273
|
+
all roles except the advisor, then one level for the advisor.
|
|
274
|
+
|
|
275
|
+
The advisor question leads with a recommendation from `ompOverlay.ts
|
|
276
|
+
advisorRecommendation`: two steps down the model own ladder, clamped to the
|
|
277
|
+
lowest level that model publishes. The recommendation is the first row
|
|
278
|
+
because `Prompt.Select` starts on the first entry and accepts no initial
|
|
279
|
+
index, so `Enter` takes it. The full ladder follows in ladder order. A
|
|
280
|
+
recommendation equal to the chosen level is omitted rather than duplicated.
|
|
281
|
+
|
|
282
|
+
A session whose advisor differs from the other roles reports both on the
|
|
283
|
+
launch line.
|
|
284
|
+
|
|
265
285
|
An omp login is required. The kit owns no login flow. It surfaces omp's own
|
|
266
286
|
authentication error unchanged.
|
|
267
287
|
|
|
268
288
|
When the catalog renames the free variant, change only the default selector
|
|
269
|
-
constant. A ladder change needs no code change
|
|
270
|
-
the catalog row of the chosen model
|
|
271
|
-
|
|
289
|
+
constant. A ladder change needs no code change. Under `--model` both levels
|
|
290
|
+
are derived from the catalog row of the chosen model, and under `--pick` the
|
|
291
|
+
user chooses them from the ladder that same row publishes. Refresh the
|
|
292
|
+
recorded default and the ladder counts in these docs in the same commit.
|
|
272
293
|
|
|
273
294
|
## Maintenance
|
|
274
295
|
|
package/cli/src/commands/omp.ts
CHANGED
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
} from "../engine-native/harnesses"
|
|
13
13
|
import {
|
|
14
14
|
advisorLevelFor,
|
|
15
|
+
advisorRecommendation,
|
|
15
16
|
buildOmpArgs,
|
|
16
17
|
ladderCeiling,
|
|
17
18
|
overlayFileName,
|
|
19
|
+
planEffortChoice,
|
|
18
20
|
parseFreeModels,
|
|
19
21
|
renderFreeOverlay,
|
|
20
22
|
type CatalogModel
|
|
@@ -91,6 +93,64 @@ const loadFreeCatalog = (omp: string): Effect.Effect<ReadonlyArray<CatalogModel>
|
|
|
91
93
|
return free
|
|
92
94
|
})
|
|
93
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Ask for the thinking levels of one model. Every choice comes from that
|
|
98
|
+
* model own ladder, because omp rejects a `:level` suffix the model does not
|
|
99
|
+
* publish. A model with no ladder and a model with one level are settled
|
|
100
|
+
* without a question.
|
|
101
|
+
*/
|
|
102
|
+
const pickLevels = (model: CatalogModel) =>
|
|
103
|
+
Effect.gen(function* () {
|
|
104
|
+
const plan = planEffortChoice(model.thinking)
|
|
105
|
+
if (plan.kind === "none") {
|
|
106
|
+
yield* Console.error(`${model.name} publishes no thinking levels; the model default applies`)
|
|
107
|
+
return { thinking: undefined, advisorThinking: undefined }
|
|
108
|
+
}
|
|
109
|
+
if (plan.kind === "fixed") {
|
|
110
|
+
yield* Console.error(`${model.name} publishes one thinking level: ${plan.level}`)
|
|
111
|
+
return { thinking: plan.level, advisorThinking: plan.level }
|
|
112
|
+
}
|
|
113
|
+
const uniform = yield* Prompt.Select({
|
|
114
|
+
message: "Use the same thinking level for every role?",
|
|
115
|
+
choices: [
|
|
116
|
+
{ title: "Yes", value: true, description: `every role runs at ${plan.highest}, the highest this model offers` },
|
|
117
|
+
{ title: "No", value: false, description: "set the advisor apart from the other roles" }
|
|
118
|
+
]
|
|
119
|
+
})
|
|
120
|
+
if (uniform) return { thinking: plan.highest, advisorThinking: plan.highest }
|
|
121
|
+
const ladderChoices = plan.levels.map((level) => ({
|
|
122
|
+
title: level,
|
|
123
|
+
value: level,
|
|
124
|
+
description:
|
|
125
|
+
level === plan.highest ? "highest this model offers" : level === plan.levels[0] ? "lowest this model offers" : ""
|
|
126
|
+
}))
|
|
127
|
+
const thinking = yield* Prompt.Select({
|
|
128
|
+
message: "Thinking level for all roles except the advisor",
|
|
129
|
+
choices: ladderChoices
|
|
130
|
+
})
|
|
131
|
+
const suggested = advisorRecommendation(plan.levels, thinking)
|
|
132
|
+
// The list leads with the suggestion, because Prompt.Select always starts
|
|
133
|
+
// on the first entry and offers no initial index. The full ladder follows
|
|
134
|
+
// in ladder order, so a different level stays one keypress away. A
|
|
135
|
+
// suggestion equal to the main level would only duplicate a row.
|
|
136
|
+
const advisorChoices =
|
|
137
|
+
suggested === undefined || suggested === thinking
|
|
138
|
+
? ladderChoices
|
|
139
|
+
: [
|
|
140
|
+
{
|
|
141
|
+
title: suggested,
|
|
142
|
+
value: suggested,
|
|
143
|
+
description: `recommended, two levels below ${thinking}`
|
|
144
|
+
},
|
|
145
|
+
...ladderChoices
|
|
146
|
+
]
|
|
147
|
+
const advisorThinking = yield* Prompt.Select({
|
|
148
|
+
message: "Thinking level for the advisor role",
|
|
149
|
+
choices: advisorChoices
|
|
150
|
+
})
|
|
151
|
+
return { thinking, advisorThinking }
|
|
152
|
+
})
|
|
153
|
+
|
|
94
154
|
export const ompCommand = Command.make("omp", { model, pick, args }, (config) =>
|
|
95
155
|
Effect.gen(function* () {
|
|
96
156
|
if (Option.isSome(config.model) && config.pick) {
|
|
@@ -139,9 +199,14 @@ export const ompCommand = Command.make("omp", { model, pick, args }, (config) =>
|
|
|
139
199
|
})
|
|
140
200
|
const match = free.find((candidate) => candidate.selector === chosen)
|
|
141
201
|
if (match === undefined) return yield* bail(`Model '${chosen}' is not in the free catalog`)
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
202
|
+
const chosenLevels = yield* pickLevels(match)
|
|
203
|
+
yield* Effect.sync(() =>
|
|
204
|
+
writeOmpSessionModel(home, {
|
|
205
|
+
selector: match.selector,
|
|
206
|
+
thinking: chosenLevels.thinking,
|
|
207
|
+
advisorThinking: chosenLevels.advisorThinking
|
|
208
|
+
})
|
|
209
|
+
)
|
|
145
210
|
}
|
|
146
211
|
|
|
147
212
|
const session: OmpSessionModel = (yield* Effect.sync(() => readOmpSessionModel(home))) ??
|
|
@@ -163,12 +228,17 @@ export const ompCommand = Command.make("omp", { model, pick, args }, (config) =>
|
|
|
163
228
|
})
|
|
164
229
|
|
|
165
230
|
// A level-free model states the model default so the line never shows
|
|
166
|
-
// an undefined level.
|
|
167
|
-
|
|
231
|
+
// an undefined level. The advisor appears only when it differs, because
|
|
232
|
+
// a uniform session has nothing extra to report.
|
|
233
|
+
const level = typeof session.thinking === "string" && session.thinking.trim() !== "" ? session.thinking : undefined
|
|
234
|
+
const advisor =
|
|
235
|
+
typeof session.advisorThinking === "string" && session.advisorThinking.trim() !== ""
|
|
236
|
+
? session.advisorThinking
|
|
237
|
+
: undefined
|
|
238
|
+
const levelText = level === undefined ? "the model default thinking level" : `${level} thinking`
|
|
239
|
+
const advisorText = advisor !== undefined && advisor !== level ? `, advisor at ${advisor}` : ""
|
|
168
240
|
yield* Console.error(
|
|
169
|
-
|
|
170
|
-
? `Starting omp with ${session.selector} at ${session.thinking} thinking (session only; deployed config unchanged)`
|
|
171
|
-
: `Starting omp with ${session.selector} at the model default thinking level (session only; deployed config unchanged)`
|
|
241
|
+
`Starting omp with ${session.selector} at ${levelText}${advisorText} (session only; deployed config unchanged)`
|
|
172
242
|
)
|
|
173
243
|
|
|
174
244
|
const child = yield* Effect.sync(() =>
|
|
@@ -134,6 +134,43 @@ export function advisorLevelFor(levels: ReadonlyArray<string>): string | undefin
|
|
|
134
134
|
return advisor ?? (known[0] as string)
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* What the picker may ask about thinking levels for one model. omp accepts a
|
|
139
|
+
* `:level` suffix on a role only for a level the model publishes, so the
|
|
140
|
+
* question set comes from the model own ladder: no ladder means no question,
|
|
141
|
+
* a single level means no choice to make, and two or more levels mean the
|
|
142
|
+
* user can split the advisor from the other roles.
|
|
143
|
+
*/
|
|
144
|
+
export type EffortPlan =
|
|
145
|
+
| { readonly kind: "none" }
|
|
146
|
+
| { readonly kind: "fixed"; readonly level: string }
|
|
147
|
+
| { readonly kind: "choose"; readonly levels: ReadonlyArray<string>; readonly highest: string }
|
|
148
|
+
|
|
149
|
+
export function planEffortChoice(levels: ReadonlyArray<string>): EffortPlan {
|
|
150
|
+
const known = THINKING_LADDER.filter((level) => levels.includes(level))
|
|
151
|
+
const only = known[0]
|
|
152
|
+
if (only === undefined) return { kind: "none" }
|
|
153
|
+
if (known.length === 1) return { kind: "fixed", level: only }
|
|
154
|
+
return { kind: "choose", levels: known, highest: known[known.length - 1] as string }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Recommend an advisor level for a chosen main level. The advisor runs quick
|
|
159
|
+
* review passes, so it sits two steps down the model own ladder, and it
|
|
160
|
+
* clamps to the lowest level the model publishes. Steps count positions in
|
|
161
|
+
* that model ladder, not the global ladder, so a sparse ladder such as
|
|
162
|
+
* `high, max` still recommends a real level.
|
|
163
|
+
*/
|
|
164
|
+
export function advisorRecommendation(
|
|
165
|
+
levels: ReadonlyArray<string>,
|
|
166
|
+
main: string
|
|
167
|
+
): string | undefined {
|
|
168
|
+
const known = THINKING_LADDER.filter((level) => levels.includes(level))
|
|
169
|
+
const index = known.indexOf(main)
|
|
170
|
+
if (index < 0) return known[0]
|
|
171
|
+
return known[Math.max(0, index - 2)]
|
|
172
|
+
}
|
|
173
|
+
|
|
137
174
|
/**
|
|
138
175
|
* Render the run overlay as YAML text. Empty fallback chains keep a retry
|
|
139
176
|
* from falling back onto a paid model. A model without a level renders bare
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by cli/scripts/generate-sot-payload.ts. DO NOT EDIT.
|
|
2
2
|
// Edit SoT/, notification.mp3, or package.json, then run: bun cli/scripts/generate-sot-payload.ts
|
|
3
3
|
|
|
4
|
-
export const GENERATED_PACKAGE_VERSION = "0.17.
|
|
4
|
+
export const GENERATED_PACKAGE_VERSION = "0.17.1"
|
|
5
5
|
|
|
6
6
|
export const GENERATED_PAYLOAD_TEXT = {
|
|
7
7
|
"SoT/.agents/skills.txt": "# Universal AI-agent skill manifest intentionally empty.\n# Global skill discovery is opt-in: add one <owner>/<repo> slug per line.\n# EngineNative ignores comments and blank lines.\n",
|
package/package.json
CHANGED