@phnx-labs/agents-cli 1.20.50 → 1.20.52
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/CHANGELOG.md +40 -0
- package/dist/commands/browser-picker.js +1 -18
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.js +7 -25
- package/dist/commands/computer.d.ts +1 -0
- package/dist/commands/computer.js +129 -8
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +66 -8
- package/dist/commands/factory.js +9 -4
- package/dist/commands/feed.d.ts +9 -0
- package/dist/commands/feed.js +69 -0
- package/dist/commands/inspect.js +1 -11
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +9 -6
- package/dist/commands/message.js +1 -4
- package/dist/commands/profiles.js +1 -18
- package/dist/commands/repo.js +33 -14
- package/dist/commands/resource-view.d.ts +1 -0
- package/dist/commands/resource-view.js +5 -17
- package/dist/commands/secrets.d.ts +23 -0
- package/dist/commands/secrets.js +174 -70
- package/dist/commands/sessions-picker.js +1 -18
- package/dist/commands/sessions.js +6 -8
- package/dist/commands/teams-picker.js +1 -32
- package/dist/commands/teams.js +5 -27
- package/dist/commands/tmux.js +1 -3
- package/dist/commands/view.js +1 -9
- package/dist/commands/worktree.js +1 -4
- package/dist/index.js +6 -2
- package/dist/lib/agents.d.ts +0 -4
- package/dist/lib/agents.js +20 -33
- package/dist/lib/auto-dispatch-linear.d.ts +18 -0
- package/dist/lib/auto-dispatch-linear.js +107 -0
- package/dist/lib/auto-dispatch-provider.d.ts +10 -0
- package/dist/lib/auto-dispatch-provider.js +25 -0
- package/dist/lib/auto-dispatch.d.ts +87 -0
- package/dist/lib/auto-dispatch.js +142 -0
- package/dist/lib/browser/cdp.js +11 -2
- package/dist/lib/browser/drivers/ssh.d.ts +28 -10
- package/dist/lib/browser/drivers/ssh.js +57 -18
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/refs.js +1 -5
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cli-resources.d.ts +0 -2
- package/dist/lib/cli-resources.js +30 -13
- package/dist/lib/cloud/rush.d.ts +15 -24
- package/dist/lib/cloud/rush.js +7 -32
- package/dist/lib/crabbox/cli.js +4 -1
- package/dist/lib/crabbox/lease.d.ts +6 -0
- package/dist/lib/crabbox/lease.js +40 -10
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +73 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +288 -101
- package/dist/lib/exec.js +32 -14
- package/dist/lib/feed.d.ts +56 -0
- package/dist/lib/feed.js +251 -0
- package/dist/lib/format.d.ts +38 -0
- package/dist/lib/format.js +108 -0
- package/dist/lib/git.d.ts +21 -0
- package/dist/lib/git.js +92 -0
- package/dist/lib/hooks/cache.d.ts +9 -2
- package/dist/lib/hooks/cache.js +220 -8
- package/dist/lib/hooks.js +24 -10
- package/dist/lib/hosts/passthrough.js +1 -0
- package/dist/lib/platform/exec.d.ts +4 -1
- package/dist/lib/platform/exec.js +8 -2
- package/dist/lib/resources.d.ts +0 -8
- package/dist/lib/resources.js +0 -10
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.js +10 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +38 -8
- package/dist/lib/secrets/icloud-import.d.ts +70 -0
- package/dist/lib/secrets/icloud-import.js +173 -0
- package/dist/lib/secrets/index.d.ts +36 -0
- package/dist/lib/secrets/index.js +99 -9
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +11 -1
- package/dist/lib/session/active.js +3 -0
- package/dist/lib/session/db.d.ts +1 -4
- package/dist/lib/session/db.js +20 -25
- package/dist/lib/session/discover.d.ts +2 -2
- package/dist/lib/session/discover.js +60 -48
- package/dist/lib/session/parse.js +35 -34
- package/dist/lib/session/render.d.ts +7 -3
- package/dist/lib/session/render.js +15 -9
- package/dist/lib/session/state.d.ts +55 -0
- package/dist/lib/session/state.js +100 -11
- package/dist/lib/session/types.d.ts +9 -0
- package/dist/lib/shims.d.ts +9 -3
- package/dist/lib/shims.js +44 -8
- package/dist/lib/ssh-tunnel.d.ts +33 -2
- package/dist/lib/ssh-tunnel.js +94 -7
- package/dist/lib/staleness/types.d.ts +0 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/types.d.ts +24 -1
- package/dist/lib/versions.d.ts +0 -26
- package/dist/lib/versions.js +2 -145
- package/dist/lib/warn-unpushed.d.ts +40 -0
- package/dist/lib/warn-unpushed.js +128 -0
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +3 -1
- package/dist/lib/resources/index.d.ts +0 -53
- package/dist/lib/resources/index.js +0 -76
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
import { summarizeToolUse } from './parse.js';
|
|
18
18
|
/** A healthy live session writes several times a minute; 2 min ⇒ "recently active". */
|
|
19
19
|
const ACTIVE_WINDOW_MS = 2 * 60_000;
|
|
20
|
+
/**
|
|
21
|
+
* A prose trailing question ("…?") is a HEURISTIC, so it decays: past this long
|
|
22
|
+
* with no session writes it stops classifying as waiting_input — otherwise a
|
|
23
|
+
* finished session that signed off with "anything else?" reads as needing input
|
|
24
|
+
* forever (RUSH-1522). The structural ExitPlanMode / AskUserQuestion signals are
|
|
25
|
+
* exempt: they are precise, still-unanswered decisions.
|
|
26
|
+
*/
|
|
27
|
+
const PROSE_QUESTION_FRESH_MS = 30 * 60_000;
|
|
20
28
|
/** Claude tool names that structurally mean "the agent handed control back to you". */
|
|
21
29
|
const PLAN_TOOL = 'ExitPlanMode';
|
|
22
30
|
const ASK_TOOL = 'AskUserQuestion';
|
|
@@ -130,6 +138,69 @@ export function extractCreatedTicket(text) {
|
|
|
130
138
|
return `#${gh[1]}`;
|
|
131
139
|
return undefined;
|
|
132
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Pull the plan markdown out of an `ExitPlanMode` tool_use event's args. The
|
|
143
|
+
* Claude tool schema is `{ plan: string }`; the transcript parser already
|
|
144
|
+
* lifts `input` onto `event.args`. Returns undefined for a missing/empty plan
|
|
145
|
+
* so consumers can rely on `plan?: string` truthiness.
|
|
146
|
+
*/
|
|
147
|
+
export function extractPlanText(args) {
|
|
148
|
+
const plan = args?.plan;
|
|
149
|
+
if (typeof plan !== 'string')
|
|
150
|
+
return undefined;
|
|
151
|
+
const trimmed = plan.trim();
|
|
152
|
+
return trimmed ? plan : undefined;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Structured question from a Claude `AskUserQuestion` tool call. Its input is
|
|
156
|
+
* `{ questions: [{ question, header, options: [{label, description}] }] }` and the
|
|
157
|
+
* whole thing is already parsed onto `event.args` by the transcript parser — this
|
|
158
|
+
* surfaces the first question + its options (instead of collapsing to a generic
|
|
159
|
+
* "Asked you a question"). The prompt is a select-list, so each option carries its
|
|
160
|
+
* 1-based selection digit as `key`.
|
|
161
|
+
*/
|
|
162
|
+
export function structuredQuestionFromAsk(args) {
|
|
163
|
+
const q = Array.isArray(args?.questions) ? args.questions[0] : undefined;
|
|
164
|
+
if (!q)
|
|
165
|
+
return undefined;
|
|
166
|
+
const text = oneLine(String(q.question ?? q.header ?? '')) || 'Asked you a question';
|
|
167
|
+
const raw = Array.isArray(q.options) ? q.options : [];
|
|
168
|
+
const options = [];
|
|
169
|
+
for (const o of raw) {
|
|
170
|
+
const label = typeof o === 'string' ? oneLine(o) : o?.label != null ? oneLine(String(o.label)) : '';
|
|
171
|
+
if (!label)
|
|
172
|
+
continue;
|
|
173
|
+
const description = typeof o === 'object' && o?.description != null ? oneLine(String(o.description)) : undefined;
|
|
174
|
+
options.push({ label, description, key: String(options.length + 1) });
|
|
175
|
+
}
|
|
176
|
+
return { text, reason: 'question', options: options.length ? options : undefined };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Canonical approve/deny choices for an interactive prompt that carries no
|
|
180
|
+
* agent-supplied option list: Claude's plan-review and permission dialogs. Approve
|
|
181
|
+
* is reliably option 1; deny/keep-planning maps to ESC, which cancels the prompt in
|
|
182
|
+
* every variant (2- or 3-option) — safer than guessing a digit that could differ.
|
|
183
|
+
*/
|
|
184
|
+
function planReviewQuestion() {
|
|
185
|
+
return {
|
|
186
|
+
text: 'Plan ready — review it',
|
|
187
|
+
reason: 'plan_review',
|
|
188
|
+
options: [
|
|
189
|
+
{ label: 'Approve plan', key: '1' },
|
|
190
|
+
{ label: 'Keep planning', key: 'esc' },
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function permissionQuestion(preview) {
|
|
195
|
+
return {
|
|
196
|
+
text: preview ? `Permission — ${preview}` : 'Waiting on a permission prompt',
|
|
197
|
+
reason: 'permission',
|
|
198
|
+
options: [
|
|
199
|
+
{ label: 'Approve', key: '1' },
|
|
200
|
+
{ label: 'Deny', key: 'esc' },
|
|
201
|
+
],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
133
204
|
/** Does an assistant message read as a question directed at the user? */
|
|
134
205
|
function looksLikeQuestion(text) {
|
|
135
206
|
const t = text.trim();
|
|
@@ -177,18 +248,27 @@ export function inferActivity(events, ctx = {}) {
|
|
|
177
248
|
const lastToolUse = lastOf(meaningful, e => e.type === 'tool_use');
|
|
178
249
|
// The most informative recent line: a message or tool call as-is, but for a
|
|
179
250
|
// trailing tool_result/thinking show the tool *call* that produced it (its
|
|
180
|
-
// command) rather than a bare
|
|
251
|
+
// command), or — failing that — the last assistant message, rather than a bare
|
|
252
|
+
// "↳ Bash" or a content-free "thinking…". This is what stops a trailing thinking
|
|
253
|
+
// block from masking the real turn (the defect behind the "Thinking…" panel).
|
|
181
254
|
const previewSource = !last
|
|
182
255
|
? undefined
|
|
183
256
|
: last.type === 'message' || last.type === 'tool_use'
|
|
184
257
|
? last
|
|
185
|
-
: (lastToolUse ?? last);
|
|
258
|
+
: (lastToolUse ?? lastMsg ?? last);
|
|
259
|
+
// Last few assistant turns, most-recent last — context for the decision panel.
|
|
260
|
+
const tail = meaningful
|
|
261
|
+
.filter(e => e.type === 'message' && e.role === 'assistant' && e.content)
|
|
262
|
+
.slice(-3)
|
|
263
|
+
.map(e => oneLine(e.content ?? ''))
|
|
264
|
+
.filter(Boolean);
|
|
186
265
|
const base = {
|
|
187
266
|
activity: 'idle',
|
|
188
267
|
lastRole: lastMsg?.role,
|
|
189
268
|
lastEventKind: last?.type,
|
|
190
269
|
lastActivityMs: ctx.mtimeMs,
|
|
191
270
|
preview: previewSource ? describeEvent(previewSource) : undefined,
|
|
271
|
+
tail: tail.length ? tail : undefined,
|
|
192
272
|
};
|
|
193
273
|
if (!last)
|
|
194
274
|
return base;
|
|
@@ -196,12 +276,15 @@ export function inferActivity(events, ctx = {}) {
|
|
|
196
276
|
// tool and nothing has come after it.
|
|
197
277
|
const lastPlanOrAsk = lastOf(meaningful, e => e.type === 'tool_use' && (e.tool === PLAN_TOOL || e.tool === ASK_TOOL));
|
|
198
278
|
if (lastPlanOrAsk && meaningful.indexOf(lastPlanOrAsk) === meaningful.length - 1) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
279
|
+
if (lastPlanOrAsk.tool === PLAN_TOOL) {
|
|
280
|
+
const question = planReviewQuestion();
|
|
281
|
+
const plan = extractPlanText(lastPlanOrAsk.args);
|
|
282
|
+
return { ...base, activity: 'waiting_input', awaitingReason: 'plan_review', preview: question.text, question, plan };
|
|
283
|
+
}
|
|
284
|
+
// AskUserQuestion: surface the real question + options (they're on `args`),
|
|
285
|
+
// not the generic "Asked you a question" that discarded them.
|
|
286
|
+
const question = structuredQuestionFromAsk(lastPlanOrAsk.args) ?? { text: 'Asked you a question', reason: 'question' };
|
|
287
|
+
return { ...base, activity: 'waiting_input', awaitingReason: 'question', preview: question.text, question };
|
|
205
288
|
}
|
|
206
289
|
// Pending tool call (tool_use with no following tool_result): mid-turn.
|
|
207
290
|
if (last.type === 'tool_use') {
|
|
@@ -209,7 +292,7 @@ export function inferActivity(events, ctx = {}) {
|
|
|
209
292
|
return { ...base, activity: 'working' };
|
|
210
293
|
// Alive but the file hasn't moved — likely blocked on a permission prompt.
|
|
211
294
|
if (ctx.pidAlive)
|
|
212
|
-
return { ...base, activity: 'waiting_input', awaitingReason: 'permission' };
|
|
295
|
+
return { ...base, activity: 'waiting_input', awaitingReason: 'permission', question: permissionQuestion(base.preview) };
|
|
213
296
|
return { ...base, activity: 'idle' };
|
|
214
297
|
}
|
|
215
298
|
// Thinking or a tool result just landed → agent is mid-turn if recently active.
|
|
@@ -223,8 +306,14 @@ export function inferActivity(events, ctx = {}) {
|
|
|
223
306
|
return { ...base, activity: canWork ? 'working' : 'idle' };
|
|
224
307
|
}
|
|
225
308
|
// Assistant spoke last and stopped. A trailing question → waiting; else idle.
|
|
226
|
-
|
|
227
|
-
|
|
309
|
+
// A prose question takes a free-text reply (no select-list), so no options/keys.
|
|
310
|
+
// Unlike the structural plan/ask signals above, the prose heuristic DECAYS: a
|
|
311
|
+
// question nobody answered within PROSE_QUESTION_FRESH_MS is a session that
|
|
312
|
+
// ended, not one that needs you (RUSH-1522). Unknown mtime keeps the question.
|
|
313
|
+
const questionFresh = ctx.mtimeMs == null || Date.now() - ctx.mtimeMs < PROSE_QUESTION_FRESH_MS;
|
|
314
|
+
if (questionFresh && looksLikeQuestion(last.content ?? '')) {
|
|
315
|
+
const text = oneLine(last.content ?? '');
|
|
316
|
+
return { ...base, activity: 'waiting_input', awaitingReason: 'question', question: { text, reason: 'question' } };
|
|
228
317
|
}
|
|
229
318
|
return { ...base, activity: 'idle' };
|
|
230
319
|
}
|
|
@@ -97,6 +97,15 @@ export interface SessionMeta {
|
|
|
97
97
|
* `isTeamOrigin` (which marks sessions spawned BY a team).
|
|
98
98
|
*/
|
|
99
99
|
spawnedTeam?: string;
|
|
100
|
+
/**
|
|
101
|
+
* The plan markdown from the LAST `ExitPlanMode` tool call in the transcript
|
|
102
|
+
* (Claude sessions only), captured at scan time. Present whenever the session
|
|
103
|
+
* ever entered plan-review; consumers can pair it with a live
|
|
104
|
+
* `awaitingReason === 'plan_review'` to decide whether it is still pending.
|
|
105
|
+
* Fills the gap that forced the Factory extension to re-read raw JSONL to
|
|
106
|
+
* recover the plan text — the CLI now carries it on the metadata row.
|
|
107
|
+
*/
|
|
108
|
+
plan?: string;
|
|
100
109
|
/**
|
|
101
110
|
* True when the session was spawned programmatically (SDK entrypoint) rather
|
|
102
111
|
* than by a human at the Claude CLI. Captured at scan time from the JSONL
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export interface ConflictInfo {
|
|
|
77
77
|
* top-level entry add/remove — deep edits to plugin contents won't
|
|
78
78
|
* trigger auto-resync, run `agents sync` for that.
|
|
79
79
|
*/
|
|
80
|
-
export declare const SHIM_SCHEMA_VERSION =
|
|
80
|
+
export declare const SHIM_SCHEMA_VERSION = 26;
|
|
81
81
|
/**
|
|
82
82
|
* Generate the full bash shim script for the given agent. The returned string
|
|
83
83
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
|
@@ -145,8 +145,14 @@ export declare function removeShim(agent: AgentId): boolean;
|
|
|
145
145
|
* editor opens the script and the agent never launches. The `.cmd` is
|
|
146
146
|
* now the only Windows artifact and carries this alias marker so
|
|
147
147
|
* staleness checks read it directly.
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
* v13 — grok aliases resolve the binary from the versioned home's
|
|
149
|
+
* .grok/downloads first, then fall back to the global ~/.grok/downloads.
|
|
150
|
+
* The old template checked only the global dir, so a `grok@<version>`
|
|
151
|
+
* alias failed with "not installed" whenever the binary was staged into
|
|
152
|
+
* the versioned home (installer run with GROK_HOME set, or grok
|
|
153
|
+
* self-update under the shim).
|
|
154
|
+
*/
|
|
155
|
+
export declare const VERSIONED_ALIAS_SCHEMA_VERSION = 13;
|
|
150
156
|
/**
|
|
151
157
|
* Generate a versioned alias script that directly execs a specific version.
|
|
152
158
|
* e.g., claude@2.0.65 -> directly runs that version's binary
|
package/dist/lib/shims.js
CHANGED
|
@@ -214,7 +214,14 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
214
214
|
// v25 — dispatcher self-recovery: if the baked AGENTS_BIN is gone (a removed/moved
|
|
215
215
|
// dev build that generated the shim), resolve `agents` on PATH instead of
|
|
216
216
|
// exiting 127, so a stale/vanished dev build can't brick every launch.
|
|
217
|
-
|
|
217
|
+
// v26 — grok resolves its binary from the versioned home's .grok/downloads first
|
|
218
|
+
// ($VERSION_DIR/home/.grok/downloads, where the binary lands when the
|
|
219
|
+
// installer runs with GROK_HOME set or grok self-updates under the shim),
|
|
220
|
+
// then falls back to the global ~/.grok/downloads for pre-fix installs.
|
|
221
|
+
// The old dispatcher checked only the global dir, so a pinned grok that
|
|
222
|
+
// installed into the versioned home fell through to the "not installed"
|
|
223
|
+
// error.
|
|
224
|
+
export const SHIM_SCHEMA_VERSION = 26;
|
|
218
225
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
219
226
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
220
227
|
function shellQuote(value) {
|
|
@@ -468,11 +475,14 @@ fi
|
|
|
468
475
|
|
|
469
476
|
VERSION_DIR="$AGENTS_USER_DIR/.history/versions/$AGENT/$VERSION"
|
|
470
477
|
|
|
471
|
-
# Grok special case: binary lives in
|
|
472
|
-
#
|
|
478
|
+
# Grok special case: binary lives in the versioned home's .grok/downloads (or,
|
|
479
|
+
# for pre-fix installs, the global ~/.grok/downloads), not node_modules. We
|
|
480
|
+
# still use the agents-cli version dir purely for GROK_HOME isolation.
|
|
473
481
|
if [ "$AGENT" = "grok" ]; then
|
|
474
|
-
#
|
|
475
|
-
|
|
482
|
+
# Check the versioned home first — this is where the binary lands when the
|
|
483
|
+
# installer runs with GROK_HOME set (i.e. via the shim or a correct
|
|
484
|
+
# \`agents add grok\`), or when grok self-updates from within the shim.
|
|
485
|
+
GROK_DOWNLOADS="$VERSION_DIR/home/.grok/downloads"
|
|
476
486
|
if [ -d "$GROK_DOWNLOADS" ]; then
|
|
477
487
|
# Prefer a binary whose filename contains the exact version
|
|
478
488
|
BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | grep -i "$VERSION" | head -1)
|
|
@@ -481,6 +491,17 @@ if [ "$AGENT" = "grok" ]; then
|
|
|
481
491
|
BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | head -1)
|
|
482
492
|
fi
|
|
483
493
|
fi
|
|
494
|
+
# Fall back to the global grok home (binary installed without GROK_HOME set,
|
|
495
|
+
# e.g. an earlier \`agents add grok@latest\` before this resolution fix).
|
|
496
|
+
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
497
|
+
GROK_DOWNLOADS="$HOME/.grok/downloads"
|
|
498
|
+
if [ -d "$GROK_DOWNLOADS" ]; then
|
|
499
|
+
BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | grep -i "$VERSION" | head -1)
|
|
500
|
+
if [ -z "$BINARY" ]; then
|
|
501
|
+
BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | head -1)
|
|
502
|
+
fi
|
|
503
|
+
fi
|
|
504
|
+
fi
|
|
484
505
|
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
485
506
|
# Last resort: the adopted native launcher (recorded absolute path) if we
|
|
486
507
|
# adopted grok, else whatever is on PATH. Prefer the adopted record — after
|
|
@@ -744,8 +765,14 @@ export function removeShim(agent) {
|
|
|
744
765
|
* editor opens the script and the agent never launches. The `.cmd` is
|
|
745
766
|
* now the only Windows artifact and carries this alias marker so
|
|
746
767
|
* staleness checks read it directly.
|
|
768
|
+
* v13 — grok aliases resolve the binary from the versioned home's
|
|
769
|
+
* .grok/downloads first, then fall back to the global ~/.grok/downloads.
|
|
770
|
+
* The old template checked only the global dir, so a `grok@<version>`
|
|
771
|
+
* alias failed with "not installed" whenever the binary was staged into
|
|
772
|
+
* the versioned home (installer run with GROK_HOME set, or grok
|
|
773
|
+
* self-update under the shim).
|
|
747
774
|
*/
|
|
748
|
-
export const VERSIONED_ALIAS_SCHEMA_VERSION =
|
|
775
|
+
export const VERSIONED_ALIAS_SCHEMA_VERSION = 13;
|
|
749
776
|
/** Internal marker string used to embed the schema version in versioned alias scripts. */
|
|
750
777
|
const VERSIONED_ALIAS_VERSION_MARKER = 'agents-versioned-alias-version:';
|
|
751
778
|
// The version string is interpolated into a generated bash script and into
|
|
@@ -822,13 +849,22 @@ export KIMI_CODE_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/
|
|
|
822
849
|
// "agents __shim" which resolves via getBinaryPath() instead.
|
|
823
850
|
const versionDir = `$HOME/.agents/.history/versions/${agent}/${version}`;
|
|
824
851
|
const binaryResolution = agent === 'grok'
|
|
825
|
-
? `# Grok ships its native binary in
|
|
826
|
-
|
|
852
|
+
? `# Grok ships its native binary in the versioned home's .grok/downloads (or,
|
|
853
|
+
# for pre-fix installs, the global ~/.grok/downloads), not node_modules.
|
|
854
|
+
GROK_DOWNLOADS="${versionDir}/home/.grok/downloads"
|
|
827
855
|
BINARY=""
|
|
828
856
|
if [ -d "$GROK_DOWNLOADS" ]; then
|
|
829
857
|
BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | grep -i "${version}" | head -1)
|
|
830
858
|
[ -n "$BINARY" ] || BINARY=$(ls "$GROK_DOWNLOADS"/grok-* 2>/dev/null | head -1)
|
|
831
859
|
fi
|
|
860
|
+
# Fall back to the global grok home (binary installed without GROK_HOME set).
|
|
861
|
+
if [ -z "$BINARY" ] || [ ! -x "$BINARY" ]; then
|
|
862
|
+
GROK_GLOBAL_DOWNLOADS="$HOME/.grok/downloads"
|
|
863
|
+
if [ -d "$GROK_GLOBAL_DOWNLOADS" ]; then
|
|
864
|
+
BINARY=$(ls "$GROK_GLOBAL_DOWNLOADS"/grok-* 2>/dev/null | grep -i "${version}" | head -1)
|
|
865
|
+
[ -n "$BINARY" ] || BINARY=$(ls "$GROK_GLOBAL_DOWNLOADS"/grok-* 2>/dev/null | head -1)
|
|
866
|
+
fi
|
|
867
|
+
fi
|
|
832
868
|
# Refuse a PATH match under our own shims dir — it resolves to this alias's
|
|
833
869
|
# sibling dispatcher shim (shims dir is ahead of ~/.local/bin on PATH) and
|
|
834
870
|
# re-execs forever. Fall through to the clean "not installed" error instead.
|
package/dist/lib/ssh-tunnel.d.ts
CHANGED
|
@@ -50,10 +50,41 @@ export declare const REMOTE_TASK_NAME = "AgentsComputerHelper";
|
|
|
50
50
|
/** Basename of the cross-published exe under native/computer-win/dist. */
|
|
51
51
|
export declare const WIN_HELPER_EXE = "computer-helper-win.exe";
|
|
52
52
|
/**
|
|
53
|
-
* Locate
|
|
54
|
-
*
|
|
53
|
+
* Locate a locally built Windows daemon exe — a repo-checkout build output from
|
|
54
|
+
* `scripts/build-win.sh`, or one bundled next to the package. Local paths are
|
|
55
|
+
* only the first candidate; npm-installed CLIs have neither and fall through to
|
|
56
|
+
* the release-asset download (`ensureWinHelperExe`).
|
|
55
57
|
*/
|
|
56
58
|
export declare function resolveWinHelperExe(): string | null;
|
|
59
|
+
/** GitHub repo whose `v<version>` releases carry the exe as an asset. */
|
|
60
|
+
export declare const WIN_HELPER_RELEASE_REPO = "phnx-labs/agents-cli";
|
|
61
|
+
/** Cache dir for downloaded helper exes, one subdir per release tag. */
|
|
62
|
+
export declare function winHelperCacheDir(version: string): string;
|
|
63
|
+
/** Release-asset URLs for the exe + its checksum at one exact `v<version>` tag. */
|
|
64
|
+
export declare function winHelperAssetUrls(version: string): {
|
|
65
|
+
exe: string;
|
|
66
|
+
sha256: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Parse the published `.sha256` asset — `sha256sum` format (`<hex> <name>`) or
|
|
70
|
+
* a bare hex digest. Throws on anything that does not lead with 64 hex chars.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseSha256Asset(text: string): string;
|
|
73
|
+
/** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
|
|
74
|
+
export declare function sha256File(file: string): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Download the exe release asset for this CLI version, verify its sha256
|
|
77
|
+
* against the published `.sha256` asset, and cache it under the agents cache
|
|
78
|
+
* dir. Only the exact `v<version>` tag is consulted — a missing asset is a
|
|
79
|
+
* hard error naming that tag, never a silent fallback to another release.
|
|
80
|
+
*/
|
|
81
|
+
export declare function downloadWinHelperExe(version: string): Promise<string>;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the helper exe for `setup --host`: local build outputs first (repo
|
|
84
|
+
* checkout / bundled), then the checksum-verified release-asset download for
|
|
85
|
+
* the running CLI version. Throws with the tag it checked when neither exists.
|
|
86
|
+
*/
|
|
87
|
+
export declare function ensureWinHelperExe(version?: string): Promise<string>;
|
|
57
88
|
/** Persisted per-device tunnel state so verbs can reconnect after `start --host`. */
|
|
58
89
|
export interface RemoteTunnelState {
|
|
59
90
|
device: string;
|
package/dist/lib/ssh-tunnel.js
CHANGED
|
@@ -21,7 +21,9 @@ import * as net from 'net';
|
|
|
21
21
|
import * as fs from 'fs';
|
|
22
22
|
import * as path from 'path';
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
|
-
import { randomBytes } from 'crypto';
|
|
24
|
+
import { randomBytes, createHash } from 'crypto';
|
|
25
|
+
import { Readable } from 'stream';
|
|
26
|
+
import { pipeline } from 'stream/promises';
|
|
25
27
|
import { sshExec, SSH_OPTS } from './ssh-exec.js';
|
|
26
28
|
import { backgroundSpawnOptions } from './platform/process.js';
|
|
27
29
|
import { encodePowerShell } from './browser/drivers/ssh.js';
|
|
@@ -29,6 +31,7 @@ import { getDevice } from './devices/registry.js';
|
|
|
29
31
|
import { sshTargetFor } from './devices/connect.js';
|
|
30
32
|
import { hostNameFor } from './devices/ssh-config.js';
|
|
31
33
|
import { getCacheDir } from './state.js';
|
|
34
|
+
import { getCliVersion } from './version.js';
|
|
32
35
|
import { openComputerClient, resolveTcpEndpoint } from './computer-rpc.js';
|
|
33
36
|
/** Build the ssh argv (after the `ssh` program name) for an `-L` tunnel. Pure.
|
|
34
37
|
*
|
|
@@ -89,8 +92,10 @@ export const REMOTE_TASK_NAME = 'AgentsComputerHelper';
|
|
|
89
92
|
/** Basename of the cross-published exe under native/computer-win/dist. */
|
|
90
93
|
export const WIN_HELPER_EXE = 'computer-helper-win.exe';
|
|
91
94
|
/**
|
|
92
|
-
* Locate
|
|
93
|
-
*
|
|
95
|
+
* Locate a locally built Windows daemon exe — a repo-checkout build output from
|
|
96
|
+
* `scripts/build-win.sh`, or one bundled next to the package. Local paths are
|
|
97
|
+
* only the first candidate; npm-installed CLIs have neither and fall through to
|
|
98
|
+
* the release-asset download (`ensureWinHelperExe`).
|
|
94
99
|
*/
|
|
95
100
|
export function resolveWinHelperExe() {
|
|
96
101
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -106,6 +111,89 @@ export function resolveWinHelperExe() {
|
|
|
106
111
|
}
|
|
107
112
|
return null;
|
|
108
113
|
}
|
|
114
|
+
/** GitHub repo whose `v<version>` releases carry the exe as an asset. */
|
|
115
|
+
export const WIN_HELPER_RELEASE_REPO = 'phnx-labs/agents-cli';
|
|
116
|
+
/** Cache dir for downloaded helper exes, one subdir per release tag. */
|
|
117
|
+
export function winHelperCacheDir(version) {
|
|
118
|
+
return path.join(getCacheDir(), 'computer', 'win-helper', `v${version}`);
|
|
119
|
+
}
|
|
120
|
+
/** Release-asset URLs for the exe + its checksum at one exact `v<version>` tag. */
|
|
121
|
+
export function winHelperAssetUrls(version) {
|
|
122
|
+
const base = `https://github.com/${WIN_HELPER_RELEASE_REPO}/releases/download/v${version}`;
|
|
123
|
+
return { exe: `${base}/${WIN_HELPER_EXE}`, sha256: `${base}/${WIN_HELPER_EXE}.sha256` };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse the published `.sha256` asset — `sha256sum` format (`<hex> <name>`) or
|
|
127
|
+
* a bare hex digest. Throws on anything that does not lead with 64 hex chars.
|
|
128
|
+
*/
|
|
129
|
+
export function parseSha256Asset(text) {
|
|
130
|
+
const m = text.trim().match(/^([A-Fa-f0-9]{64})(\s|$)/);
|
|
131
|
+
if (!m)
|
|
132
|
+
throw new Error(`malformed .sha256 release asset: ${JSON.stringify(text.slice(0, 80))}`);
|
|
133
|
+
return m[1].toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
/** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
|
|
136
|
+
export function sha256File(file) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const hash = createHash('sha256');
|
|
139
|
+
fs.createReadStream(file)
|
|
140
|
+
.on('error', reject)
|
|
141
|
+
.on('data', (d) => hash.update(d))
|
|
142
|
+
.on('end', () => resolve(hash.digest('hex')));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Download the exe release asset for this CLI version, verify its sha256
|
|
147
|
+
* against the published `.sha256` asset, and cache it under the agents cache
|
|
148
|
+
* dir. Only the exact `v<version>` tag is consulted — a missing asset is a
|
|
149
|
+
* hard error naming that tag, never a silent fallback to another release.
|
|
150
|
+
*/
|
|
151
|
+
export async function downloadWinHelperExe(version) {
|
|
152
|
+
const cached = path.join(winHelperCacheDir(version), WIN_HELPER_EXE);
|
|
153
|
+
if (fs.existsSync(cached))
|
|
154
|
+
return cached;
|
|
155
|
+
const tag = `v${version}`;
|
|
156
|
+
const { exe: exeUrl, sha256: shaUrl } = winHelperAssetUrls(version);
|
|
157
|
+
const missing = (status, url) => new Error(`no ${WIN_HELPER_EXE} release asset for tag ${tag} (HTTP ${status} on ${url}). ` +
|
|
158
|
+
`The Windows helper ships as a GitHub release asset per tagged CLI version; ` +
|
|
159
|
+
`from a repo checkout you can build it locally instead: bash scripts/build-win.sh`);
|
|
160
|
+
// Checksum first: it is tiny and 404s fast when the tag has no assets.
|
|
161
|
+
const shaRes = await fetch(shaUrl, { signal: AbortSignal.timeout(30_000) });
|
|
162
|
+
if (!shaRes.ok)
|
|
163
|
+
throw missing(shaRes.status, shaUrl);
|
|
164
|
+
const expected = parseSha256Asset(await shaRes.text());
|
|
165
|
+
console.error(`Downloading ${WIN_HELPER_EXE} ${tag} from GitHub releases (~160 MB)...`);
|
|
166
|
+
const exeRes = await fetch(exeUrl, { signal: AbortSignal.timeout(15 * 60_000) });
|
|
167
|
+
if (!exeRes.ok || !exeRes.body)
|
|
168
|
+
throw missing(exeRes.status, exeUrl);
|
|
169
|
+
fs.mkdirSync(path.dirname(cached), { recursive: true });
|
|
170
|
+
// Stream to a partial file and rename only after the checksum passes, so an
|
|
171
|
+
// interrupted download can never be picked up as a valid cache hit.
|
|
172
|
+
const partial = `${cached}.download`;
|
|
173
|
+
try {
|
|
174
|
+
await pipeline(Readable.fromWeb(exeRes.body), fs.createWriteStream(partial));
|
|
175
|
+
const actual = await sha256File(partial);
|
|
176
|
+
if (actual !== expected) {
|
|
177
|
+
throw new Error(`sha256 mismatch for ${exeUrl}: expected ${expected}, got ${actual}`);
|
|
178
|
+
}
|
|
179
|
+
fs.renameSync(partial, cached);
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
fs.rmSync(partial, { force: true });
|
|
183
|
+
}
|
|
184
|
+
return cached;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the helper exe for `setup --host`: local build outputs first (repo
|
|
188
|
+
* checkout / bundled), then the checksum-verified release-asset download for
|
|
189
|
+
* the running CLI version. Throws with the tag it checked when neither exists.
|
|
190
|
+
*/
|
|
191
|
+
export async function ensureWinHelperExe(version = getCliVersion()) {
|
|
192
|
+
const local = resolveWinHelperExe();
|
|
193
|
+
if (local)
|
|
194
|
+
return local;
|
|
195
|
+
return downloadWinHelperExe(version);
|
|
196
|
+
}
|
|
109
197
|
function remoteStateDir() {
|
|
110
198
|
return path.join(getCacheDir(), 'computer', 'remote');
|
|
111
199
|
}
|
|
@@ -249,10 +337,9 @@ function copyFileOverScp(target, remotePath, filePath, timeoutMs = 600_000) {
|
|
|
249
337
|
*/
|
|
250
338
|
export async function setupRemoteHelper(name) {
|
|
251
339
|
const { target } = await resolveRemoteDevice(name);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
340
|
+
// Local build output, else the checksum-verified GitHub release asset for
|
|
341
|
+
// this CLI version. Throws naming the tag it checked when neither exists.
|
|
342
|
+
const exe = await ensureWinHelperExe();
|
|
256
343
|
const prep = sshExec(target, encodePowerShell(buildPushScript()), { timeoutMs: 60_000 });
|
|
257
344
|
if (prep.code !== 0) {
|
|
258
345
|
throw new Error(`preparing helper exe path on '${name}' failed (exit ${prep.code ?? 'null'}): ${prep.stderr.trim() || prep.stdout.trim()}`);
|
|
@@ -83,6 +83,7 @@ export declare const loadSessions: ModuleLoader;
|
|
|
83
83
|
export declare const loadTeams: ModuleLoader;
|
|
84
84
|
export declare const loadCloud: ModuleLoader;
|
|
85
85
|
export declare const loadMessage: ModuleLoader;
|
|
86
|
+
export declare const loadFeed: ModuleLoader;
|
|
86
87
|
export declare const loadServe: ModuleLoader;
|
|
87
88
|
export declare const loadAudit: ModuleLoader;
|
|
88
89
|
/**
|
|
@@ -61,6 +61,7 @@ export const loadSessions = async () => (await import('../../commands/sessions.j
|
|
|
61
61
|
export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
|
|
62
62
|
export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
|
|
63
63
|
export const loadMessage = async () => (await import('../../commands/message.js')).registerMessageCommand;
|
|
64
|
+
export const loadFeed = async () => (await import('../../commands/feed.js')).registerFeedCommand;
|
|
64
65
|
export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
|
|
65
66
|
export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
|
|
66
67
|
/**
|
|
@@ -154,6 +155,7 @@ export const COMMAND_LOADERS = {
|
|
|
154
155
|
teams: [loadTeams],
|
|
155
156
|
cloud: [loadCloud],
|
|
156
157
|
message: [loadMessage],
|
|
158
|
+
feed: [loadFeed],
|
|
157
159
|
serve: [loadServe],
|
|
158
160
|
audit: [loadAudit],
|
|
159
161
|
};
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -122,6 +122,8 @@ export declare function getProjectRoutinesDir(cwd?: string): string | null;
|
|
|
122
122
|
export declare function getRunsDir(): string;
|
|
123
123
|
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
124
124
|
export declare function getMailboxRootDir(): string;
|
|
125
|
+
/** Root for open-block feed records (~/.agents/.history/feed/). */
|
|
126
|
+
export declare function getFeedDir(): string;
|
|
125
127
|
/** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
|
|
126
128
|
export declare function getVersionsDir(): string;
|
|
127
129
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|
package/dist/lib/state.js
CHANGED
|
@@ -89,6 +89,7 @@ const TEAMS_AGENTS_DIR = path.join(HISTORY_DIR, 'teams', 'agents');
|
|
|
89
89
|
const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
|
|
90
90
|
const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
|
|
91
91
|
const MAILBOX_DIR = path.join(HISTORY_DIR, 'mailbox');
|
|
92
|
+
const FEED_DIR = path.join(HISTORY_DIR, 'feed');
|
|
92
93
|
// Cache bucket (regenerable).
|
|
93
94
|
const SHIMS_DIR = path.join(CACHE_DIR, 'shims');
|
|
94
95
|
const HOOK_SHIMS_DIR = path.join(SHIMS_DIR, 'hooks');
|
|
@@ -313,6 +314,8 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
|
|
|
313
314
|
export function getRunsDir() { return RUNS_DIR; }
|
|
314
315
|
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
315
316
|
export function getMailboxRootDir() { return MAILBOX_DIR; }
|
|
317
|
+
/** Root for open-block feed records (~/.agents/.history/feed/). */
|
|
318
|
+
export function getFeedDir() { return FEED_DIR; }
|
|
316
319
|
/** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
|
|
317
320
|
export function getVersionsDir() { return VERSIONS_DIR; }
|
|
318
321
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|
|
@@ -569,25 +572,35 @@ function writeIfChanged(filePath, content) {
|
|
|
569
572
|
/**
|
|
570
573
|
* Partition the in-memory Meta across three files by sync-domain:
|
|
571
574
|
* - central `~/.agents/agents.yaml` — portable, everything else
|
|
572
|
-
* - device `~/.agents/devices/<machine>/agents.yaml` — `agents:` pins
|
|
575
|
+
* - device `~/.agents/devices/<machine>/agents.yaml` — `agents:` pins +
|
|
576
|
+
* `defaultBrowserProfile:` (both per-device)
|
|
573
577
|
* - history `~/.agents/.history/version-resources.json` — `versions:` (machine-local)
|
|
574
578
|
* All callers funnel through writeMeta → here, so nothing else changes. Empty
|
|
575
579
|
* `agents:` / `versions:` are not written (no empty committed files).
|
|
576
580
|
*/
|
|
577
581
|
function writeMetaUnlocked(meta) {
|
|
578
|
-
const { agents, versions, ...central } = meta;
|
|
582
|
+
const { agents, versions, defaultBrowserProfile, ...central } = meta;
|
|
579
583
|
// Write the machine-local files FIRST, then strip central — so a crash mid-write
|
|
580
584
|
// never removes pins/versions from central before they're persisted elsewhere.
|
|
581
585
|
const devicePath = getDeviceMetaPath();
|
|
582
|
-
|
|
586
|
+
const hasAgents = !!agents && Object.keys(agents).length > 0;
|
|
587
|
+
const hasDefaultBrowser = !!defaultBrowserProfile;
|
|
588
|
+
if (hasAgents || hasDefaultBrowser) {
|
|
589
|
+
// Device-local doc carries `agents:` pins and `defaultBrowserProfile:` — both
|
|
590
|
+
// are per-machine and must never land in central agents.yaml (which syncs).
|
|
591
|
+
const deviceDoc = {};
|
|
592
|
+
if (hasAgents)
|
|
593
|
+
deviceDoc.agents = agents;
|
|
594
|
+
if (hasDefaultBrowser)
|
|
595
|
+
deviceDoc.defaultBrowserProfile = defaultBrowserProfile;
|
|
583
596
|
fs.mkdirSync(path.dirname(devicePath), { recursive: true });
|
|
584
|
-
writeIfChanged(devicePath, META_HEADER + yaml.stringify(
|
|
597
|
+
writeIfChanged(devicePath, META_HEADER + yaml.stringify(deviceDoc));
|
|
585
598
|
}
|
|
586
599
|
else if (fs.existsSync(devicePath)) {
|
|
587
|
-
// Every
|
|
588
|
-
// write — otherwise the stale device file survives and
|
|
589
|
-
// re-applies the removed pin on the next read,
|
|
590
|
-
// (e.g. a
|
|
600
|
+
// Every device-local value was cleared. Persist the emptied doc instead of
|
|
601
|
+
// skipping the write — otherwise the stale device file survives and
|
|
602
|
+
// overlayMachineLocal re-applies the removed pin/default on the next read,
|
|
603
|
+
// leaving a dangling value (e.g. a default pointing at a deleted profile).
|
|
591
604
|
writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents: {} }));
|
|
592
605
|
}
|
|
593
606
|
if (versions && Object.keys(versions).length > 0) {
|
|
@@ -602,6 +615,8 @@ function writeMetaUnlocked(meta) {
|
|
|
602
615
|
* Overlay this machine's local state onto a central-portable Meta:
|
|
603
616
|
* - `agents:` from the device file (device wins; the union both preserves the
|
|
604
617
|
* one-level merge and self-heals a pre-migration central that still has pins)
|
|
618
|
+
* - `defaultBrowserProfile:` from the device file (device is the sole source;
|
|
619
|
+
* the field is stripped from central on write, so nothing to merge against)
|
|
605
620
|
* - `versions:` from the history JSON (wholesale replace; falls back to
|
|
606
621
|
* whatever central carried when the history file doesn't exist yet)
|
|
607
622
|
*/
|
|
@@ -612,6 +627,8 @@ function overlayMachineLocal(meta) {
|
|
|
612
627
|
const dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
|
|
613
628
|
if (dm?.agents)
|
|
614
629
|
meta.agents = { ...meta.agents, ...dm.agents };
|
|
630
|
+
if (dm?.defaultBrowserProfile)
|
|
631
|
+
meta.defaultBrowserProfile = dm.defaultBrowserProfile;
|
|
615
632
|
}
|
|
616
633
|
catch { /* ignore malformed device file */ }
|
|
617
634
|
}
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -1693,6 +1693,7 @@ export class AgentManager {
|
|
|
1693
1693
|
];
|
|
1694
1694
|
if (model)
|
|
1695
1695
|
args.push('--model', model);
|
|
1696
|
+
args.push('--env', 'AGENTS_RUNTIME=teams');
|
|
1696
1697
|
return args;
|
|
1697
1698
|
}
|
|
1698
1699
|
buildCommand(agentType, prompt, mode, model, cwd = null, sessionId = null, effort = 'medium', version = null, profileName = null) {
|
|
@@ -1704,9 +1705,11 @@ export class AgentManager {
|
|
|
1704
1705
|
];
|
|
1705
1706
|
if (cwd)
|
|
1706
1707
|
cmd.push('--cwd', cwd);
|
|
1707
|
-
// Pin
|
|
1708
|
-
//
|
|
1709
|
-
|
|
1708
|
+
// Pin the session UUID to our agent_id so buildExecEnv keys
|
|
1709
|
+
// AGENTS_MAILBOX_DIR by the same id mailboxIdForActiveSession returns.
|
|
1710
|
+
// Claude also forwards --session-id to its CLI (unified identity);
|
|
1711
|
+
// other agents ignore the flag but still get the correct mailbox dir.
|
|
1712
|
+
if (sessionId) {
|
|
1710
1713
|
cmd.push('--session-id', sessionId);
|
|
1711
1714
|
}
|
|
1712
1715
|
// Claude: grant access to the teammate's working directory.
|