@standardagents/code 0.13.8 → 0.13.10
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/README.md +196 -12
- package/dist/index.js +122 -31
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -67,10 +67,10 @@ we're letting new folks in every week.
|
|
|
67
67
|
Work continues on that machine after you close the terminal, and a machine without a daemon
|
|
68
68
|
still runs the full CLI as a watcher of sessions executing elsewhere.
|
|
69
69
|
- **Permission-aware by design.** A single auto-accept level (1–5) controls what runs unattended;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
higher-risk calls prompt you first. Operations explicitly marked as requiring permission prompt
|
|
71
|
+
regardless of their numeric risk unless you previously granted that exact tool. Accepting a risk
|
|
72
|
+
tier raises the shared thread level so every client displays and enforces the same policy. A
|
|
73
|
+
catastrophic-command guard blocks dangerous shell calls at *every* level.
|
|
74
74
|
- **Real project containment.** File destinations and background-process working directories are
|
|
75
75
|
checked through filesystem symlinks; any path that resolves outside the project always requires
|
|
76
76
|
a fresh human approval.
|
|
@@ -115,6 +115,169 @@ pnpm code <dir> # run against a project directory (defaults to cwd)
|
|
|
115
115
|
pnpm code --endpoint http://localhost:5178 <dir>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
The parallel Rust TUI preview signs in, loads the shared machine → project →
|
|
119
|
+
session projection, and attaches through the same version-matched network
|
|
120
|
+
core. It owns the terminal's alternate screen and does not open native HTTP or
|
|
121
|
+
WebSocket connections. Terminals at least 100 columns wide keep the session
|
|
122
|
+
tree available as a full-height left column; Tab or its clickable divider
|
|
123
|
+
collapses it to a one-column rail with a vertically stacked `Tab` hint. A
|
|
124
|
+
one-column gutter separates that rail from the conversation while the
|
|
125
|
+
transcript, composer, and chat controls expand into the reclaimed space.
|
|
126
|
+
Narrower terminals show the session tree full-screen when focused and omit the
|
|
127
|
+
sidebar collapse control. An authenticated launch with no attached session uses
|
|
128
|
+
that same shell. A normal launch registers or refreshes the current directory
|
|
129
|
+
as a project and highlights its row without creating a thread. `--browse`
|
|
130
|
+
opens the account-wide picker without a project target. The sidebar remains
|
|
131
|
+
available while the right column presents a session-selection empty state.
|
|
132
|
+
Machine and project headings sit on the same left edge, sessions use one
|
|
133
|
+
shallow indentation step, and a blank row separates machines. A one-cell `⣿`
|
|
134
|
+
texture marks machine headings and continues through their divider. Machine
|
|
135
|
+
rows have no disclosure glyph; a collapsed machine instead shows its known
|
|
136
|
+
thread count, such as `(13)`; offline machines show that retained count too.
|
|
137
|
+
Collapsed projects likewise append their known thread count.
|
|
138
|
+
Each machine's green/red presence dot precedes its name. The leading texture
|
|
139
|
+
cell takes the branch accent only while that machine is active, hovered, or
|
|
140
|
+
keyboard-highlighted; inactive machine texture remains structural gray.
|
|
141
|
+
Machines receive stable colors from DMUX's eight-accent palette, so color
|
|
142
|
+
belongs to a branch rather than to global selection. Machine color is limited
|
|
143
|
+
to its texture/divider while the active machine name stays white. Project
|
|
144
|
+
headings and dividers remain structural gray at every state. Unrelated
|
|
145
|
+
hierarchy recedes to dark gray.
|
|
146
|
+
The attached project and thread use typography without persistent row fills;
|
|
147
|
+
the thread title inherits its machine accent. Hover or keyboard navigation uses
|
|
148
|
+
the same brighter row background for machines, projects, and threads;
|
|
149
|
+
navigated threads also add a `›` marker. Every thread has a status cell: hollow gray is idle, solid
|
|
150
|
+
blue is unread, and a green animated texture is working. Unread state uses the
|
|
151
|
+
shared core's account-scoped, message-recency watermark; opening a thread
|
|
152
|
+
advances that shared state, and background activity timestamps never create a
|
|
153
|
+
false unread dot.
|
|
154
|
+
Click a heading or select its name and press Enter to collapse it. Right moves
|
|
155
|
+
keyboard focus to the heading's complete `[+]` button; Enter activates it,
|
|
156
|
+
Left returns to the name, and Right again returns to an attached conversation.
|
|
157
|
+
The button is hidden until its row is hovered or keyboard-highlighted; while
|
|
158
|
+
hidden, the heading divider fills those final three cells instead of reserving
|
|
159
|
+
an empty control gap.
|
|
160
|
+
The project `[+]` opens a prospective session composer. The machine `[+]`
|
|
161
|
+
opens its structure-only directory picker and registers the chosen project
|
|
162
|
+
before opening that composer. The canonical thread is created when its first
|
|
163
|
+
message is submitted, so closing an untouched composer leaves no empty thread.
|
|
164
|
+
The composer takes keyboard focus and explains that the first message creates
|
|
165
|
+
the session.
|
|
166
|
+
An offline machine has no disclosure/collapse action and hides its retained
|
|
167
|
+
project/session branch. Its machine row keeps the last known thread count and
|
|
168
|
+
can still receive keyboard or mouse highlight, but its foreground remains gray
|
|
169
|
+
and activation does nothing. Creation controls are absent while that machine is
|
|
170
|
+
offline, and presence is checked again before a core request is sent.
|
|
171
|
+
Those actions use the shared core's `project.open`, `session.start`, and
|
|
172
|
+
`machines.fsBrowse` operations. The Rust face receives filesystem data and
|
|
173
|
+
canonical session metadata from the core.
|
|
174
|
+
Press Tab to reveal and focus sessions, use ↑/↓ or j/k and Enter to activate,
|
|
175
|
+
and press Esc or Tab to return to the composer. Clicking anywhere in the
|
|
176
|
+
expanded sidebar also transfers keyboard focus there, beginning on the clicked
|
|
177
|
+
row when there is one. Left from an empty composer
|
|
178
|
+
also reveals and focuses the sidebar; Left retains normal cursor movement when
|
|
179
|
+
the composer contains text. Activating a prospective or existing chat returns
|
|
180
|
+
keyboard focus to the conversation. Switching saves the current shared draft
|
|
181
|
+
before detaching, and never stops the session's remote work. While the selected
|
|
182
|
+
conversation loads, the full-height session tree stays mounted and only the
|
|
183
|
+
right chat column enters its loading state. Session rows also switch on one
|
|
184
|
+
click. Passive mouse motion gives clickable session rows a high-contrast
|
|
185
|
+
surface, highlights the sidebar toggle rail, Markdown
|
|
186
|
+
links, and the jump-to-latest pill, and asks supporting terminals such as
|
|
187
|
+
Ghostty for a pointer cursor. Redraws are coalesced until the semantic hover
|
|
188
|
+
target really changes. The transcript owns pane-local click-drag selection, so it copies
|
|
189
|
+
semantic Markdown text without Shift and never sweeps through the sidebar.
|
|
190
|
+
White is reserved for user text and high-value accents. Assistant prose and
|
|
191
|
+
tool, system, reasoning, and structural rows use sampled cool-gray tiers;
|
|
192
|
+
semantic states and Markdown keep muted accent colors. Live thought and answer blocks grow from a stable prefix,
|
|
193
|
+
so streamed chunks extend visible text without deleting and reflowing its
|
|
194
|
+
beginning. Running subagents retain a bounded list of their reads, searches,
|
|
195
|
+
edits, and other tool actions beneath the current activity phrase.
|
|
196
|
+
Settled reads and searches roll up under `Explored` while preserving every
|
|
197
|
+
action. Successful commands use one compact highlighted row. Failures retain
|
|
198
|
+
their complete output as a quiet gray block beneath the highlighted command,
|
|
199
|
+
and prose-like tool results share the transcript's word wrapper. Known
|
|
200
|
+
arguments and results receive muted semantic syntax colors. Consecutive edits
|
|
201
|
+
roll up under `Edited N files`; their complete red and green hunks fill the
|
|
202
|
+
available conversation width.
|
|
203
|
+
Thinking appears in an expandable block with an animated Braille texture while
|
|
204
|
+
the trace is active. Active traces start collapsed, and completed traces retain
|
|
205
|
+
a one-line disclosure. Click any thinking row to toggle its trace, or press Ctrl+T
|
|
206
|
+
to toggle the newest trace.
|
|
207
|
+
Releasing the button clears the highlight and shows `Copied text` in the status
|
|
208
|
+
row. Clicking anywhere in the active conversation column returns keyboard
|
|
209
|
+
focus to its composer without disrupting drag selection. Prose wraps at word
|
|
210
|
+
boundaries; long unbroken tokens and code still
|
|
211
|
+
hard-wrap to the pane. The complete loaded transcript remains scrollable with
|
|
212
|
+
the mouse wheel or Page Up/Page Down. Ctrl+Home jumps to the beginning and
|
|
213
|
+
Ctrl+End resumes bottom-follow; output that arrives while scrolled up leaves
|
|
214
|
+
the viewport anchored and shows a one-click “Jump to latest” pill at the bottom
|
|
215
|
+
of the conversation. The pill calls out new output when it arrives. Wheel bursts
|
|
216
|
+
accumulate between frames, preserving responsive trackpad motion instead of dropping input.
|
|
217
|
+
While a turn runs, an animated amber/coral/violet Braille status row stays
|
|
218
|
+
directly above the composer, after the optional `Session todos` card.
|
|
219
|
+
The composer and submitted user messages share one quiet filled rectangular
|
|
220
|
+
surface with a chevron gutter. One empty surface row sits above the editable
|
|
221
|
+
rows and one below, so the empty composer is three terminal rows; it grows
|
|
222
|
+
from one to eight content rows, wraps prose on word boundaries,
|
|
223
|
+
preserves hard newlines from Shift+Enter and multiline paste, and then scrolls
|
|
224
|
+
internally without displacing more of the transcript. Short conversations use
|
|
225
|
+
natural document flow: the composer and telemetry follow the final transcript
|
|
226
|
+
row, leaving unused space below. Once the transcript consumes the available
|
|
227
|
+
viewport, the composer stays pinned at the bottom and the transcript scrolls
|
|
228
|
+
above it. The mouse
|
|
229
|
+
wheel follows the pane under the pointer, so a capped composer scrolls without
|
|
230
|
+
moving the conversation; the next edit resumes caret-follow. Pastes over 1,000
|
|
231
|
+
characters render as an atomic cyan `[Pasted Content N chars]` element while
|
|
232
|
+
their exact text remains in the shared draft and submitted message. Backspace
|
|
233
|
+
or Delete at that element removes the complete paste. Enter sends or queues,
|
|
234
|
+
while Ctrl+Enter steers. Up/Down moves through visual editor
|
|
235
|
+
rows before recalling the core-backed per-thread sent history at the top or
|
|
236
|
+
bottom edge.
|
|
237
|
+
Structured session todos appear in a collapsible card above the composer. One
|
|
238
|
+
blank row separates the neutral teal-gray card from the transcript. Todo
|
|
239
|
+
descriptions and steps wrap to show their complete content. Escape promotes the
|
|
240
|
+
oldest queued message through the shared core. During active work with an empty
|
|
241
|
+
queue, Escape stops execution and releases the composer for a new message.
|
|
242
|
+
An offline runner does not make its cloud conversation disappear: the session
|
|
243
|
+
still opens for review, but the composer pauses instead of letting new work
|
|
244
|
+
silently wait for a missing executor. There is no separate offline banner; the
|
|
245
|
+
composer becomes a hollow, unfilled surface carrying the state inline. If work
|
|
246
|
+
is already waiting, it says `Waiting for <machine> — Esc stops`. Pushed machine-presence events unlock
|
|
247
|
+
the composer and restore the retained sidebar branch as soon as the daemon
|
|
248
|
+
reconnects; there is no presence poll or session relist.
|
|
249
|
+
The line beneath the composer is session telemetry rather than a persistent
|
|
250
|
+
keyboard cheat sheet: it shows the attached thread's agent, runner machine,
|
|
251
|
+
current working directory, running background-process count, and
|
|
252
|
+
compaction-scaled context usage. The muted path gets priority over the literal
|
|
253
|
+
`context` label. Full agent names stay visible at comfortable widths; only a
|
|
254
|
+
narrow footer abbreviates them (`Unlimited One` → `U1`, `Sama One` → `S1`)
|
|
255
|
+
before squeezing the deepest complete path breadcrumbs further.
|
|
256
|
+
The row ends with the old terminal face's five colored permission dots, so the
|
|
257
|
+
current effective auto-accept ceiling remains visible without adding another
|
|
258
|
+
row. Accepted risk grants raise the dots immediately and reconcile from the
|
|
259
|
+
persisted approval state. Approval requests show the tool summary, stated
|
|
260
|
+
reason, execution machine, five-dot risk level, and fully labeled decisions in
|
|
261
|
+
a bordered card. These
|
|
262
|
+
values come from the shared roster, session tree,
|
|
263
|
+
approvals, process registry, and context projections in the network core.
|
|
264
|
+
Headings, emphasis, links, lists, tables, quotes, and fenced code use
|
|
265
|
+
the same progressive parser contract as the TypeScript UI. PNG attachments use
|
|
266
|
+
Kitty Unicode placeholders in verified Kitty/Ghostty/WezTerm environments,
|
|
267
|
+
including configured tmux; other terminals keep the same layout and show a
|
|
268
|
+
text fallback:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
pnpm tui:rust
|
|
272
|
+
# Open the account-wide project picker:
|
|
273
|
+
pnpm tui:rust -- --browse
|
|
274
|
+
# Optional direct-attach fast path:
|
|
275
|
+
pnpm tui:rust -- --thread <thread-id>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
This preview is additive; the TypeScript CLI remains the normal entry point
|
|
279
|
+
while the Rust face is brought to feature parity.
|
|
280
|
+
|
|
118
281
|
Installed from npm:
|
|
119
282
|
|
|
120
283
|
```bash
|
|
@@ -154,6 +317,19 @@ The first time you choose this machine, the CLI offers to install its daemon in
|
|
|
154
317
|
here. Declining leaves local execution unavailable; remote machines and watching stay available,
|
|
155
318
|
and the command sets it up any time.
|
|
156
319
|
|
|
320
|
+
Manage the local daemon from any shell:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
standardcode daemon status
|
|
324
|
+
standardcode daemon restart
|
|
325
|
+
standardcode daemon update
|
|
326
|
+
standardcode daemon add-project /absolute/path/to/project
|
|
327
|
+
standardcode daemon uninstall
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`restart` immediately reloads the installed launchd or systemd service. `update` lets active tool
|
|
331
|
+
calls finish, installs the latest Standard Code package, and restarts the daemon.
|
|
332
|
+
|
|
157
333
|
Use `--endpoint [url]` or `-e [url]` to point a single CLI run at a different Standard Agents
|
|
158
334
|
instance (local dev, self-hosted). If you omit the URL, the CLI prompts for it. Tokens are
|
|
159
335
|
remembered per endpoint, but this override does not change the saved default endpoint.
|
|
@@ -178,9 +354,10 @@ development never disables TLS verification in unrelated commands.
|
|
|
178
354
|
|
|
179
355
|
- **Forwarded tools** executed by the session's machine daemon: read/write/edit files, list,
|
|
180
356
|
grep, glob, bash, delete.
|
|
181
|
-
- **Permissions**: a
|
|
182
|
-
server-marked permission requests cannot be
|
|
183
|
-
command guard blocks dangerous shell commands
|
|
357
|
+
- **Permissions**: a shared auto-accept level 1–5 (shift-tab cycles it); accepting a risk tier
|
|
358
|
+
raises the stored thread level across clients; server-marked permission requests cannot be
|
|
359
|
+
downgraded by a model-supplied risk. A catastrophic-command guard blocks dangerous shell commands
|
|
360
|
+
at every level.
|
|
184
361
|
- **Durable, resumable** long-running/permission-gated tool calls (park & resume over HTTP).
|
|
185
362
|
- **Background processes** tracked in the thread (survive resume from any machine).
|
|
186
363
|
- **Subagent delegation** and non-blocking web research.
|
|
@@ -194,7 +371,8 @@ development never disables TLS verification in unrelated commands.
|
|
|
194
371
|
## Tests
|
|
195
372
|
|
|
196
373
|
```bash
|
|
197
|
-
pnpm test #
|
|
374
|
+
pnpm test # TypeScript checks/tests plus Rust unit and PTY tests
|
|
375
|
+
pnpm check:rust # rustfmt and clippy with warnings denied
|
|
198
376
|
```
|
|
199
377
|
|
|
200
378
|
`scripts/run-tests.mjs` owns the explicit no-backend test manifest, prints each
|
|
@@ -216,10 +394,16 @@ test layout, and contributor conventions.
|
|
|
216
394
|
- Two version-locked packages: `@standardagents/code` and its shared
|
|
217
395
|
`@standardagents/code-network` client. Both use Node built-ins and
|
|
218
396
|
web-platform APIs without third-party runtime packages.
|
|
219
|
-
- `src/progressive-markdown.ts` is the canonical structured streaming parser
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
397
|
+
- `src/progressive-markdown.ts` is the canonical structured streaming parser used by every face.
|
|
398
|
+
The Node face-core sends its versioned documents to Rust, which handles
|
|
399
|
+
terminal layout and paint. Web receives an exact source copy, and macOS runs
|
|
400
|
+
the generated JavaScriptCore bundle. Shared vectors cover parser output and
|
|
401
|
+
Rust transport-schema compatibility.
|
|
402
|
+
Run `pnpm markdown:sync` after changing the authored TypeScript parser and
|
|
403
|
+
`pnpm markdown:check` in CI.
|
|
404
|
+
- The hosted instance authors the session-state reducer published through
|
|
405
|
+
`@standardagents/code-network`. It owns live-draft accumulation and busy/tool/message
|
|
406
|
+
reconciliation; a changed chunk ID is an alias, never permission to erase text.
|
|
223
407
|
- Commit messages use Conventional Commits, scope `cli` (e.g. `feat(cli): …`).
|
|
224
408
|
|
|
225
409
|
---
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import * as approvals_star from '@standardagents/code-network/approvals';
|
|
|
14
14
|
import net from 'net';
|
|
15
15
|
import { setImmediate } from 'timers';
|
|
16
16
|
import fs12 from 'fs';
|
|
17
|
+
import { isTranscriptMetaTool } from '@standardagents/code-network/tool-activity';
|
|
17
18
|
import { permissionKey, Bridge as Bridge$1 } from '@standardagents/code-network/bridge';
|
|
18
19
|
import readline from 'readline';
|
|
19
20
|
import * as types_star from '@standardagents/code-network/types';
|
|
@@ -687,7 +688,10 @@ var Bridge = class extends Bridge$1 {
|
|
|
687
688
|
isCatastrophic: (command) => isCatastrophic(command),
|
|
688
689
|
decide: (permKey, risk, requestPermission) => decide(perm, permKey, risk, requestPermission),
|
|
689
690
|
grantAlways: (permKey) => void perm.alwaysAllow.add(permKey),
|
|
690
|
-
grantRisk: (risk) =>
|
|
691
|
+
grantRisk: (risk) => {
|
|
692
|
+
perm.level = Math.max(perm.level, risk);
|
|
693
|
+
perm.allowRisk.clear();
|
|
694
|
+
},
|
|
691
695
|
persistApprovals: () => void (0, approvals_exports.saveApprovals)(api, threadId, perm)
|
|
692
696
|
},
|
|
693
697
|
{
|
|
@@ -798,7 +802,6 @@ function prettyAgentName(id) {
|
|
|
798
802
|
if (!key) return "subagent";
|
|
799
803
|
return AGENT_NAMES[key] || key.replace(/[_-]+/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase());
|
|
800
804
|
}
|
|
801
|
-
var META_TOOLS = /* @__PURE__ */ new Set(["set_goal", "update_goal_step", "reasoning"]);
|
|
802
805
|
var APPROVAL_PENDING_MARKER = "Awaiting your approval";
|
|
803
806
|
var ToolActivityFeed = class {
|
|
804
807
|
calls = /* @__PURE__ */ new Map();
|
|
@@ -833,7 +836,7 @@ var ToolActivityFeed = class {
|
|
|
833
836
|
const text = (0, session_state_exports.messageText)(message.content);
|
|
834
837
|
if (text.includes(session_state_exports.WAITING_FOR_CLIENT_MARKER) || text.includes(APPROVAL_PENDING_MARKER)) return null;
|
|
835
838
|
this.settled.add(callId);
|
|
836
|
-
if (
|
|
839
|
+
if (isTranscriptMetaTool(req.tool)) return null;
|
|
837
840
|
const summary = describe(req);
|
|
838
841
|
if (toolResultFailed(message, text)) {
|
|
839
842
|
return { line: `\u2717 ${summary} \u2014 ${text.trim() || "failed"}` };
|
|
@@ -4014,7 +4017,7 @@ function readVersion() {
|
|
|
4014
4017
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
4015
4018
|
} catch {
|
|
4016
4019
|
}
|
|
4017
|
-
return "0.13.
|
|
4020
|
+
return "0.13.10" ;
|
|
4018
4021
|
}
|
|
4019
4022
|
function isLocalHost(host) {
|
|
4020
4023
|
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.endsWith(".localhost") || /^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host);
|
|
@@ -6464,7 +6467,10 @@ var ThreadWorker = class {
|
|
|
6464
6467
|
return { choice: "deny", reason: "The approval request timed out with no one to approve it." };
|
|
6465
6468
|
}
|
|
6466
6469
|
if (response.choice === "always") this.perm.alwaysAllow.add(permKey);
|
|
6467
|
-
if (response.choice === "always_risk")
|
|
6470
|
+
if (response.choice === "always_risk") {
|
|
6471
|
+
this.perm.level = Math.max(this.perm.level, effectiveRisk);
|
|
6472
|
+
this.perm.allowRisk.clear();
|
|
6473
|
+
}
|
|
6468
6474
|
if (response.choice === "always" || response.choice === "always_risk") {
|
|
6469
6475
|
(0, approvals_exports.saveApprovals)(api, threadId, this.perm);
|
|
6470
6476
|
}
|
|
@@ -6976,6 +6982,15 @@ function run2(cmd, args) {
|
|
|
6976
6982
|
var plistPath = () => path9.join(os6.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
6977
6983
|
var unitPath = () => path9.join(os6.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
6978
6984
|
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
6985
|
+
function restartServiceInvocation(platform, uid = 501) {
|
|
6986
|
+
if (platform === "darwin") {
|
|
6987
|
+
return { cmd: "launchctl", args: ["kickstart", "-k", `gui/${uid}/${SERVICE_LABEL}`] };
|
|
6988
|
+
}
|
|
6989
|
+
if (platform === "linux") {
|
|
6990
|
+
return { cmd: "systemctl", args: ["--user", "restart", SYSTEMD_UNIT] };
|
|
6991
|
+
}
|
|
6992
|
+
return null;
|
|
6993
|
+
}
|
|
6979
6994
|
function installService(command, endpoint) {
|
|
6980
6995
|
if (process.platform === "darwin") return installLaunchd(command, endpoint);
|
|
6981
6996
|
if (process.platform === "linux") return installSystemd(command, endpoint);
|
|
@@ -7086,6 +7101,39 @@ function uninstallService() {
|
|
|
7086
7101
|
}
|
|
7087
7102
|
return { ok: false, detail: `Unsupported platform: ${process.platform}` };
|
|
7088
7103
|
}
|
|
7104
|
+
function restartService() {
|
|
7105
|
+
if (process.platform === "darwin") {
|
|
7106
|
+
if (!fs12.existsSync(plistPath())) {
|
|
7107
|
+
return {
|
|
7108
|
+
ok: false,
|
|
7109
|
+
detail: "The daemon service is not installed. Run `standardcode daemon install` first."
|
|
7110
|
+
};
|
|
7111
|
+
}
|
|
7112
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 501;
|
|
7113
|
+
const invocation = restartServiceInvocation(process.platform, uid);
|
|
7114
|
+
const restart = run2(invocation.cmd, invocation.args);
|
|
7115
|
+
if (restart.ok) return { ok: true, detail: "LaunchAgent restarted" };
|
|
7116
|
+
const boot = run2("launchctl", ["bootstrap", `gui/${uid}`, plistPath()]);
|
|
7117
|
+
if (boot.ok) return { ok: true, detail: "LaunchAgent loaded and started" };
|
|
7118
|
+
return {
|
|
7119
|
+
ok: false,
|
|
7120
|
+
detail: `launchctl failed: ${restart.output || boot.output}`,
|
|
7121
|
+
manualHint: `Load it manually: launchctl bootstrap gui/${uid} ${plistPath()}`
|
|
7122
|
+
};
|
|
7123
|
+
}
|
|
7124
|
+
if (process.platform === "linux") {
|
|
7125
|
+
if (!fs12.existsSync(unitPath())) {
|
|
7126
|
+
return {
|
|
7127
|
+
ok: false,
|
|
7128
|
+
detail: "The daemon service is not installed. Run `standardcode daemon install` first."
|
|
7129
|
+
};
|
|
7130
|
+
}
|
|
7131
|
+
const invocation = restartServiceInvocation(process.platform);
|
|
7132
|
+
const restart = run2(invocation.cmd, invocation.args);
|
|
7133
|
+
return restart.ok ? { ok: true, detail: "systemd user service restarted" } : { ok: false, detail: `systemctl restart failed: ${restart.output}` };
|
|
7134
|
+
}
|
|
7135
|
+
return { ok: false, detail: `Unsupported platform: ${process.platform}` };
|
|
7136
|
+
}
|
|
7089
7137
|
function serviceStatus() {
|
|
7090
7138
|
if (process.platform === "darwin") {
|
|
7091
7139
|
const installed = fs12.existsSync(plistPath());
|
|
@@ -7133,6 +7181,8 @@ function usage() {
|
|
|
7133
7181
|
" e.g. when another app drives the install).",
|
|
7134
7182
|
" uninstall Stop and remove the service.",
|
|
7135
7183
|
" status Service + registry status for this machine.",
|
|
7184
|
+
" restart Restart the installed service immediately.",
|
|
7185
|
+
" update [--endpoint url] Install the latest CLI, then restart the service.",
|
|
7136
7186
|
" run [--endpoint url] Run the daemon in the foreground (what the service runs).",
|
|
7137
7187
|
" add-project <path> Register a project directory for remote sessions.",
|
|
7138
7188
|
" remove-project <path> Remove a registered project directory.",
|
|
@@ -7262,9 +7312,10 @@ async function installCommand(endpointFlag, opts = {}) {
|
|
|
7262
7312
|
stdout.write(
|
|
7263
7313
|
`${c3.green}\u2713${c3.reset} ${c3.bold}${displayName}${c3.reset} is online.
|
|
7264
7314
|
|
|
7265
|
-
|
|
7266
|
-
${c3.dim}Projects
|
|
7267
|
-
|
|
7315
|
+
Standard Code sessions can now edit files and run tools on this machine while you drive them from any device signed in to your account.
|
|
7316
|
+
${c3.dim}Projects are added automatically when you start Standard Code from their directory.
|
|
7317
|
+
To add a project manually, run:${c3.reset}
|
|
7318
|
+
${c3.bold}standardcode daemon add-project /absolute/path/to/project${c3.reset}
|
|
7268
7319
|
`
|
|
7269
7320
|
);
|
|
7270
7321
|
} else {
|
|
@@ -7314,6 +7365,55 @@ async function statusCommand() {
|
|
|
7314
7365
|
for (const p of projects.sort()) stdout.write(` ${c3.dim}${p}${c3.reset}
|
|
7315
7366
|
`);
|
|
7316
7367
|
}
|
|
7368
|
+
async function updateDaemonCommand(endpointFlag) {
|
|
7369
|
+
const status = serviceStatus();
|
|
7370
|
+
if (!status.installed) {
|
|
7371
|
+
stdout.write(
|
|
7372
|
+
`${c3.red}\u2717${c3.reset} The daemon service is not installed. Run \`standardcode daemon install\` first.
|
|
7373
|
+
`
|
|
7374
|
+
);
|
|
7375
|
+
process.exit(1);
|
|
7376
|
+
}
|
|
7377
|
+
const endpoint = resolveEndpoint(endpointFlag);
|
|
7378
|
+
const api = await ensureSignedIn(endpoint);
|
|
7379
|
+
const identity = loadMachineIdentity();
|
|
7380
|
+
const record = await loadMachine(api, identity.machine_id).catch(() => null);
|
|
7381
|
+
if (status.running && record && daemonOnline(record)) {
|
|
7382
|
+
await enqueueMachineCommand(api, identity.machine_id, "update");
|
|
7383
|
+
stdout.write(
|
|
7384
|
+
`${c3.green}\u2713${c3.reset} Update requested. The daemon will finish active tool calls, install the latest Standard Code, and restart.
|
|
7385
|
+
`
|
|
7386
|
+
);
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
const pm = detectPackageManager();
|
|
7390
|
+
if (!pm) {
|
|
7391
|
+
stdout.write(
|
|
7392
|
+
`${c3.red}\u2717${c3.reset} This source checkout cannot update the installed CLI. Run \`standardcode daemon update\` through your global Standard Code command.
|
|
7393
|
+
`
|
|
7394
|
+
);
|
|
7395
|
+
process.exit(1);
|
|
7396
|
+
}
|
|
7397
|
+
const { display } = updateCommand(pm);
|
|
7398
|
+
stdout.write(`${c3.dim}Running ${display}\u2026${c3.reset}
|
|
7399
|
+
`);
|
|
7400
|
+
const result = await runUpdate(pm);
|
|
7401
|
+
if (!result.ok) {
|
|
7402
|
+
stdout.write(`${c3.red}\u2717${c3.reset} Update failed.
|
|
7403
|
+
`);
|
|
7404
|
+
for (const line of result.output.trim().split("\n").slice(-6)) {
|
|
7405
|
+
if (line) stdout.write(`${c3.dim}${line}${c3.reset}
|
|
7406
|
+
`);
|
|
7407
|
+
}
|
|
7408
|
+
process.exit(1);
|
|
7409
|
+
}
|
|
7410
|
+
const restarted = restartService();
|
|
7411
|
+
stdout.write(
|
|
7412
|
+
`${restarted.ok ? c3.green + "\u2713" : c3.red + "\u2717"}${c3.reset} ${restarted.ok ? "Standard Code updated. " : "Standard Code updated, but the daemon restart failed. "}${restarted.detail}
|
|
7413
|
+
`
|
|
7414
|
+
);
|
|
7415
|
+
if (!restarted.ok) process.exit(1);
|
|
7416
|
+
}
|
|
7317
7417
|
async function projectCommand(action, target) {
|
|
7318
7418
|
if (!target) {
|
|
7319
7419
|
stdout.write(`${c3.red}\u2717${c3.reset} Expected a project path.
|
|
@@ -7369,6 +7469,18 @@ async function runDaemonCommand(argv) {
|
|
|
7369
7469
|
case "status":
|
|
7370
7470
|
await statusCommand();
|
|
7371
7471
|
return;
|
|
7472
|
+
case "restart": {
|
|
7473
|
+
const result = restartService();
|
|
7474
|
+
stdout.write(`${result.ok ? c3.green + "\u2713" : c3.red + "\u2717"}${c3.reset} ${result.detail}
|
|
7475
|
+
`);
|
|
7476
|
+
if (result.manualHint) stdout.write(`${c3.dim}${result.manualHint}${c3.reset}
|
|
7477
|
+
`);
|
|
7478
|
+
if (!result.ok) process.exit(1);
|
|
7479
|
+
return;
|
|
7480
|
+
}
|
|
7481
|
+
case "update":
|
|
7482
|
+
await updateDaemonCommand(endpoint);
|
|
7483
|
+
return;
|
|
7372
7484
|
case "add-project":
|
|
7373
7485
|
await projectCommand("add", rest[0]);
|
|
7374
7486
|
return;
|
|
@@ -8586,13 +8698,9 @@ async function runInteractive(tui, api, accountStream, threadId, projectDir, mac
|
|
|
8586
8698
|
const pendingSent = /* @__PURE__ */ new Map();
|
|
8587
8699
|
let lastSent = null;
|
|
8588
8700
|
let upgradeInFlight = false;
|
|
8589
|
-
let tokensIn = 0;
|
|
8590
|
-
let tokensOut = 0;
|
|
8591
8701
|
let liveOut = 0;
|
|
8592
|
-
const countedLogs = /* @__PURE__ */ new Set();
|
|
8593
8702
|
const activeSteps = /* @__PURE__ */ new Map();
|
|
8594
8703
|
const refreshStatus = () => {
|
|
8595
|
-
tui.setTokens(tokensIn, tokensOut + liveOut);
|
|
8596
8704
|
let label = null;
|
|
8597
8705
|
for (const v of activeSteps.values()) label = v;
|
|
8598
8706
|
tui.setStep(label, liveOut);
|
|
@@ -9432,24 +9540,6 @@ ${c5.dim}runs on ${request.machine || runnerName}${c5.reset}`,
|
|
|
9432
9540
|
refreshBgCount();
|
|
9433
9541
|
void relayApprovals().catch(() => {
|
|
9434
9542
|
});
|
|
9435
|
-
try {
|
|
9436
|
-
const logs = await api.getLogs(threadId, 100);
|
|
9437
|
-
let landed = 0;
|
|
9438
|
-
for (const l of logs) {
|
|
9439
|
-
if (!l.is_complete) continue;
|
|
9440
|
-
const id = l.id ?? `${l.created_at}:${l.total_tokens}`;
|
|
9441
|
-
if (countedLogs.has(id)) continue;
|
|
9442
|
-
countedLogs.add(id);
|
|
9443
|
-
const inT = Number(l.input_tokens) || 0;
|
|
9444
|
-
const outT = Number(l.output_tokens) || 0;
|
|
9445
|
-
tokensIn += inT;
|
|
9446
|
-
tokensOut += outT;
|
|
9447
|
-
landed += outT;
|
|
9448
|
-
}
|
|
9449
|
-
if (landed > 0) liveOut = 0;
|
|
9450
|
-
refreshStatus();
|
|
9451
|
-
} catch {
|
|
9452
|
-
}
|
|
9453
9543
|
const COMPACTION_TRIGGER_FRACTION = 0.5;
|
|
9454
9544
|
const COMPACTION_TRIGGER_CAP = 5e4;
|
|
9455
9545
|
try {
|
|
@@ -9733,7 +9823,8 @@ function showDaemonInfo(tui, session) {
|
|
|
9733
9823
|
`${c5.dim}The daemon is the executor on every machine: it runs the agent's tools, keeps working after you close the terminal, self-restarts, self-updates, and picks up sessions you start from anywhere.${c5.reset}`
|
|
9734
9824
|
);
|
|
9735
9825
|
} else {
|
|
9736
|
-
tui.print(`${c5.dim}
|
|
9826
|
+
tui.print(`${c5.dim}Daemon commands: standardcode daemon status | restart | update | uninstall.${c5.reset}`);
|
|
9827
|
+
tui.print(`${c5.dim}Add a project: standardcode daemon add-project /absolute/path/to/project${c5.reset}`);
|
|
9737
9828
|
}
|
|
9738
9829
|
}
|
|
9739
9830
|
function showKeybindings(tui) {
|