@circuitwall/jarela 1.4.0 → 1.4.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.
Files changed (97) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  15. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +10 -1
  23. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
  24. package/.next/standalone/.next/server/app/api/v1/dashboard/currency/route.js +10 -5
  25. package/.next/standalone/.next/server/app/api/v1/dashboard/currency/route.js.map +1 -1
  26. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +9 -1
  27. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js.map +1 -1
  28. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +33 -8
  29. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js.map +1 -1
  30. package/.next/standalone/.next/server/app/page.js +63 -202
  31. package/.next/standalone/.next/server/app/page.js.map +1 -1
  32. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/setup/page.js +1 -1
  35. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/chunks/1718.js +159 -0
  38. package/.next/standalone/.next/server/chunks/1718.js.map +1 -0
  39. package/.next/standalone/.next/server/chunks/2082.js +6 -3
  40. package/.next/standalone/.next/server/chunks/2082.js.map +1 -1
  41. package/.next/standalone/.next/server/chunks/210.js +28 -0
  42. package/.next/standalone/.next/server/chunks/210.js.map +1 -1
  43. package/.next/standalone/.next/server/chunks/423.js +6 -3
  44. package/.next/standalone/.next/server/chunks/423.js.map +1 -1
  45. package/.next/standalone/.next/server/chunks/4631.js +37 -5
  46. package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
  47. package/.next/standalone/.next/server/chunks/8167.js +255 -204
  48. package/.next/standalone/.next/server/chunks/8167.js.map +1 -1
  49. package/.next/standalone/.next/server/chunks/8866.js +38 -5
  50. package/.next/standalone/.next/server/chunks/8866.js.map +1 -1
  51. package/.next/standalone/.next/server/chunks/9032.js +8 -0
  52. package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
  53. package/.next/standalone/.next/server/chunks/{7883.js → 9557.js} +15 -3
  54. package/.next/standalone/.next/server/chunks/9557.js.map +1 -0
  55. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  56. package/.next/standalone/.next/server/middleware.js +6 -3
  57. package/.next/standalone/.next/server/pages/404.html +2 -2
  58. package/.next/standalone/.next/server/pages/500.html +1 -1
  59. package/.next/standalone/.next/server/proxy.js.map +1 -1
  60. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  61. package/.next/standalone/.next/static/chunks/{2351-68d8987bbe17ba2d.js → 2351-1ab119fb3b48f4c9.js} +258 -205
  62. package/.next/standalone/.next/static/chunks/2351-1ab119fb3b48f4c9.js.map +1 -0
  63. package/.next/standalone/.next/static/chunks/{9209-0d46118e502f8bf5.js → 4097-64691f9110cf167c.js} +14 -2
  64. package/.next/standalone/.next/static/chunks/4097-64691f9110cf167c.js.map +1 -0
  65. package/.next/standalone/.next/static/chunks/app/{page-74846c864241b96d.js → page-145150e0468544e7.js} +64 -203
  66. package/.next/standalone/.next/static/chunks/app/page-145150e0468544e7.js.map +1 -0
  67. package/.next/standalone/.next/static/chunks/app/setup/{page-9a465b5fa755b3c3.js → page-a1463a9ace439ff7.js} +2 -2
  68. package/.next/standalone/.next/static/chunks/app/setup/{page-9a465b5fa755b3c3.js.map → page-a1463a9ace439ff7.js.map} +1 -1
  69. package/.next/standalone/.next/static/chunks/{webpack-ff5627013a5e3842.js → webpack-f4ac5c5f92cfd1c1.js} +13 -1
  70. package/.next/standalone/.next/static/chunks/webpack-f4ac5c5f92cfd1c1.js.map +1 -0
  71. package/.next/standalone/package.json +1 -1
  72. package/CHANGELOG.md +60 -0
  73. package/README.md +1 -1
  74. package/api/client.ts +10 -9
  75. package/app/api/v1/dashboard/currency/route.ts +7 -2
  76. package/app/api/v1/providers/[provider]/probe/route.ts +12 -1
  77. package/app/api/v1/threads/[thread_id]/run/route.ts +22 -8
  78. package/components/layout/AppShell.tsx +53 -17
  79. package/components/setup/PinKeypad.tsx +238 -0
  80. package/components/setup/ScreenLock.tsx +8 -173
  81. package/components/setup/UnlockScreen.tsx +25 -192
  82. package/lib/documents/remote/github.ts +16 -2
  83. package/lib/documents/remote/mail.ts +11 -2
  84. package/lib/lifecycle/shutdown.ts +9 -0
  85. package/lib/providers/github-copilot-auth.ts +2 -0
  86. package/lib/providers/github-copilot.ts +1 -0
  87. package/lib/tools/async-results.ts +11 -0
  88. package/package.json +1 -1
  89. package/scripts/install-to-system.ps1 +2 -2
  90. package/scripts/installed-launcher.ps1 +81 -17
  91. package/.next/standalone/.next/server/chunks/7883.js.map +0 -1
  92. package/.next/standalone/.next/static/chunks/2351-68d8987bbe17ba2d.js.map +0 -1
  93. package/.next/standalone/.next/static/chunks/9209-0d46118e502f8bf5.js.map +0 -1
  94. package/.next/standalone/.next/static/chunks/app/page-74846c864241b96d.js.map +0 -1
  95. package/.next/standalone/.next/static/chunks/webpack-ff5627013a5e3842.js.map +0 -1
  96. /package/.next/standalone/.next/static/{AV5AO0yTRABo-NgwxhDe7 → WQdcnm9NyqpeNc0Z8_woo}/_buildManifest.js +0 -0
  97. /package/.next/standalone/.next/static/{AV5AO0yTRABo-NgwxhDe7 → WQdcnm9NyqpeNc0Z8_woo}/_ssgManifest.js +0 -0
