@cmetech/otto 1.1.0 → 1.1.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.
@@ -1 +1 @@
1
- 9ee03dda8858f377
1
+ 48d229fb6702d2a2
@@ -4,6 +4,17 @@
4
4
  // To add or correct release notes, edit CHANGELOG.md and rebuild. Editing this
5
5
  // file directly will be clobbered on the next build.
6
6
  export const RELEASE_NOTES = [
7
+ {
8
+ version: '1.1.1',
9
+ date: '2026-05-29',
10
+ headline: 'Windows `otto update` no longer emits EPERM cleanup warnings or leaves orphan `.otto-*` staging dirs.',
11
+ added: [
12
+ '`update-status.json` written to `~/.otto/agent/` on every `otto update` — tracks `startedAt`, `completedAt`, `fromVersion`, `toVersion`, and `exitCode`. The next `otto update` prints "Last update: vX.Y.Z → vA.B.C at … (✓ success / ✗ exit N)" before checking the registry, so you can see whether a previous background update finished.',
13
+ ],
14
+ fixed: [
15
+ '`otto update` on Windows would log `EPERM: operation not permitted, unlink` warnings against `duckdb.dll`, `sharp.dll`, and `otto_engine.win32-x64.node` because the running otto.exe still held DLL handles. The install still succeeded but left orphan `.otto-RouGtD54`-style staging directories under `%APPDATA%\\npm\\node_modules\\@cmetech\\`. `otto update` now spawns a detached PowerShell bootstrap in a visible "OTTO Updater" console window that waits for the parent otto.exe to exit (max 30s), runs the install with locks released, sweeps the orphan staging dirs, and writes a completion status to `~/.otto/agent/update-status.json` that the next `otto update` surfaces. macOS / Linux behavior unchanged — POSIX inode semantics handle live-process replacement without the lock issue.',
16
+ ],
17
+ },
7
18
  {
8
19
  version: '1.1.0',
9
20
  date: '2026-05-29',
@@ -2,5 +2,24 @@ interface RunUpdateOptions {
2
2
  agentDir?: string;
3
3
  skillsDir?: string;
4
4
  }
5
+ /**
6
+ * Build the PowerShell bootstrap that runs the install in a detached, visible
7
+ * console window. Exported for tests. The script:
8
+ * 1. Writes an initial status file at `statusPath`.
9
+ * 2. Waits up to 30s for the parent otto.exe (parentPid) to exit so its
10
+ * DLL handles release.
11
+ * 3. Runs the install command (`npm install -g …` or `bun add -g …`).
12
+ * 4. Sweeps any orphan `.otto-*` staging dirs npm left behind.
13
+ * 5. Updates the status file with completion details.
14
+ * 6. Prints a clear "Done" message and waits for a keypress.
15
+ */
16
+ export declare function buildWindowsBootstrapScript(args: {
17
+ parentPid: number;
18
+ installCmd: string;
19
+ statusPath: string;
20
+ current: string;
21
+ latest: string;
22
+ startedAt: string;
23
+ }): string;
5
24
  export declare function runUpdate(options?: RunUpdateOptions): Promise<void>;
6
25
  export {};
@@ -1,15 +1,177 @@
1
- import { execSync } from 'node:child_process';
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
2
5
  import { agentDir as defaultAgentDir } from './app-paths.js';
3
6
  import { initResources } from './resource-loader.js';
4
7
  import { compareSemver, fetchLatestVersionFromRegistry, resolveInstallCommand } from './update-check.js';
5
8
  const NPM_PACKAGE = '@cmetech/otto';
9
+ function getStatusPath(agentDir) {
10
+ return join(agentDir, 'update-status.json');
11
+ }
12
+ function maybeShowPriorStatus(agentDir) {
13
+ const statusPath = getStatusPath(agentDir);
14
+ if (!existsSync(statusPath))
15
+ return;
16
+ try {
17
+ const status = JSON.parse(readFileSync(statusPath, 'utf-8'));
18
+ if (status.completedAt) {
19
+ const dim = '\x1b[2m';
20
+ const reset = '\x1b[0m';
21
+ const outcome = status.exitCode === 0 ? '✓ success' : `✗ exit ${status.exitCode}`;
22
+ process.stdout.write(`${dim}Last update: v${status.fromVersion} → v${status.toVersion} at ${status.completedAt} (${outcome})${reset}\n`);
23
+ }
24
+ }
25
+ catch {
26
+ // Ignore parse errors — file may be partial or corrupt.
27
+ }
28
+ }
29
+ /**
30
+ * Build the PowerShell bootstrap that runs the install in a detached, visible
31
+ * console window. Exported for tests. The script:
32
+ * 1. Writes an initial status file at `statusPath`.
33
+ * 2. Waits up to 30s for the parent otto.exe (parentPid) to exit so its
34
+ * DLL handles release.
35
+ * 3. Runs the install command (`npm install -g …` or `bun add -g …`).
36
+ * 4. Sweeps any orphan `.otto-*` staging dirs npm left behind.
37
+ * 5. Updates the status file with completion details.
38
+ * 6. Prints a clear "Done" message and waits for a keypress.
39
+ */
40
+ export function buildWindowsBootstrapScript(args) {
41
+ // PowerShell single-quoted strings are literal — backslashes and $ are not
42
+ // interpreted. Path values flow straight through.
43
+ const psQuote = (value) => `'${value.replace(/'/g, "''")}'`;
44
+ const status = psQuote(args.statusPath);
45
+ const current = psQuote(args.current);
46
+ const latest = psQuote(args.latest);
47
+ const startedAt = psQuote(args.startedAt);
48
+ const installCmd = args.installCmd; // run as-is — must be a valid PowerShell command line
49
+ return `# OTTO update bootstrap — auto-generated by otto update on Windows
50
+ $ErrorActionPreference = 'Continue'
51
+ $Host.UI.RawUI.WindowTitle = 'OTTO Updater'
52
+
53
+ Write-Host 'OTTO Updater — please do not close this window' -ForegroundColor Cyan
54
+ Write-Host '─────────────────────────────────────────────────'
55
+ Write-Host ''
56
+
57
+ # Write initial status
58
+ $statusPath = ${status}
59
+ $statusDir = Split-Path -Parent $statusPath
60
+ if ($statusDir -and -not (Test-Path $statusDir)) {
61
+ New-Item -ItemType Directory -Path $statusDir -Force | Out-Null
62
+ }
63
+ @{
64
+ startedAt = ${startedAt}
65
+ fromVersion = ${current}
66
+ toVersion = ${latest}
67
+ } | ConvertTo-Json | Set-Content -Path $statusPath -Encoding UTF8
68
+
69
+ # Wait for parent otto.exe (pid ${args.parentPid}) to exit, max 30s
70
+ Write-Host -NoNewline 'Waiting for OTTO to exit...'
71
+ $deadline = (Get-Date).AddSeconds(30)
72
+ while ((Get-Process -Id ${args.parentPid} -ErrorAction SilentlyContinue) -and ((Get-Date) -lt $deadline)) {
73
+ Start-Sleep -Milliseconds 300
74
+ }
75
+ if (Get-Process -Id ${args.parentPid} -ErrorAction SilentlyContinue) {
76
+ Write-Host ' timeout!' -ForegroundColor Yellow
77
+ Write-Host 'OTTO did not exit in 30s. To finish manually, run:' -ForegroundColor Yellow
78
+ Write-Host (' ' + ${psQuote(installCmd)})
79
+ Write-Host ''
80
+ Write-Host 'Press any key to close this window.'
81
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
82
+ exit 1
83
+ }
84
+ Write-Host ' done.'
85
+ Write-Host ''
86
+
87
+ # Run the install
88
+ Write-Host ('Running: ' + ${psQuote(installCmd)}) -ForegroundColor Cyan
89
+ Write-Host ''
90
+ ${installCmd}
91
+ $exitCode = $LASTEXITCODE
92
+
93
+ # Sweep orphan .otto-* staging dirs npm couldn't clean up
94
+ $stagingRoot = Join-Path $env:APPDATA 'npm\\node_modules\\@cmetech'
95
+ if (Test-Path $stagingRoot) {
96
+ Get-ChildItem -Path $stagingRoot -Directory -Filter '.otto-*' -ErrorAction SilentlyContinue |
97
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
98
+ }
99
+
100
+ # Write final status
101
+ @{
102
+ startedAt = ${startedAt}
103
+ completedAt = (Get-Date -Format 'o')
104
+ fromVersion = ${current}
105
+ toVersion = ${latest}
106
+ exitCode = $exitCode
107
+ } | ConvertTo-Json | Set-Content -Path $statusPath -Encoding UTF8
108
+
109
+ Write-Host ''
110
+ if ($exitCode -eq 0) {
111
+ Write-Host ('Done. OTTO v' + ${latest} + ' installed.') -ForegroundColor Green
112
+ Write-Host 'Open any terminal and run otto to use the new version.'
113
+ } else {
114
+ Write-Host ('Install failed with exit code ' + $exitCode) -ForegroundColor Red
115
+ Write-Host ('Try manually: ' + ${psQuote(installCmd)})
116
+ }
117
+ Write-Host ''
118
+ Write-Host 'Press any key to close this window.'
119
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
120
+ exit $exitCode
121
+ `;
122
+ }
123
+ function runWindowsDetachedUpdate(current, latest, installCmd, agentDir) {
124
+ const tempDir = mkdtempSync(join(tmpdir(), 'otto-update-'));
125
+ const scriptPath = join(tempDir, 'update.ps1');
126
+ const statusPath = getStatusPath(agentDir);
127
+ const startedAt = new Date().toISOString();
128
+ if (!existsSync(agentDir)) {
129
+ mkdirSync(agentDir, { recursive: true });
130
+ }
131
+ const script = buildWindowsBootstrapScript({
132
+ parentPid: process.pid,
133
+ installCmd,
134
+ statusPath,
135
+ current,
136
+ latest,
137
+ startedAt,
138
+ });
139
+ writeFileSync(scriptPath, script, 'utf-8');
140
+ const bold = '\x1b[1m';
141
+ const dim = '\x1b[2m';
142
+ const reset = '\x1b[0m';
143
+ process.stdout.write(`${dim}Updating:${reset} v${current} → ${bold}v${latest}${reset}\n\n`);
144
+ process.stdout.write('Update is running in a new window — Windows cannot replace files while OTTO is open.\n');
145
+ process.stdout.write(`When the update window shows ${bold}"Done."${reset} run \`otto\` in any terminal to use v${latest}.\n\n`);
146
+ // `cmd.exe /c start "Title" powershell.exe -File <script>`
147
+ // Node quotes args that contain spaces, so `start OTTO Updater` becomes
148
+ // `start "OTTO Updater"`, which CMD's start builtin treats as the window
149
+ // title (longstanding CMD quirk).
150
+ const child = spawn('cmd.exe', [
151
+ '/c',
152
+ 'start',
153
+ 'OTTO Updater',
154
+ 'powershell.exe',
155
+ '-NoProfile',
156
+ '-ExecutionPolicy',
157
+ 'Bypass',
158
+ '-File',
159
+ scriptPath,
160
+ ], { detached: true, stdio: 'ignore', windowsHide: false });
161
+ child.unref();
162
+ // We MUST exit so the bootstrap's pid-wait loop unblocks and DLL handles
163
+ // release. Anything after this point is unreachable.
164
+ process.exit(0);
165
+ }
6
166
  export async function runUpdate(options = {}) {
7
167
  const current = process.env.OTTO_VERSION || '0.0.0';
168
+ const agentDir = options.agentDir ?? defaultAgentDir;
8
169
  const bold = '\x1b[1m';
9
170
  const dim = '\x1b[2m';
10
171
  const green = '\x1b[32m';
11
172
  const yellow = '\x1b[33m';
12
173
  const reset = '\x1b[0m';
174
+ maybeShowPriorStatus(agentDir);
13
175
  process.stdout.write(`${dim}Current version:${reset} v${current}\n`);
14
176
  process.stdout.write(`${dim}Checking npm registry...${reset}\n`);
15
177
  const latest = await fetchLatestVersionFromRegistry();
@@ -20,15 +182,24 @@ export async function runUpdate(options = {}) {
20
182
  process.stdout.write(`${dim}Latest version:${reset} v${latest}\n`);
21
183
  if (compareSemver(latest, current) <= 0) {
22
184
  process.stdout.write(`${green}Already up to date.${reset}\n`);
23
- initResources(options.agentDir ?? defaultAgentDir, options.skillsDir);
185
+ initResources(agentDir, options.skillsDir);
24
186
  return;
25
187
  }
26
- process.stdout.write(`${dim}Updating:${reset} v${current} → ${bold}v${latest}${reset}\n`);
27
188
  const installCmd = resolveInstallCommand(`${NPM_PACKAGE}@latest`);
189
+ // On Windows, run install in a detached, visible window. The current
190
+ // process holds locks on duckdb.dll, sharp.dll, otto_engine.win32-x64.node,
191
+ // and others — npm install would log EPERM warnings and leave orphan
192
+ // .otto-* staging dirs (issue surfaced by a v1.0.7 → v1.1.0 update on
193
+ // Windows). The bootstrap waits for this process to exit, runs npm install
194
+ // cleanly, sweeps the orphans, and reports completion via a status file
195
+ // that the next `otto update` invocation surfaces.
196
+ if (process.platform === 'win32') {
197
+ runWindowsDetachedUpdate(current, latest, installCmd, agentDir);
198
+ // unreachable
199
+ }
200
+ process.stdout.write(`${dim}Updating:${reset} v${current} → ${bold}v${latest}${reset}\n`);
28
201
  try {
29
- execSync(installCmd, {
30
- stdio: 'inherit',
31
- });
202
+ execSync(installCmd, { stdio: 'inherit' });
32
203
  process.stdout.write(`\n${green}${bold}Updated to v${latest}${reset}\n`);
33
204
  }
34
205
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmetech/otto",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Terminal-based developer chat assistant. Permanent hard fork of gsd-pi with LangFlow flow triggers, a flow builder, and optional gateway routing for compliance environments.",
5
5
  "keywords": [
6
6
  "ai",
@@ -183,11 +183,11 @@
183
183
  "@anthropic-ai/claude-agent-sdk": "0.2.83",
184
184
  "fsevents": "~2.3.3",
185
185
  "koffi": "^2.9.0",
186
- "@cmetech/otto-engine-darwin-arm64": "1.1.0",
187
- "@cmetech/otto-engine-darwin-x64": "1.1.0",
188
- "@cmetech/otto-engine-linux-arm64-gnu": "1.1.0",
189
- "@cmetech/otto-engine-linux-x64-gnu": "1.1.0",
190
- "@cmetech/otto-engine-win32-x64-msvc": "1.1.0"
186
+ "@cmetech/otto-engine-darwin-arm64": "1.1.1",
187
+ "@cmetech/otto-engine-darwin-x64": "1.1.1",
188
+ "@cmetech/otto-engine-linux-arm64-gnu": "1.1.1",
189
+ "@cmetech/otto-engine-linux-x64-gnu": "1.1.1",
190
+ "@cmetech/otto-engine-win32-x64-msvc": "1.1.1"
191
191
  },
192
192
  "overrides": {
193
193
  "gaxios": "7.1.4",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto-build/contracts",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shared public contracts for OTTO workspace boundaries",
5
5
  "license": "MIT",
6
6
  "otto": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto-build/daemon",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "OTTO daemon — background process for project monitoring and Discord integration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@anthropic-ai/sdk": "^0.52.0",
32
- "@otto-build/contracts": "^1.1.0",
33
- "@otto-build/rpc-client": "^1.1.0",
32
+ "@otto-build/contracts": "^1.1.1",
33
+ "@otto-build/rpc-client": "^1.1.1",
34
34
  "discord.js": "^14.25.1",
35
35
  "yaml": "^2.8.0",
36
36
  "zod": "^3.24.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto-build/mcp-server",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "MCP server exposing OTTO orchestration tools for compatible clients",
5
5
  "license": "MIT",
6
6
  "otto": {
@@ -34,8 +34,8 @@
34
34
  "test": "npm run build:test && node --test dist/mcp-server.test.js dist/remote-questions.test.js"
35
35
  },
36
36
  "dependencies": {
37
- "@otto-build/contracts": "^1.1.0",
38
- "@otto-build/rpc-client": "^1.1.0",
37
+ "@otto-build/contracts": "^1.1.1",
38
+ "@otto-build/rpc-client": "^1.1.1",
39
39
  "@modelcontextprotocol/sdk": "^1.27.1",
40
40
  "zod": "^4.0.0"
41
41
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto/native",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Native Rust bindings for OTTO — high-performance native modules via N-API",
5
5
  "type": "commonjs",
6
6
  "otto": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto/pi-agent-core",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "General-purpose agent core (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "otto": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto/pi-ai",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Unified LLM API (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "otto": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto/pi-coding-agent",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Coding agent CLI (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "otto": {
@@ -28,7 +28,7 @@
28
28
  "copy-assets": "node scripts/copy-assets.cjs"
29
29
  },
30
30
  "dependencies": {
31
- "@otto-build/contracts": "^1.1.0",
31
+ "@otto-build/contracts": "^1.1.1",
32
32
  "@mariozechner/jiti": "^2.6.2",
33
33
  "@silvia-odwyer/photon-node": "^0.3.4",
34
34
  "chalk": "^5.5.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto/pi-tui",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Terminal User Interface library (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "otto": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otto-build/rpc-client",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Standalone RPC client SDK for OTTO — zero internal dependencies",
5
5
  "license": "MIT",
6
6
  "otto": {
@@ -34,7 +34,7 @@
34
34
  "test": "node --test dist/rpc-client.test.js"
35
35
  },
36
36
  "dependencies": {
37
- "@otto-build/contracts": "^1.1.0"
37
+ "@otto-build/contracts": "^1.1.1"
38
38
  },
39
39
  "engines": {
40
40
  "node": ">=22.0.0"
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loop24/client",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "piConfig": {
5
5
  "_comment": "AUTO-SYNCED from root package.json by scripts/sync-piconfig.mjs (runs on prebuild). Do not edit this block directly — edit root package.json and re-run `npm run build` or `npm run sync-piconfig`.",
6
6
  "name": "otto",
@@ -15,6 +15,17 @@ export interface ReleaseNote {
15
15
  }
16
16
 
17
17
  export const RELEASE_NOTES: ReleaseNote[] = [
18
+ {
19
+ version: '1.1.1',
20
+ date: '2026-05-29',
21
+ headline: 'Windows `otto update` no longer emits EPERM cleanup warnings or leaves orphan `.otto-*` staging dirs.',
22
+ added: [
23
+ '`update-status.json` written to `~/.otto/agent/` on every `otto update` — tracks `startedAt`, `completedAt`, `fromVersion`, `toVersion`, and `exitCode`. The next `otto update` prints "Last update: vX.Y.Z → vA.B.C at … (✓ success / ✗ exit N)" before checking the registry, so you can see whether a previous background update finished.',
24
+ ],
25
+ fixed: [
26
+ '`otto update` on Windows would log `EPERM: operation not permitted, unlink` warnings against `duckdb.dll`, `sharp.dll`, and `otto_engine.win32-x64.node` because the running otto.exe still held DLL handles. The install still succeeded but left orphan `.otto-RouGtD54`-style staging directories under `%APPDATA%\\npm\\node_modules\\@cmetech\\`. `otto update` now spawns a detached PowerShell bootstrap in a visible "OTTO Updater" console window that waits for the parent otto.exe to exit (max 30s), runs the install with locks released, sweeps the orphan staging dirs, and writes a completion status to `~/.otto/agent/update-status.json` that the next `otto update` surfaces. macOS / Linux behavior unchanged — POSIX inode semantics handle live-process replacement without the lock issue.',
27
+ ],
28
+ },
18
29
  {
19
30
  version: '1.1.0',
20
31
  date: '2026-05-29',