omo-ai 5.1.38 → 5.1.41

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.
Files changed (76) hide show
  1. package/bin/lib/launcher.js +2 -0
  2. package/bin/lib/legacy-service-credentials.js +36 -0
  3. package/bin/lib/provider-map.json +1 -1
  4. package/bin/lib/setup-credentials.js +7 -1
  5. package/bin/lib/setup-import.js +13 -63
  6. package/bin/lib/setup-model-choices-import.js +8 -5
  7. package/bin/lib/setup-providers-import.js +11 -4
  8. package/bin/lib/setup-stream-items.js +64 -0
  9. package/bin/lib/setup-stream.d.ts +94 -0
  10. package/bin/lib/setup-stream.js +238 -0
  11. package/package.json +2 -2
  12. package/plugin/CHANGELOG.md +74 -0
  13. package/plugin/extensions/CaseFolding.txt +1654 -0
  14. package/plugin/extensions/dream-persona.md +2 -2
  15. package/plugin/extensions/facts-persona.md +12 -1
  16. package/plugin/extensions/gateway-rules-extension.mjs +1 -1
  17. package/plugin/extensions/gateway-store-worker.mjs +1 -1
  18. package/plugin/extensions/loro_wasm_bg.wasm +0 -0
  19. package/plugin/extensions/memory-run-supervisor.mjs +2 -2
  20. package/plugin/extensions/omo-computer-use.js +2 -2
  21. package/plugin/extensions/omo-feedback.js +2 -0
  22. package/plugin/extensions/omo-init-deep-advisor.js +1 -1
  23. package/plugin/extensions/omo-member.js +2 -2
  24. package/plugin/extensions/omo-memory-doctor.js +2 -2
  25. package/plugin/extensions/omo-memory-memfs.js +2 -2
  26. package/plugin/extensions/omo-memory-palace.js +2 -2
  27. package/plugin/extensions/omo-memory-sync.js +2 -0
  28. package/plugin/extensions/omo-onboarding-scan.js +2 -0
  29. package/plugin/extensions/omo-recall-store.js +2 -0
  30. package/plugin/extensions/omo-session-history.js +2 -0
  31. package/plugin/extensions/omo-side-panel.js +2 -2
  32. package/plugin/extensions/omo-task.js +2 -2
  33. package/plugin/extensions/omo-telemetry-posthog.js +2 -0
  34. package/plugin/extensions/omo-thread-client.js +1 -1
  35. package/plugin/extensions/omo.js +2 -2
  36. package/plugin/extensions/reflection-persona.md +1 -1
  37. package/plugin/package.json +6 -1
  38. package/plugin/runtime/agent-toolkit-sdk/sdk.js +2 -2
  39. package/plugin/runtime/ast-grep-mcp/manifest.json +1 -1
  40. package/plugin/runtime/category-coverage/index.js +2 -2
  41. package/plugin/runtime/lsp-daemon/dist/.omo-runtime-manifest.json +4 -4
  42. package/plugin/runtime/lsp-daemon/dist/cli.js +10 -0
  43. package/plugin/runtime/lsp-daemon/dist/client.js +10 -0
  44. package/plugin/runtime/lsp-daemon/dist/index.js +10 -0
  45. package/plugin/runtime/rollback-migrate.js +1 -1
  46. package/plugin/runtime/task-config/index.js +1 -1
  47. package/plugin/runtime/thread-sdk/sdk.js +2 -2
  48. package/plugin/scripts/install.mjs +7 -0
  49. package/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  50. package/plugin/skills/frontend/SKILL.md +6 -6
  51. package/plugin/skills/frontend/references/design/README.md +2 -2
  52. package/plugin/skills/frontend/references/perfection/README.md +2 -2
  53. package/plugin/skills/frontend/scripts/perfection/accent-stripes/declarations.ts +22 -13
  54. package/plugin/skills/frontend/scripts/perfection/accent-stripes/findings.ts +7 -6
  55. package/plugin/skills/frontend/scripts/perfection/accent-stripes/lint.ts +18 -24
  56. package/plugin/skills/frontend/scripts/perfection/accent-stripes/markup.ts +14 -7
  57. package/plugin/skills/frontend/scripts/perfection/accent-stripes/selector.ts +5 -1
  58. package/plugin/skills/frontend/scripts/perfection/accent-stripes/tailwind.ts +32 -14
  59. package/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +1 -1
  60. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/inline-ok.tsx +1 -0
  61. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/neutral-and-flat.css +1 -1
  62. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/tailwind-ok.tsx +5 -2
  63. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/negative/uniform-and-rings.css +2 -2
  64. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/accent-outlines.css +4 -0
  65. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/accent-outlines.tsx +8 -0
  66. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/{negative → positive}/flat-stripe-side.tsx +3 -1
  67. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/report-callouts.html +8 -0
  68. package/plugin/skills/frontend/scripts/perfection/fixtures/accent-stripes/positive/square-surfaces.css +8 -0
  69. package/plugin/skills/git-master/SKILL.md +1 -1
  70. package/plugin/skills/onboarding/SKILL.md +56 -23
  71. package/plugin/skills/ulw-plan/SKILL.md +1 -1
  72. package/plugin/skills/ulw-plan/references/full-workflow.md +6 -5
  73. package/plugin/skills/ulw-research/SKILL.md +10 -2
  74. package/plugin/skills/ulw-research/references/deliverable-phase.md +10 -0
  75. package/plugin/skills/ulw-research/references/report-visualization.md +23 -0
  76. package/plugin/skills/visualize/SKILL.md +4 -3