@@ -182,7 +182,10 @@ async function indexGmailMail(row: DocumentSourceRow): Promise<RemoteIndexStats>
182
182
  const batch = list.messages ?? [];
183
183
  if (batch.length === 0) break;
184
184
 
185
- const results = await Promise.all(batch.slice(0, limit).map(async (entry) => {
185
+ // Use allSettled so a single 404/transient 5xx on one message
186
+ // doesn't bin the whole page (and stall the cursor on the next
187
+ // run). Failures get counted into stats.errors instead.
188
+ const settled = await Promise.allSettled(batch.slice(0, limit).map(async (entry) => {
186
189
  const msg = await googleFetch(
187
190
  auth,
188
191
  "Gmail",
@@ -212,10 +215,16 @@ async function indexGmailMail(row: DocumentSourceRow): Promise<RemoteIndexStats>
212
215
  });
213
216
  }));
214
217
 
215
- for (const [index, item] of results.entries()) {
218
+ for (const [index, outcome] of settled.entries()) {
216
219
  const id = batch[index]?.id ?? "";
217
220
  if (id) keep.add(`gmail://${id}`);
218
221
  stats.scanned++;
222
+ if (outcome.status === "rejected") {
223
+ stats.errors++;
224
+ console.warn(`[mail-indexer] message ${id} failed:`, outcome.reason);
225
+ continue;
226
+ }
227
+ const item = outcome.value;
219
228
  stats.added += item.status === "added" ? 1 : 0;
220
229
  stats.updated += item.status === "updated" ? 1 : 0;
221
230
  stats.unchanged += item.status === "unchanged" ? 1 : 0;
@@ -131,6 +131,15 @@ async function runShutdown(): Promise<void> {
131
131
  console.error("[jarela] waiting for runs failed:", err);
132
132
  }
133
133
 
134
+ // 4b. Stop the async-tool-results sweeper. Holds a setInterval that
135
+ // would otherwise keep the event loop alive past closeDb().
136
+ try {
137
+ const { stopAsyncResults } = await import("@/lib/tools/async-results");
138
+ stopAsyncResults();
139
+ } catch (err) {
140
+ console.error("[jarela] stopping async-results sweeper failed:", err);
141
+ }
142
+
134
143
  // 5. Close the DB. WAL is checkpointed so the next boot is fast and we
135
144
  // leave no stale -shm/-wal sidecars on disk.
136
145
  try {
@@ -44,6 +44,7 @@ export async function startDeviceFlow(): Promise<DeviceCodeResponse> {
44
44
  "User-Agent": "Jarela/1.0",
45
45
  },
46
46
  body: JSON.stringify({ client_id: COPILOT_CLIENT_ID, scope: SCOPE }),
47
+ signal: AbortSignal.timeout(30_000),
47
48
  });
48
49
  if (!res.ok) {
49
50
  const body = await res.text().catch(() => res.statusText);
@@ -69,6 +70,7 @@ export async function pollDeviceFlow(device_code: string): Promise<PollResult> {
69
70
  device_code,
70
71
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
71
72
  }),
73
+ signal: AbortSignal.timeout(30_000),
72
74
  });
73
75
  if (!res.ok) {
74
76
  return { status: "error", error: `HTTP ${res.status}` };
@@ -53,6 +53,7 @@ async function getCopilotToken(pat: string): Promise<string> {
53
53
  Authorization: `token ${pat}`,
54
54
  "User-Agent": "Jarela/1.0",
55
55
  },
56
+ signal: AbortSignal.timeout(30_000),
56
57
  });
