thoth-agents 0.3.7 → 0.3.9

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.
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "thoth-agents",
11
11
  "description": "Adaptive root orchestration, six specialist subagents, and a runtime-autonomous Spec Kit-compatible SDD bundle.",
12
- "version": "0.3.7",
12
+ "version": "0.3.9",
13
13
  "author": {
14
14
  "name": "thoth-agents maintainers"
15
15
  },
package/README.md CHANGED
@@ -56,6 +56,11 @@ npx thoth-agents@latest install --agent=opencode --dry-run
56
56
  npx thoth-agents@latest install --agent=opencode
57
57
  ```
58
58
 
59
+ Here, `@latest` selects which CLI release runs. That CLI writes the exact
60
+ executing release to OpenCode configuration, for example
61
+ `plugin: ["thoth-agents@0.4.8"]`; it never writes a `latest` plugin entry or
62
+ falls back to one when package identity cannot be verified.
63
+
59
64
  Restart OpenCode and initialize the current repository:
60
65
 
61
66
  ```text
@@ -393,9 +398,31 @@ npx thoth-agents@latest list
393
398
  npx thoth-agents@latest install --agent=opencode
394
399
  npx thoth-agents@latest install --agent=codex
395
400
  npx thoth-agents@latest install --agent=claude
401
+ npx thoth-agents@latest update --harness=opencode
402
+ npx thoth-agents@latest update --harness=opencode --apply
396
403
  npx thoth-agents@latest model --harness=codex --role=deep --model=gpt-5.6-sol