@@ -12,6 +12,7 @@ import { isHostStatusAll, runHostStatusAll, sessionActivityReader } from "./host
12
12
  import { runDoctor } from "./doctor.js"
13
13
  import { ensureEnginePrepared, preparePluginLaunchSpec } from "./engine-prepare.js"
14
14
  import { migrateLegacyBunGlobalManifest } from "./legacy-bun-global-migration.js"
15
+ import { removeLegacyServiceCredentials } from "./legacy-service-credentials.js"
15
16
  import { adoptLegacyFlatState, canonicalAgentDir } from "./agent-dir.js"
16
17
  import { senpiEnvironment } from "./engine-environment.js"
17
18
  import { withHerdrAgentHint } from "./herdr-agent-hint.js"
@@ -130,6 +131,7 @@ export function rollbackMigrateCall(request) {
130
131
 
131
132
  export async function runLauncher(args = process.argv.slice(2)) {
132
133
  migrateLegacyBunGlobalManifest()
134
+ removeLegacyServiceCredentials()
133
135
  reportLegacyFlatAdoption()
134
136
  const command = args[0]
135
137
  // The toolkit CLI is no longer part of the Native payload; the loop is driven in-process by the
@@ -0,0 +1,36 @@
1
+ import { lstatSync, readFileSync, unlinkSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import { join } from "node:path"
4
+
5
+ // An intermediate sign-in build left device private keys and refresh material in a plaintext
6
+ // `~/.omo/credentials.json`, sometimes world-readable (#10697). Credentials now live only in the OS
7
+ // keystore, so nothing reads this file; remove it on launch. Only the exact legacy shape is removed:
8
+ // a regular file (never a symlink or FIFO) whose JSON object carries both private keys.
9
+ const MAX_LEGACY_BYTES = 64 * 1024
10
+
11
+ export function removeLegacyServiceCredentials(home = process.env.HOME || process.env.USERPROFILE || homedir()) {
12
+ const path = join(home, ".omo", "credentials.json")
13
+ let stats
14
+ try {
15
+ stats = lstatSync(path)
16
+ } catch {
17
+ return false
18
+ }
19
+ if (!stats.isFile() || stats.size > MAX_LEGACY_BYTES) return false
20
+
21
+ let parsed
22
+ try {
23
+ parsed = JSON.parse(readFileSync(path, "utf8"))
24
+ } catch {
25
+ return false
26
+ }
27
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return false
28
+ if (!("signingPrivateKey" in parsed) || !("sealingPrivateKey" in parsed)) return false
29
+
30
+ try {
31
+ unlinkSync(path)
32
+ } catch {
33
+ return false
34
+ }
35
+ return true
36
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "Derived from @code-yeongyu/senpi 2026.10.19 builtinProviders(). builtinProviderIds and oauthProviderIds are the engine registry verbatim; providers maps a source harness id onto the omo provider that serves the same endpoint; oauthLogins maps a source OAuth id onto the omo provider to run /login for. Re-derive all four whenever the senpi pin changes.",
2
+ "_comment": "Derived from @code-yeongyu/senpi 2026.10.21-1 builtinProviders(). builtinProviderIds and oauthProviderIds are the engine registry verbatim; providers maps a source harness id onto the omo provider that serves the same endpoint; oauthLogins maps a source OAuth id onto the omo provider to run /login for. Re-derive all four whenever the senpi pin changes.",
3
3
  "builtinProviderIds": [
4
4
  "alibaba-token-plan",
5
5
  "amazon-bedrock",
@@ -165,7 +165,13 @@ export async function planCredentials(runtime, target, customProviderIds) {
165
165
  }
166
166
  }
167
167
 
168
- export function applyCredentials(plan) {
168
+ /**
169
+ * The stage's one writer. With `options.writeAuth` (the engine's locked credential store) the
170
+ * additions merge into whatever the store holds under its lock; the CLI path keeps its direct
171
+ * backup-and-write.
172
+ */
173
+ export function applyCredentials(plan, options = {}) {
174
+ if (options.writeAuth !== undefined) return options.writeAuth(plan.target, plan.result.additions)
169
175
  writeAuthStore(plan.target, plan.current, plan.result.additions)
170
176
  }
171
177
 
@@ -1,20 +1,16 @@
1
1
  /**
2
- * `omo setup`: every stage's plan is built first (nothing is written while planning), the one
3
- * summary renders them all, one consent covers the whole plan, then each stage applies its part.
4
- * `--ask-each` asks per stage instead; `--yes` consents; `--dry-run` prints the summary and exits.
2
+ * `omo setup`: every stage's plan is built first from the setup stream (nothing is written while
3
+ * planning), the one summary renders them all, one consent covers the whole plan, then each stage
4
+ * applies its part. `--ask-each` asks per stage instead; `--yes` consents; `--dry-run` prints the
5
+ * summary and exits.
5
6
  */
6
7
 
7
8
  import { homedir } from "node:os"
8
- import { dirname, join, relative } from "node:path"
9
+ import { dirname, relative } from "node:path"
9
10
  import { createInterface } from "node:readline/promises"
10
11
  import { canonicalAgentDir } from "./agent-dir.js"
11
12
  import { setupCoverage } from "./category-coverage.js"
12
- import { detectHarnesses } from "./setup-detect.js"
13
- import { applyCredentials, credentialCounts, credentialPlanLines, credentialQuestion, opencodeOauthProviders, planCredentials } from "./setup-credentials.js"
14
- import { applyAssets, assetCounts, assetPlanLines, assetQuestion, planAssets } from "./setup-assets-import.js"
15
- import { planOpencodeProviders } from "./setup-opencode-providers.js"
16
- import { applyProviders, planProviders, providerCounts, providerPlanLines, providerQuestion } from "./setup-providers-import.js"
17
- import { applyModelChoices, modelChoiceCounts, modelChoicePlanLines, modelChoiceQuestion, planModelChoices } from "./setup-model-choices-import.js"
13
+ import { applySetupStages, collectSetupPlans, scanSetup, setupStages } from "./setup-stream.js"
18
14
  import { displayPath, formatSetupSummary, TELEMETRY_NOTICE } from "./setup-summary.js"
19
15
 
20
16
  function write(lines) {
@@ -37,17 +33,6 @@ async function ask(question, options, defaultYes) {
37
33
  }
38
34
  }
39
35
 
40
- // In apply order: the model-choice stage runs last because a default model or category may name a
41
- // custom provider the stage before it carries. `present` stages report counts after the run.
42
- function stageList(plans) {
43
- return [
44
- { plan: plans.credentials, present: !plans.credentials.malformed, lines: credentialPlanLines, counts: credentialCounts, question: credentialQuestion, apply: applyCredentials },
45
- { plan: plans.assets, present: plans.assets.present, lines: assetPlanLines, counts: assetCounts, question: assetQuestion, apply: applyAssets },
46
- { plan: plans.providers, present: plans.providers.present, lines: providerPlanLines, counts: providerCounts, question: providerQuestion, apply: applyProviders },
47
- { plan: plans.modelChoices, present: plans.modelChoices.present, lines: modelChoicePlanLines, counts: modelChoiceCounts, question: modelChoiceQuestion, apply: applyModelChoices },
48
- ]
49
- }
50
-
51
36
  // The directories the whole plan writes into, a directory inside another one folded into it.
52
37
  function targetRoots(plans, agentDir, home) {
53
38
  const choiceDirs = plans.modelChoices.items.filter((item) => item.state === "pending").map((item) => dirname(item.target.path))
@@ -56,51 +41,20 @@ function targetRoots(plans, agentDir, home) {
56
41
  return roots.filter((path) => !roots.some((root) => inside(path, root))).map((path) => displayPath(path, home))
57
42
  }
58
43
 
59
- async function planAll(runtime, agentDir) {
60
- const providerSource = planOpencodeProviders(runtime)
61
- // A provider the custom-provider stage described (carried or refused with its reason) is reported there only.
62
- const described = new Set([...providerSource.providers.map((item) => item.id), ...providerSource.skipped])
63
- const credentials = await planCredentials(runtime, join(agentDir, "auth.json"), described)
64
- const assets = planAssets({ runtime, agentDir })
65
- const providers = planProviders(providerSource, agentDir)
66
- const oauthProviders = opencodeOauthProviders(runtime)
67
- const planChoices = (customProviders) => planModelChoices({ runtime, agentDir, customProviders, oauthProviders })
68
- return { credentials, assets, providers, modelChoices: await planChoices(providers.result.added), planChoices }
69
- }
70
-
71
- // Per-stage consent. A declined provider stage leaves models.json as it is, so the model choices
72
- // are re-planned against it rather than against providers that will not exist.
73
- async function applyEach(plans, confirm) {
74
- const applied = new Set()
75
- for (const [index, stage] of stageList(plans).entries()) {
76
- if (index === 3 && plans.providers.pending > 0 && !applied.has(2)) {
77
- const shown = new Set(plans.modelChoices.notices)
78
- plans.modelChoices = await plans.planChoices([])
79
- stage.plan = plans.modelChoices
80
- write(plans.modelChoices.notices.filter((notice) => !shown.has(notice)))
81
- }
82
- if (stage.plan.pending === 0) continue
83
- if (!await confirm(stage.question(stage.plan), false)) continue
84
- stage.apply(stage.plan)
85
- applied.add(index)
86
- }
87
- return applied
88
- }
89
-
90
44
  export async function runSetup(args = process.argv.slice(2), options = {}) {
91
45
  const home = options.home ?? homedir()
92
46
  const env = options.env ?? process.env
93
47
  const agentDir = canonicalAgentDir(env, home)
94
48
  const runtime = { stdin: process.stdin, stdout: process.stdout, ...options, home, env }
95
49
  const dryRun = args.includes("--dry-run")
96
- const inventory = await detectHarnesses(runtime)
97
- const plans = await planAll(runtime, agentDir)
50
+ // The CLI has always carried OpenCode's custom providers; the onboarding RPC leaves them opt-in.
51
+ const { inventory, plans } = await collectSetupPlans(scanSetup(runtime, agentDir, { includeCustomProviders: true }))
98
52
  if (dryRun) process.stdout.write("DRY RUN: no files will be written\n")
99
53
  const categories = await setupCoverage({ agentDir, home, env, plans, loadRuntime: options.loadCoverageRuntime, loadEngine: options.loadCoverageEngine })
100
54
  process.stdout.write(formatSetupSummary({ home, agentDir, inventory, ...plans, categories }))
101
55
  process.stdout.write(`${TELEMETRY_NOTICE}\n`)
102
56
  if (dryRun) {
103
- write(stageList(plans).filter((stage) => stage.present).flatMap((stage) => stage.lines(stage.plan)))
57
+ write(setupStages(plans).filter((stage) => stage.present).flatMap((stage) => stage.lines(stage.plan)))
104
58
  return
105
59
  }
106
60
  const confirm = async (question, defaultYes) => {
@@ -108,19 +62,15 @@ export async function runSetup(args = process.argv.slice(2), options = {}) {
108
62
  if (!accepted && runtime.stdin.isTTY === true) process.stdout.write("Import cancelled\n")
109
63
  return accepted
110
64
  }
111
- const pending = stageList(plans).reduce((total, stage) => total + stage.plan.pending, 0)
65
+ const pending = setupStages(plans).reduce((total, stage) => total + stage.plan.pending, 0)
112
66
  let applied = new Set()
113
67
  if (pending === 0) {
114
68
  process.stdout.write("Nothing new to import.\n")
115
69
  } else if (args.includes("--ask-each")) {
116
- applied = await applyEach(plans, confirm)
70
+ applied = await applySetupStages(plans, (stage) => confirm(stage.question(stage.plan), false), (line) => write([line]))
117
71
  } else {
118
72
  if (!await confirm(`Import all of the above into ${targetRoots(plans, agentDir, home).join(" and ")}? [Y/n] `, true)) return
119
- for (const [index, stage] of stageList(plans).entries()) {
120
- if (stage.plan.pending === 0) continue
121
- stage.apply(stage.plan)
122
- applied.add(index)
123
- }
73
+ applied = await applySetupStages(plans, () => true, (line) => write([line]))
124
74
  }
125
- write(stageList(plans).filter((stage, index) => stage.present && (applied.has(index) || stage.plan.pending === 0)).flatMap((stage) => stage.counts(stage.plan)))
75
+ write(setupStages(plans).filter((stage) => stage.present && (applied.has(stage.id) || stage.plan.pending === 0)).flatMap((stage) => stage.counts(stage.plan)))
126
76
  }
@@ -151,7 +151,7 @@ function writeText(target, text) {
151
151
 
152
152
  // Every edit is an insertion into the file's own text (comments, order and formatting survive);
153
153
  // a file the edit cannot reproduce exactly is left untouched and reported.
154
- function writeTarget(target, items) {
154
+ function writeTarget(target, items, report) {
155
155
  try {
156
156
  let text = target.text ?? "{}\n"
157
157
  for (const item of items) {
@@ -160,7 +160,7 @@ function writeTarget(target, items) {
160
160
  writeText(target, text)
161
161
  return items.map((item) => item.label)
162
162
  } catch (error) {
163
- process.stdout.write(`WARN senpi: could not edit ${target.path} safely (${error.message}); these model choices were not written: ${items.map((item) => item.label).join(", ")}\n`)
163
+ report(`WARN senpi: could not edit ${target.path} safely (${error.message}); these model choices were not written: ${items.map((item) => item.label).join(", ")}`)
164
164
  return []
165
165
  }
166
166
  }
@@ -198,9 +198,12 @@ export async function planModelChoices(stage) {
198
198
  }
199
199
  }
200
200
 
201
- /** The stage's one writer; records what it wrote as `plan.carried` for the counts. */
202
- export function applyModelChoices(plan) {
201
+ /** The stage's one writer; records what it wrote as `plan.carried` for the counts. `options.report`
202
+ * receives the warning when a target cannot be edited (the CLI prints it; callers inside the
203
+ * engine route it to their own reply). */
204
+ export function applyModelChoices(plan, options = {}) {
205
+ const report = options.report ?? ((line) => process.stdout.write(`${line}\n`))
203
206
  const pending = plan.items.filter((item) => item.state === "pending")
204
207
  const targets = [...new Set(pending.map((item) => item.target))]
205
- plan.carried = targets.flatMap((target) => writeTarget(target, pending.filter((item) => item.target === target)))
208
+ plan.carried = targets.flatMap((target) => writeTarget(target, pending.filter((item) => item.target === target), report))
206
209
  }
@@ -101,18 +101,25 @@ function writeModels(path, target, added) {
101
101
  export function planProviders(source, agentDir) {
102
102
  const paths = { models: join(agentDir, "models.json"), auth: join(agentDir, "auth.json") }
103
103
  const models = readModelsTarget(paths.models)
104
- const result = classify(source, models, readAuthStore(paths.auth))
104
+ const auth = readAuthStore(paths.auth)
105
+ const result = classify(source, models, auth)
105
106
  const notices = [...source.notices]
106
107
  if (result.blocked.length > 0) notices.push(`WARN senpi: malformed models.json; these custom providers were not imported: ${result.blocked.join(", ")}`)
107
- return { paths, models, notices, result, skipped: source.skipped, present: source.providers.length > 0, pending: result.added.length }
108
+ return { paths, models, authBytes: auth.bytes, notices, result, skipped: source.skipped, present: source.providers.length > 0, pending: result.added.length }
108
109
  }
109
110
 
110
- export function applyProviders(plan) {
111
+ /**
112
+ * The stage's one writer. With `options.writeAuth` the provider keys merge into whatever the
113
+ * store holds under its lock; the CLI path keeps its direct backup-and-write.
114
+ */
115
+ export function applyProviders(plan, options = {}) {
111
116
  writeModels(plan.paths.models, plan.models, plan.result.added)
112
117
  // Re-read: the credential stage of the same run may have just written auth.json, after this plan
113
118
  // was classified. A mapped credential target can also be a custom provider id (claude-sdk-oauth ->
114
119
  // anthropic-subscription), so a key written since the plan is kept, never overwritten.
115
120
  const auth = readAuthStore(plan.paths.auth)
116
121
  plan.result.keys = auth.malformed ? [] : plan.result.keys.filter((item) => !Object.hasOwn(auth.entries, item.provider))
117
- if (plan.result.keys.length > 0) writeAuthStore(plan.paths.auth, auth, plan.result.keys)
122
+ if (plan.result.keys.length === 0) return
123
+ if (options.writeAuth !== undefined) return options.writeAuth(plan.paths.auth, plan.result.keys)
124
+ writeAuthStore(plan.paths.auth, auth, plan.result.keys)
118
125
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * One `{id, kind, state, detail?}` row per thing a setup source found, built from the stage plan
3
+ * the summary renders. Rows travel over RPC, so they carry ids and states only: never a key.
4
+ */
5
+
6
+ const MODEL_CHOICE_STATES = { pending: "import", kept: "exists", carried: "carried", blocked: "blocked" }
7
+
8
+ function rows(ids, kind, state) {
9
+ return ids.map((id) => ({ id, kind, state }))
10
+ }
11
+
12
+ function detectItems(inventory) {
13
+ return inventory.harnesses
14
+ .filter((harness) => harness.installed)
15
+ .map((harness) => ({ id: harness.id, kind: "harness", state: "found", detail: `${harness.entryCount} login(s), ${harness.importableCount} importable` }))
16
+ }
17
+
18
+ function providerItems(plan) {
19
+ const { result } = plan
20
+ return [
21
+ ...result.added.map((provider) => ({ id: provider.id, kind: "provider", state: "import", detail: `${provider.config.models.length} model(s)` })),
22
+ ...rows(result.skippedExisting, "provider", "exists"),
23
+ ...rows(result.blocked, "provider", "blocked"),
24
+ ...rows(plan.skipped, "provider", "skipped"),
25
+ ]
26
+ }
27
+
28
+ function credentialItems(plan) {
29
+ if (plan.malformed) return []
30
+ const { result, guidance } = plan
31
+ return [
32
+ ...result.additions.map((item) => ({ id: item.provider, kind: "api-key", state: "import", detail: `from ${item.source}` })),
33
+ ...rows(result.skippedExisting, "api-key", "exists"),
34
+ ...guidance.logins.map((login) => ({ id: login.provider, kind: "oauth", state: login.state, ...(login.target === undefined ? {} : { detail: `/login ${login.target}` }) })),
35
+ ...rows(result.skippedUnmapped, "api-key", "unmapped"),
36
+ ]
37
+ }
38
+
39
+ function assetItems({ result }) {
40
+ return [
41
+ ...result.servers.added.map((server) => ({ id: server.name, kind: "mcp", state: "import" })),
42
+ ...rows(result.servers.skippedExisting, "mcp", "exists"),
43
+ ...rows(result.servers.blocked, "mcp", "blocked"),
44
+ ...result.skills.added.map((skill) => ({ id: skill.name, kind: "skill", state: "import" })),
45
+ ...rows(result.skills.skippedExisting, "skill", "exists"),
46
+ ...rows(result.skills.skippedBundled, "skill", "bundled"),
47
+ ]
48
+ }
49
+
50
+ function modelChoiceItems(plan) {
51
+ return plan.items.map((item) => ({ id: item.label, kind: item.kind, state: MODEL_CHOICE_STATES[item.state] ?? item.state }))
52
+ }
53
+
54
+ const ITEMS_BY_SOURCE = {
55
+ detect: detectItems,
56
+ providers: providerItems,
57
+ credentials: credentialItems,
58
+ assets: assetItems,
59
+ "model-choices": modelChoiceItems,
60
+ }
61
+
62
+ export function setupStageItems(source, plan) {
63
+ return ITEMS_BY_SOURCE[source](plan)
64
+ }
@@ -0,0 +1,94 @@
1
+ // Types for setup-stream.js, which omo-senpi's onboarding scan imports across the package
2
+ // boundary: omo-ai ships this module as plain JS, so it is the single source of the setup stream.
3
+
4
+ export type SetupSource = "detect" | "providers" | "credentials" | "assets" | "model-choices"
5
+ export type SetupStageId = "credentials" | "assets" | "providers" | "model-choices"
6
+
7
+ export const SETUP_SOURCES: readonly SetupSource[]
8
+
9
+ export type SetupItem = {
10
+ readonly id: string
11
+ readonly kind: string
12
+ readonly state: string
13
+ readonly detail?: string
14
+ }
15
+
16
+ export type SetupStagePlan = {
17
+ readonly pending: number
18
+ readonly notices?: readonly string[]
19
+ readonly [field: string]: unknown
20
+ }
21
+
22
+ export type SetupStageResult = {
23
+ readonly source: SetupSource
24
+ readonly items: readonly SetupItem[]
25
+ readonly notices: readonly string[]
26
+ readonly plan: SetupStagePlan
27
+ readonly disabled?: true
28
+ readonly error?: Error
29
+ }
30
+
31
+ export type SetupRuntime = {
32
+ readonly home: string
33
+ readonly env: Readonly<Record<string, string | undefined>>
34
+ readonly pluginRoot?: string
35
+ readonly loadModelCatalog?: () => Promise<readonly unknown[]>
36
+ readonly loadSqlite?: () => Promise<unknown>
37
+ }
38
+
39
+ export type SetupScanOptions = {
40
+ readonly includeCustomProviders?: boolean
41
+ readonly onSourceStart?: (source: SetupSource) => void
42
+ }
43
+
44
+ export type SetupInventory = {
45
+ readonly harnesses: readonly { readonly id: string; readonly installed: boolean }[]
46
+ }
47
+
48
+ export type SetupPlans = { readonly [stage: string]: unknown }
49
+
50
+ export type SetupApplyStage = {
51
+ readonly id: SetupStageId
52
+ readonly plan: SetupStagePlan
53
+ }
54
+
55
+ export function canonicalAgentDir(env?: Readonly<Record<string, string | undefined>>, home?: string): string
56
+
57
+ export function scanSetup(runtime: SetupRuntime, agentDir: string, options?: SetupScanOptions): AsyncGenerator<SetupStageResult, void, undefined>
58
+
59
+ export function collectSetupPlans(stream: AsyncIterable<SetupStageResult> | Iterable<SetupStageResult>): Promise<{ inventory: SetupInventory; plans: SetupPlans }>
60
+
61
+ export type SetupAuthEntry = {
62
+ readonly provider: string
63
+ readonly key: string
64
+ }
65
+
66
+ /** Per-apply writer options: how credential additions and notice lines travel. */
67
+ export type SetupApplyOptions = {
68
+ /** Routes credential additions through a locked credential store (the engine's AuthStorage) instead of the CLI's direct writer. */
69
+ readonly writeAuth?: (target: string, additions: readonly SetupAuthEntry[]) => Promise<void> | void
70
+ /** Receives the apply's notice lines (the CLI prints them; an RPC caller collects them into its reply). */
71
+ readonly report?: (line: string) => void
72
+ }
73
+
74
+ /** Refused by `applySetupStages` when `staleGuard` finds a target that changed since planning. */
75
+ export class SetupStaleError extends Error {
76
+ readonly paths: readonly string[]
77
+ readonly applied: readonly SetupStageId[]
78
+ }
79
+
80
+ /** The target paths of the given stages whose content changed since their plans read them (content hash, never mtime alone). */
81
+ export function staleSetupTargets(plans: SetupPlans, stageIds: Iterable<SetupStageId>, written?: ReadonlySet<string>): string[]
82
+
83
+ /** True when any of the given stages would write into a credential store. */
84
+ export function authWritesPending(plans: SetupPlans, stageIds: Iterable<SetupStageId>): boolean
85
+
86
+ export function applySetupStages(
87
+ plans: SetupPlans,
88
+ confirm: (stage: SetupApplyStage) => boolean | Promise<boolean>,
89
+ report?: (line: string) => void,
90
+ options?: SetupApplyOptions & {
91
+ /** Re-checked right before each stage writes; a non-empty result refuses that stage with `SetupStaleError`. */
92
+ readonly staleGuard?: (stageId: SetupStageId, written: ReadonlySet<string>) => readonly string[] | undefined
93
+ },
94
+ ): Promise<Set<SetupStageId>>