57
58
 
58
59
  if (!res.ok) {
@@ -57,6 +57,17 @@ function ensureSweeper(): void {
57
57
  (sweeper as unknown as { unref?: () => void }).unref?.();
58
58
  }
59
59
 
60
+ /**
61
+ * Tear down the sweeper. Called from the shutdown drain so the timer
62
+ * isn't keeping the event loop alive past close-time. Idempotent.
63
+ */
64
+ export function stopAsyncResults(): void {
65
+ if (sweeper) {
66
+ clearInterval(sweeper);
67
+ sweeper = null;
68
+ }
69
+ }
70
+
60
71
  /**
61
72
  * Carve out a slot for a new async tool call and return its key.
62
73
  * The key is opaque and URL-safe — the agent treats it as a token.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circuitwall/jarela",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Jarela — local chat interface for LangGraph agents (multi-provider, single-process, SQLite-backed).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Andrew Ge Wu",
@@ -245,8 +245,8 @@ $settings = New-ScheduledTaskSettingsSet `
245
245
  -StartWhenAvailable `
246
246
  -Hidden `
247
247
  -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
248
- -RestartInterval (New-TimeSpan -Minutes 1) `
249
- -RestartCount 999 `
248
+ -RestartInterval (New-TimeSpan -Minutes 5) `
249
+ -RestartCount 3 `
250
250
  -MultipleInstances IgnoreNew
251
251
 
252
252
  $task = New-ScheduledTask `
@@ -120,41 +120,105 @@ $env:PORT = "$Port"
120
120
  $env:HOSTNAME = '127.0.0.1'
121
121
  $env:NODE_ENV = 'production'
122
122
 
123
- # Separate files for the node child's stdout/stderr. The launcher writes its
124
- # own supervisor messages to $LogFile; Start-Process cannot share that handle
125
- # with a child or it fails silently on Windows ("file is in use").
123
+ # Separate files for the node child's stdout/stderr. We append (>>) instead
124
+ # of truncating each spawn so a crash-and-respawn cycle keeps the prior
125
+ # stderr lines around for diagnosis.
126
126
  $ServerOut = Join-Path $LogDir 'server.out.log'
127
127
  $ServerErr = Join-Path $LogDir 'server.err.log'
128
128
 
129
+ # Spawn node and reliably capture stdout/stderr to files. Background:
130
+ # `Start-Process -NoNewWindow -RedirectStandardOutput X -RedirectStandardError Y`
131
+ # silently drops both streams when the parent chain is wscript -> powershell
132
+ # (i.e. when there is no console attached to the PowerShell host). Under that
133
+ # setup, server.out.log / server.err.log stay 0 bytes forever, hiding crash
134
+ # reasons. Switching to a raw `[System.Diagnostics.Process]` with async
135
+ # `BeginOutputReadLine`/`BeginErrorReadLine` event handlers works regardless
136
+ # of console attachment because the read loop runs on a .NET worker thread.
137
+ function Start-NodeChild([string]$nodeExe, [string]$workDir, [string]$outFile, [string]$errFile) {
138
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
139
+ $psi.FileName = $nodeExe
140
+ $psi.Arguments = 'server.js'
141
+ $psi.WorkingDirectory = $workDir
142
+ $psi.UseShellExecute = $false
143
+ $psi.RedirectStandardOutput = $true
144
+ $psi.RedirectStandardError = $true
145
+ $psi.CreateNoWindow = $true
146
+ # Env (PORT, HOSTNAME, NODE_ENV set above) is inherited by default.
147
+
148
+ $proc = New-Object System.Diagnostics.Process
149
+ $proc.StartInfo = $psi
150
+ $proc.EnableRaisingEvents = $true
151
+
152
+ # StreamWriters with AutoFlush=true so every line lands on disk immediately.
153
+ $outFs = [System.IO.File]::Open($outFile, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
154
+ $errFs = [System.IO.File]::Open($errFile, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
155
+ $outSw = New-Object System.IO.StreamWriter($outFs, [System.Text.Encoding]::UTF8)
156
+ $errSw = New-Object System.IO.StreamWriter($errFs, [System.Text.Encoding]::UTF8)
157
+ $outSw.AutoFlush = $true
158
+ $errSw.AutoFlush = $true
159
+
160
+ $outEvent = Register-ObjectEvent -InputObject $proc -EventName OutputDataReceived -MessageData $outSw -Action {
161
+ if ($EventArgs.Data -ne $null) { $Event.MessageData.WriteLine($EventArgs.Data) }
162
+ }
163
+ $errEvent = Register-ObjectEvent -InputObject $proc -EventName ErrorDataReceived -MessageData $errSw -Action {
164
+ if ($EventArgs.Data -ne $null) { $Event.MessageData.WriteLine($EventArgs.Data) }
165
+ }
166
+
167
+ if (-not $proc.Start()) { return $null }
168
+ $proc.BeginOutputReadLine()
169
+ $proc.BeginErrorReadLine()
170
+
171
+ return [PSCustomObject]@{
172
+ Process = $proc
173
+ OutWriter = $outSw
174
+ ErrWriter = $errSw
175
+ OutFs = $outFs
176
+ ErrFs = $errFs
177
+ OutEvent = $outEvent
178
+ ErrEvent = $errEvent
179
+ }
180
+ }
181
+
129
182
  # Supervisor loop: respawn server.js if it exits. Bail after too many rapid
130
- # restarts so Task Scheduler can retry the whole task.
183
+ # restarts (3 in 60s) so Task Scheduler can decide. With an encrypted master
184
+ # key, every restart costs the user a manual PIN entry, so we prefer to fail
185
+ # loudly rather than churn silently.
131
186
  $restartCount = 0
132
187
  $windowStart = Get-Date
133
188
 
134
189
  while ($true) {
135
190
  Write-Log "Starting 'node server.js' on http://127.0.0.1:$Port (out=$ServerOut)"
136
- $proc = Start-Process -FilePath $node `
137
- -ArgumentList 'server.js' `
138
- -WorkingDirectory $InstallDir `
139
- -NoNewWindow `
140
- -PassThru `
141
- -RedirectStandardOutput $ServerOut `
142
- -RedirectStandardError $ServerErr
143
- if (-not $proc) {
144
- Write-Log "FATAL: Start-Process returned null for node server.js"
191
+ $child = Start-NodeChild -nodeExe $node -workDir $InstallDir -outFile $ServerOut -errFile $ServerErr
192
+ if (-not $child -or -not $child.Process) {
193
+ Write-Log "FATAL: failed to start node server.js"
145
194
  Start-Sleep -Seconds 5
146
195
  exit 1
147
196
  }
148
- Write-Log "spawned node PID $($proc.Id)"
149
- Wait-ProcessExit $proc.Id
150
- Write-Log "server.js exited (PID $($proc.Id))"
197
+ $childPid = $child.Process.Id
198
+ Write-Log "spawned node PID $childPid"
199
+
200
+ Wait-ProcessExit $childPid
201
+ $exitCode = $null
202
+ try { $exitCode = $child.Process.ExitCode } catch {}
203
+
204
+ # Drain remaining async reads, then unregister handlers and close files.
205
+ try { $child.Process.WaitForExit() } catch {}
206
+ try { Unregister-Event -SourceIdentifier $child.OutEvent.Name -ErrorAction SilentlyContinue } catch {}
207
+ try { Unregister-Event -SourceIdentifier $child.ErrEvent.Name -ErrorAction SilentlyContinue } catch {}
208
+ try { $child.OutWriter.Dispose() } catch {}
209
+ try { $child.ErrWriter.Dispose() } catch {}
210
+ try { $child.OutFs.Dispose() } catch {}
211
+ try { $child.ErrFs.Dispose() } catch {}
212
+ try { $child.Process.Dispose() } catch {}
213
+
214
+ Write-Log "server.js exited (PID $childPid, exit=$exitCode)"
151
215
 
152
216
  if (((Get-Date) - $windowStart).TotalSeconds -gt 60) {
153
217
  $restartCount = 0
154
218
  $windowStart = Get-Date
155
219
  }
156
220
  $restartCount++
157
- if ($restartCount -ge 5) {
221
+ if ($restartCount -ge 3) {
158
222
  Write-Log "Too many rapid restarts ($restartCount in <60s). Exiting; Task Scheduler will retry."
159
223
  exit 1
160
224
  }