397
404
  ```
398
405
 
406
+ `update` previews by default. Applying it performs the same complete refresh as
407
+ installation for the selected harness: OpenCode refreshes its exact plugin pin,
408
+ managed configuration, owned skills, external skills, and provider setup;
409
+ Codex runs native plugin setup before its global agent pack, external skills,
410
+ and provider setup; Claude refreshes its native plugin before external skills
411
+ and provider setup. Any required failure makes the operation fail rather than
412
+ claiming a complete update.
413
+
414
+ After every required step succeeds, the CLI records that harness's executing
415
+ version in
416
+ `${XDG_CONFIG_HOME:-~/.config}/thoth-agents/install-state.json`. `status` shows
417
+ the executing CLI version and this last complete CLI-managed version. The three
418
+ harness records are independent: native Codex or Claude marketplace updates do
419
+ not advance them and do not prove that CLI-managed agents, skills,
420
+ configuration, or provider setup are current.
421
+
422
+ OpenCode's runtime release check is notification-only. It does not rewrite the
423
+ exact plugin pin, invalidate package state, or run a package manager. Follow the
424
+ notification by rerunning the latest CLI installer or applying CLI Update.
425
+
399
426
  In the interactive `Configure models` flow, `Apply` is always available. When
400
427
  no role is dirty, it reapplies every currently displayed role value; after an
401
428
  edit, preview and apply remain limited to the dirty roles. In OpenCode, both
@@ -5,20 +5,135 @@ import * as path from "path";
5
5
  // src/cli/config-io.ts
6
6
  import {
7
7
  copyFileSync,
8
- existsSync as existsSync2,
9
- readFileSync,
8
+ existsSync as existsSync3,
9
+ readFileSync as readFileSync2,
10
10
  renameSync,
11
11
  statSync,
12
12
  writeFileSync
13
13
  } from "fs";
14
14
 
15
+ // src/cli/package-version.ts
16
+ import { readFileSync } from "fs";
17
+ import { dirname as dirname2, join as join2 } from "path";
18
+ import { fileURLToPath } from "url";
19
+
20
+ // src/cli/package-root.ts
21
+ import { existsSync } from "fs";
22
+ import { dirname, join, parse } from "path";
23
+ function findPackageRoot(startDir) {
24
+ let currentDir = startDir;
25
+ const filesystemRoot = parse(startDir).root;
26
+ while (true) {
27
+ if (existsSync(join(currentDir, "package.json"))) {
28
+ return currentDir;
29
+ }
30
+ if (currentDir === filesystemRoot) {
31
+ return null;
32
+ }
33
+ const parentDir = dirname(currentDir);
34
+ if (parentDir === currentDir) {
35
+ return null;
36
+ }
37
+ currentDir = parentDir;
38
+ }
39
+ }
40
+
41
+ // src/cli/package-version.ts
42
+ var EXPECTED_PACKAGE_NAME = "thoth-agents";
43
+ var SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
44
+ function isValidPackageVersion(version) {
45
+ return typeof version === "string" && SEMVER_PATTERN.test(version);
46
+ }
47
+ function resolveExecutingPackageVersion(options = {}) {
48
+ const moduleUrl = options.moduleUrl ?? import.meta.url;
49
+ const packageRoot = findPackageRoot(dirname2(fileURLToPath(moduleUrl)));
50
+ if (!packageRoot) {
51
+ return {
52
+ ok: false,
53
+ error: {
54
+ code: "package-root-not-found",
55
+ message: "Could not locate the executing thoth-agents package root."
56
+ }
57
+ };
58
+ }
59
+ const packageJsonPath = join2(packageRoot, "package.json");
60
+ let serializedMetadata;
61
+ try {
62
+ serializedMetadata = readFileSync(packageJsonPath, "utf8");
63
+ } catch (error) {
64
+ return {
65
+ ok: false,
66
+ error: {
67
+ code: "package-metadata-unreadable",
68
+ message: `Could not read executing package metadata: ${error}`,
69
+ packageJsonPath
70
+ }
71
+ };
72
+ }
73
+ let metadata;
74
+ try {
75
+ metadata = JSON.parse(serializedMetadata);
76
+ } catch (error) {
77
+ return {
78
+ ok: false,
79
+ error: {
80
+ code: "package-metadata-malformed",
81
+ message: `Could not parse executing package metadata: ${error}`,
82
+ packageJsonPath
83
+ }
84
+ };
85
+ }
86
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
87
+ return {
88
+ ok: false,
89
+ error: {
90
+ code: "package-metadata-malformed",
91
+ message: "Executing package metadata must be a JSON object.",
92
+ packageJsonPath
93
+ }
94
+ };
95
+ }
96
+ const { name, version } = metadata;
97
+ if (typeof name !== "string" || name.length === 0) {
98
+ return {
99
+ ok: false,
100
+ error: {
101
+ code: "package-metadata-malformed",
102
+ message: "Executing package metadata has no valid package name.",
103
+ packageJsonPath
104
+ }
105
+ };
106
+ }
107
+ if (name !== EXPECTED_PACKAGE_NAME) {
108
+ return {
109
+ ok: false,
110
+ error: {
111
+ code: "package-name-mismatch",
112
+ message: `Expected package ${EXPECTED_PACKAGE_NAME}, received ${name}.`,
113
+ packageJsonPath
114
+ }
115
+ };
116
+ }
117
+ if (!isValidPackageVersion(version)) {
118
+ return {
119
+ ok: false,
120
+ error: {
121
+ code: "package-version-invalid",
122
+ message: "Executing package metadata has no valid semantic version.",
123
+ packageJsonPath
124
+ }
125
+ };
126
+ }
127
+ return { ok: true, version, packageRoot };
128
+ }
129
+
15
130
  // src/cli/paths.ts
16
- import { existsSync, mkdirSync } from "fs";
131
+ import { existsSync as existsSync2, mkdirSync } from "fs";
17
132
  import { homedir } from "os";
18
- import { dirname, join } from "path";
133
+ import { dirname as dirname3, join as join3 } from "path";
19
134
  function getDefaultOpenCodeConfigDir() {
20
- const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
21
- return join(userConfigDir, "opencode");
135
+ const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join3(homedir(), ".config");
136
+ return join3(userConfigDir, "opencode");
22
137
  }
23
138
  function getCustomOpenCodeConfigDir() {
24
139
  const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
@@ -33,7 +148,7 @@ function getConfigDir() {
33
148
  }
34
149
  function getOpenCodeConfigPaths() {
35
150
  const configDir = getDefaultOpenCodeConfigDir();
36
- return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
151
+ return [join3(configDir, "opencode.json"), join3(configDir, "opencode.jsonc")];
37
152
  }
38
153
  function getConfigJson() {
39
154
  return getOpenCodeConfigPaths()[0];
@@ -42,37 +157,37 @@ function getConfigJsonc() {
42
157
  return getOpenCodeConfigPaths()[1];
43
158
  }
44
159
  function getLiteConfig() {
45
- return join(getConfigDir(), "thoth-agents.json");
160
+ return join3(getConfigDir(), "thoth-agents.json");
46
161
  }
47
162
  function getLiteConfigJsonc() {
48
- return join(getConfigDir(), "thoth-agents.jsonc");
163
+ return join3(getConfigDir(), "thoth-agents.jsonc");
49
164
  }
50
165
  function getOpenCodeManagedModelStatePath() {
51
- return join(getConfigDir(), ".thoth-agents-managed-models.json");
166
+ return join3(getConfigDir(), ".thoth-agents-managed-models.json");
52
167
  }
53
168
  function getExistingLiteConfigPath() {
54
169
  const jsonPath = getLiteConfig();
55
- if (existsSync(jsonPath)) return jsonPath;
170
+ if (existsSync2(jsonPath)) return jsonPath;
56
171
  const jsoncPath = getLiteConfigJsonc();
57
- if (existsSync(jsoncPath)) return jsoncPath;
172
+ if (existsSync2(jsoncPath)) return jsoncPath;
58
173
  return jsonPath;
59
174
  }
60
175
  function getExistingConfigPath() {
61
176
  const jsonPath = getConfigJson();
62
- if (existsSync(jsonPath)) return jsonPath;
177
+ if (existsSync2(jsonPath)) return jsonPath;
63
178
  const jsoncPath = getConfigJsonc();
64
- if (existsSync(jsoncPath)) return jsoncPath;
179
+ if (existsSync2(jsoncPath)) return jsoncPath;
65
180
  return jsonPath;
66
181
  }
67
182
  function ensureConfigDir() {
68
183
  const configDir = getConfigDir();
69
- if (!existsSync(configDir)) {
184
+ if (!existsSync2(configDir)) {
70
185
  mkdirSync(configDir, { recursive: true });
71
186
  }
72
187
  }
73
188
  function ensureOpenCodeConfigDir() {
74
- const configDir = dirname(getConfigJson());
75
- if (!existsSync(configDir)) {
189
+ const configDir = dirname3(getConfigJson());
190
+ if (!existsSync2(configDir)) {
76
191
  mkdirSync(configDir, { recursive: true });
77
192
  }
78
193
  }
@@ -349,10 +464,10 @@ function stripJsonComments(json) {
349
464
  }
350
465
  function parseConfigFile(path2) {
351
466
  try {
352
- if (!existsSync2(path2)) return { config: null };
467
+ if (!existsSync3(path2)) return { config: null };
353
468
  const stat = statSync(path2);
354
469
  if (stat.size === 0) return { config: null };
355
- const content = readFileSync(path2, "utf-8");
470
+ const content = readFileSync2(path2, "utf-8");
356
471
  if (content.trim().length === 0) return { config: null };
357
472
  return { config: JSON.parse(stripJsonComments(content)) };
358
473
  } catch (err) {
@@ -378,7 +493,7 @@ function writeConfig(configPath, config) {
378
493
  const bakPath = `${configPath}.bak`;
379
494
  const content = `${JSON.stringify(config, null, 2)}
380
495
  `;
381
- if (existsSync2(configPath)) {
496
+ if (existsSync3(configPath)) {
382
497
  copyFileSync(configPath, bakPath);
383
498
  }
384
499
  writeFileSync(tmpPath, content);
@@ -391,7 +506,7 @@ function mergeOpenCodeMainConfig(config, update) {
391
506
  ...plugins.filter(
392
507
  (plugin) => plugin !== PACKAGE_NAME && !plugin.startsWith(`${PACKAGE_NAME}@`)
393
508
  ),
394
- `${PACKAGE_NAME}@latest`
509
+ `${PACKAGE_NAME}@${update.pluginVersion}`
395
510
  ];
396
511
  }
397
512
  if (update.disableDefaults) {
@@ -406,6 +521,13 @@ function mergeOpenCodeMainConfig(config, update) {
406
521
  }
407
522
  function updateOpenCodeMainConfig(update) {
408
523
  const configPath = getExistingConfigPath();
524
+ if (update.ensurePlugin && !isValidPackageVersion(update.pluginVersion)) {
525
+ return {
526
+ success: false,
527
+ configPath,
528
+ error: "An approved plugin version is required."
529
+ };
530
+ }
409
531
  try {
410
532
  ensureOpenCodeConfigDir();
411
533
  } catch (err) {
@@ -446,7 +568,7 @@ function writeLiteConfig(installConfig, targetPath) {
446
568
  const bakPath = `${configPath}.bak`;
447
569
  const content = `${JSON.stringify(config, null, 2)}
448
570
  `;
449
- if (existsSync2(configPath)) {
571
+ if (existsSync3(configPath)) {
450
572
  copyFileSync(configPath, bakPath);
451
573
  }
452
574
  writeFileSync(tmpPath, content);
@@ -1748,6 +1870,9 @@ export {
1748
1870
  getDefaultOpenCodeModel,
1749
1871
  getDefaultOpenCodeVariant,
1750
1872
  POLL_INTERVAL_BACKGROUND_MS,
1873
+ findPackageRoot,
1874
+ isValidPackageVersion,
1875
+ resolveExecutingPackageVersion,
1751
1876
  getOpenCodeConfigPaths,
1752
1877
  getOpenCodeManagedModelStatePath,
1753
1878
  getExistingLiteConfigPath,
@@ -14,7 +14,7 @@ import {
14
14
  getStepBudgetPromptSection,
15
15
  loadAgentPrompt,
16
16
  renderRolePrompt
17
- } from "./chunk-F2K4CS5J.js";
17
+ } from "./chunk-2TLT6WWY.js";
18
18
 
19
19
  // src/agents/deep.ts
20
20
  var DEEP_PROMPT = renderRolePrompt(