@phnx-labs/agents-cli 1.21.2 → 1.21.3
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 +34 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.d.ts +4 -0
- package/dist/commands/computer-actions.js +34 -0
- package/dist/commands/computer.js +4 -2
- package/dist/commands/perf.d.ts +16 -0
- package/dist/commands/perf.js +11 -1
- package/dist/commands/projects.d.ts +11 -1
- package/dist/commands/projects.js +16 -2
- package/dist/commands/sessions-picker.js +17 -2
- package/dist/commands/sessions.d.ts +5 -0
- package/dist/commands/sessions.js +14 -0
- package/dist/index.js +8 -0
- package/dist/lib/browser/service.js +13 -0
- package/dist/lib/computer/dispatch.d.ts +3 -1
- package/dist/lib/computer/dispatch.js +10 -2
- package/dist/lib/event-stream.d.ts +2 -0
- package/dist/lib/event-stream.js +3 -0
- package/dist/lib/events.d.ts +3 -1
- package/dist/lib/events.js +4 -2
- package/dist/lib/git.d.ts +14 -0
- package/dist/lib/git.js +36 -0
- package/dist/lib/hooks/profile.js +1 -14
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/percentile.d.ts +12 -0
- package/dist/lib/percentile.js +24 -0
- package/dist/lib/perf/db.d.ts +1 -2
- package/dist/lib/perf/db.js +2 -14
- package/dist/lib/plugins.js +12 -1
- package/dist/lib/resources.d.ts +16 -0
- package/dist/lib/resources.js +25 -14
- package/dist/lib/routines.js +1 -14
- package/dist/lib/runner.js +8 -1
- 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/session/db.d.ts +16 -1
- package/dist/lib/session/db.js +227 -4
- package/dist/lib/session/discover.d.ts +28 -0
- package/dist/lib/session/discover.js +42 -1
- package/dist/lib/session/highlights.d.ts +24 -4
- package/dist/lib/session/highlights.js +52 -7
- package/dist/lib/session/parse.js +19 -3
- package/dist/lib/session/prompt.d.ts +19 -0
- package/dist/lib/session/prompt.js +43 -0
- package/dist/lib/session/types.d.ts +42 -0
- package/dist/lib/types.d.ts +14 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.21.3
|
|
4
|
+
|
|
5
|
+
- **`agents projects import --from-factory` stops printing raw git errors.** Reading each
|
|
6
|
+
checkout's real remote is done per registry row, and a checkout with no `origin` makes git
|
|
7
|
+
write `error: No such remote 'origin'` straight to the terminal — its own stderr, which the
|
|
8
|
+
surrounding try/catch never sees. Importing 12 rows printed two of them between the progress
|
|
9
|
+
lines. The probe now discards git's stderr; an absent remote is an expected answer, not
|
|
10
|
+
something to report. Source: `apps/cli/src/commands/projects.ts`.
|
|
11
|
+
|
|
12
|
+
- **Sessions now track browser/computer tool use and skill/plugin/slash-command
|
|
13
|
+
usage, queryable with `agents sessions --skill <name>` / `--plugin <name>`.**
|
|
14
|
+
`browser.navigate`, `browser.screenshot`, and a new `computer.action` event
|
|
15
|
+
fire on every `agents browser`/`agents computer` action, carrying session
|
|
16
|
+
identity for free. The sessions index persists `usedBrowser`/`usedComputer`
|
|
17
|
+
(from a scoped events-log read, not a transcript re-scan) and a new
|
|
18
|
+
`session_resource_usage` table records every skill and slash-command
|
|
19
|
+
invocation with its owning plugin, source repo, and git commit — resolved
|
|
20
|
+
against `resolveResource()`/`discoverPlugins()` at scan time. The sessions
|
|
21
|
+
picker preview surfaces both as `browser`/`computer` and `Skills:` tags.
|
|
22
|
+
Source: `apps/cli/src/lib/browser/service.ts`, `apps/cli/src/commands/computer-actions.ts`,
|
|
23
|
+
`apps/cli/src/lib/session/db.ts`, `apps/cli/src/lib/session/highlights.ts`,
|
|
24
|
+
`apps/cli/src/lib/session/discover.ts`, `apps/cli/src/commands/sessions.ts`.
|
|
25
|
+
- **`ResolvedResource` and `DiscoveredPlugin` carry provenance: `repoRoot` and a
|
|
26
|
+
lazily-resolved `snapshotSha`.** Every resource/plugin resolution can now
|
|
27
|
+
answer "which DotAgents repo, which commit" without an extra lookup; the git
|
|
28
|
+
shell-out is memoized per repo root and only runs when a caller actually
|
|
29
|
+
reads `snapshotSha`. Source: `apps/cli/src/lib/resources.ts`,
|
|
30
|
+
`apps/cli/src/lib/plugins.ts`, `apps/cli/src/lib/git.ts`.
|
|
31
|
+
- **`SessionEvent.slashCommand` captures a typed or model-invoked slash command**
|
|
32
|
+
(both the `<command-name>` wrapper and the `SlashCommand` tool call), and
|
|
33
|
+
`agents sessions`'s perf sample for `command.end` now carries the session id
|
|
34
|
+
and agent instead of being anonymous. Source: `apps/cli/src/lib/session/prompt.ts`,
|
|
35
|
+
`apps/cli/src/lib/session/parse.ts`, `apps/cli/src/index.ts`.
|
|
36
|
+
|
|
3
37
|
## 1.21.2
|
|
4
38
|
|
|
5
39
|
- **`agents trends` — resource and session analytics dashboard.** Baked recipes
|
package/dist/bin/agents
CHANGED
|
Binary file
|
|
@@ -82,4 +82,8 @@ export declare function shouldRaise(opts: {
|
|
|
82
82
|
}): boolean;
|
|
83
83
|
export declare function appPathIsElectron(appPath: string | null, exists?: (p: string) => boolean): boolean;
|
|
84
84
|
export declare function electronWebviewTip(appLabel: string): string;
|
|
85
|
+
export declare function emitComputerAction(verb: string, targetPid: number | undefined, opts: {
|
|
86
|
+
bundle?: string;
|
|
87
|
+
host?: string;
|
|
88
|
+
}, extra?: Record<string, unknown>): void;
|
|
85
89
|
export declare function registerActionCommands(program: Command): void;
|
|
@@ -11,6 +11,7 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { openComputerClient, describeTransport, resolvePolicyPath, } from '../lib/computer-rpc.js';
|
|
13
13
|
import { COMPUTER_INPUT_GATED_VERBS, formatComputerPermissionGrantHint, } from '../lib/permissions.js';
|
|
14
|
+
import { emit as emitEvent } from '../lib/events.js';
|
|
14
15
|
function isComputerInputVerb(verb) {
|
|
15
16
|
return typeof verb === 'string' && COMPUTER_INPUT_GATED_VERBS.includes(verb);
|
|
16
17
|
}
|
|
@@ -350,6 +351,22 @@ function emit(result, json, human) {
|
|
|
350
351
|
console.log(human());
|
|
351
352
|
}
|
|
352
353
|
}
|
|
354
|
+
// Record one `computer.action` per verb invocation, one call site per command
|
|
355
|
+
// below. Session/agent/machine identity is stamped for free by emitEvent's
|
|
356
|
+
// provenance floor (events.ts resolveProvenance) — this only carries the
|
|
357
|
+
// action-specific facts: which verb, against which target pid/bundle/host.
|
|
358
|
+
// NOTE: the field is `targetPid`, never `pid` — `pid` is a reserved envelope
|
|
359
|
+
// key (the emitting process's OWN pid, events.ts RESERVED_META_KEYS) that
|
|
360
|
+
// sanitizePayload() silently strips from the payload before it can collide.
|
|
361
|
+
export function emitComputerAction(verb, targetPid, opts, extra = {}) {
|
|
362
|
+
emitEvent('computer.action', {
|
|
363
|
+
command: verb,
|
|
364
|
+
targetPid,
|
|
365
|
+
bundle: opts.bundle,
|
|
366
|
+
host: opts.host,
|
|
367
|
+
...extra,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
353
370
|
// Add the shared --pid/--bundle/--host target options to a verb. `--host` routes
|
|
354
371
|
// the verb at a remote Windows device: the `computer` preAction hook hydrates
|
|
355
372
|
// COMPUTER_HELPER_TCP from the tunnel `start --host` recorded, so withClient's
|
|
@@ -376,6 +393,7 @@ export function registerActionCommands(program) {
|
|
|
376
393
|
await withClient(async (client) => {
|
|
377
394
|
const res = unwrap(await client.call('list_apps'));
|
|
378
395
|
const list = res.apps || [];
|
|
396
|
+
emitComputerAction('apps', undefined, opts);
|
|
379
397
|
emit(res, Boolean(opts.json), () => list.length === 0
|
|
380
398
|
? '(no allow-listed apps running)'
|
|
381
399
|
: list
|
|
@@ -422,6 +440,7 @@ export function registerActionCommands(program) {
|
|
|
422
440
|
if (opts.background)
|
|
423
441
|
params.background = true;
|
|
424
442
|
const res = unwrap(await client.call('click', params));
|
|
443
|
+
emitComputerAction('click', pid, opts, { id: opts.id, count: opts.count });
|
|
425
444
|
emit(res, Boolean(opts.json), () => `clicked (${res.action ?? 'ok'})`);
|
|
426
445
|
});
|
|
427
446
|
});
|
|
@@ -440,6 +459,7 @@ export function registerActionCommands(program) {
|
|
|
440
459
|
}
|
|
441
460
|
await applyFocusPolicy(client, pid, opts);
|
|
442
461
|
const res = unwrap(await client.call('right_click', { pid, ...spec.params }));
|
|
462
|
+
emitComputerAction('right-click', pid, opts, { id: opts.id });
|
|
443
463
|
emit(res, Boolean(opts.json), () => `right-clicked (${res.method ?? 'ok'})`);
|
|
444
464
|
});
|
|
445
465
|
});
|
|
@@ -466,6 +486,8 @@ export function registerActionCommands(program) {
|
|
|
466
486
|
if (opts.allowSecureField)
|
|
467
487
|
params.allow_secure_field = true;
|
|
468
488
|
const res = unwrap(await client.call('type', params));
|
|
489
|
+
// textLength, never the text itself — users type passwords/secrets into fields.
|
|
490
|
+
emitComputerAction('type', pid, opts, { id: opts.id, textLength: opts.text.length, committed: Boolean(res.committed) });
|
|
469
491
|
emit(res, Boolean(opts.json), () => `typed ${opts.text.length} char(s)${res.committed ? ' (committed)' : ''}`);
|
|
470
492
|
});
|
|
471
493
|
});
|
|
@@ -493,6 +515,7 @@ export function registerActionCommands(program) {
|
|
|
493
515
|
params.char_delay_ms = charDelay;
|
|
494
516
|
const res = unwrap(await client.call('type_text', params));
|
|
495
517
|
warnIfNotFrontmost(res);
|
|
518
|
+
emitComputerAction('type-text', pid, opts, { textLength: opts.text.length, committed: Boolean(opts.commit) });
|
|
496
519
|
emit(res, Boolean(opts.json), () => `typed ${res.chars ?? opts.text.length} char(s)`);
|
|
497
520
|
});
|
|
498
521
|
});
|
|
@@ -513,6 +536,7 @@ export function registerActionCommands(program) {
|
|
|
513
536
|
params.require_frontmost = true;
|
|
514
537
|
const res = unwrap(await client.call('key', params));
|
|
515
538
|
warnIfNotFrontmost(res);
|
|
539
|
+
emitComputerAction('key', pid, opts, { keys: opts.keys });
|
|
516
540
|
emit(res, Boolean(opts.json), () => `sent ${opts.keys}`);
|
|
517
541
|
});
|
|
518
542
|
});
|
|
@@ -548,6 +572,7 @@ export function registerActionCommands(program) {
|
|
|
548
572
|
if (opts.background)
|
|
549
573
|
params.background = true;
|
|
550
574
|
const res = unwrap(await client.call('drag', params));
|
|
575
|
+
emitComputerAction('drag', pid, opts, { from: opts.from, to: opts.to });
|
|
551
576
|
emit(res, Boolean(opts.json), () => `dragged ${opts.from} -> ${opts.to} (${res.method ?? 'ok'})`);
|
|
552
577
|
});
|
|
553
578
|
});
|
|
@@ -574,6 +599,7 @@ export function registerActionCommands(program) {
|
|
|
574
599
|
if (opts.dx != null)
|
|
575
600
|
params.dx = opts.dx;
|
|
576
601
|
const res = unwrap(await client.call('scroll', params));
|
|
602
|
+
emitComputerAction('scroll', pid, opts, { id: opts.id, dx: opts.dx, dy: opts.dy });
|
|
577
603
|
emit(res, Boolean(opts.json), () => `scrolled (${res.method ?? 'ok'})`);
|
|
578
604
|
});
|
|
579
605
|
});
|
|
@@ -587,6 +613,7 @@ export function registerActionCommands(program) {
|
|
|
587
613
|
await withClient(async (client) => {
|
|
588
614
|
const pid = await resolveTargetPid(client, opts, { verb: 'ax-action' });
|
|
589
615
|
const res = unwrap(await client.call('ax_action', { pid, element_id: opts.id, action: opts.action }));
|
|
616
|
+
emitComputerAction('ax-action', pid, opts, { id: opts.id, action: opts.action });
|
|
590
617
|
emit(res, Boolean(opts.json), () => `performed ${opts.action}`);
|
|
591
618
|
});
|
|
592
619
|
});
|
|
@@ -599,6 +626,7 @@ export function registerActionCommands(program) {
|
|
|
599
626
|
await withClient(async (client) => {
|
|
600
627
|
const pid = await resolveTargetPid(client, opts, { verb: 'focus' });
|
|
601
628
|
const res = unwrap(await client.call('set_focus', { pid, element_id: opts.id }));
|
|
629
|
+
emitComputerAction('focus', pid, opts, { id: opts.id });
|
|
602
630
|
emit(res, Boolean(opts.json), () => `focused ${opts.id}`);
|
|
603
631
|
});
|
|
604
632
|
});
|
|
@@ -615,6 +643,7 @@ export function registerActionCommands(program) {
|
|
|
615
643
|
await withClient(async (client) => {
|
|
616
644
|
const pid = await resolveTargetPid(client, opts, { verb: 'raise' });
|
|
617
645
|
const res = unwrap(await client.call('focus_window', { pid, ...buildRaiseParams(opts) }));
|
|
646
|
+
emitComputerAction('raise', pid, opts, { windowId: opts.windowId, title: opts.title });
|
|
618
647
|
emit(res, Boolean(opts.json), () => {
|
|
619
648
|
const scope = res.raised_window ? `window ${res.title ?? res.window_id ?? ''}`.trim() : 'app';
|
|
620
649
|
return `raised ${scope} (${res.focus_elapsed_ms ?? 0}ms)`;
|
|
@@ -644,6 +673,9 @@ export function registerActionCommands(program) {
|
|
|
644
673
|
if (params.duration_ms == null)
|
|
645
674
|
params.pid = await resolveTargetPid(client, opts, { verb: 'wait' });
|
|
646
675
|
const res = unwrap(await client.call('wait', params));
|
|
676
|
+
emitComputerAction('wait', params.pid, opts, {
|
|
677
|
+
until: opts.until, durationMs: opts.duration, satisfied: Boolean(res.satisfied),
|
|
678
|
+
});
|
|
647
679
|
emit(res, Boolean(opts.json), () => res.satisfied ? `satisfied (${res.waited_ms}ms)` : `timed out (${res.waited_ms}ms)`);
|
|
648
680
|
});
|
|
649
681
|
});
|
|
@@ -662,6 +694,7 @@ export function registerActionCommands(program) {
|
|
|
662
694
|
if (opts.maxChars != null)
|
|
663
695
|
params.max_chars = opts.maxChars;
|
|
664
696
|
const res = unwrap(await client.call('get_text', params));
|
|
697
|
+
emitComputerAction('get-text', pid, opts, { id: opts.id });
|
|
665
698
|
emit(res, Boolean(opts.json), () => String(res.text ?? ''));
|
|
666
699
|
});
|
|
667
700
|
});
|
|
@@ -688,6 +721,7 @@ export function registerActionCommands(program) {
|
|
|
688
721
|
if (opts.name)
|
|
689
722
|
params.name = opts.name;
|
|
690
723
|
const res = unwrap(await client.call('launch_app', params));
|
|
724
|
+
emitComputerAction('launch', res.pid, opts, { path: opts.path, name: opts.name });
|
|
691
725
|
emit(res, Boolean(opts.json), () => `launched ${res.name} (pid ${res.pid})`);
|
|
692
726
|
});
|
|
693
727
|
});
|
|
@@ -7,7 +7,7 @@ import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketP
|
|
|
7
7
|
import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/ssh-tunnel.js';
|
|
8
8
|
import { sshExec } from '../lib/ssh-exec.js';
|
|
9
9
|
import { encodePowershell } from '../lib/hosts/remote-cmd.js';
|
|
10
|
-
import { registerActionCommands, withClient, unwrap, pickTarget } from './computer-actions.js';
|
|
10
|
+
import { registerActionCommands, withClient, unwrap, pickTarget, emitComputerAction } from './computer-actions.js';
|
|
11
11
|
import { runComputerLoop } from '../lib/computer/loop.js';
|
|
12
12
|
import { makeVerbDispatcher } from '../lib/computer/dispatch.js';
|
|
13
13
|
import { makeClaudeResponder, resolveApiKey, DEFAULT_CLAUDE_MODEL, DEFAULT_CLAUDE_BASE_URL } from '../lib/computer/model.js';
|
|
@@ -301,7 +301,7 @@ function registerRunCommand(program) {
|
|
|
301
301
|
maxTokens: opts.maxTokens,
|
|
302
302
|
});
|
|
303
303
|
await withClient(async (client) => {
|
|
304
|
-
const dispatch = makeVerbDispatcher(client);
|
|
304
|
+
const dispatch = makeVerbDispatcher(client, { host: opts.host });
|
|
305
305
|
const targetInput = opts.bundle ? { bundle: opts.bundle } : {};
|
|
306
306
|
const result = await runComputerLoop({
|
|
307
307
|
task: opts.task,
|
|
@@ -375,6 +375,7 @@ function registerScreenshotCommand(program) {
|
|
|
375
375
|
// --list: enumerate windows, no image.
|
|
376
376
|
if (opts.list) {
|
|
377
377
|
const res = unwrap(await client.call('screenshot', { pid, list: true }));
|
|
378
|
+
emitComputerAction('screenshot', pid, opts, { list: true });
|
|
378
379
|
const windows = res.windows || [];
|
|
379
380
|
if (opts.json) {
|
|
380
381
|
console.log(JSON.stringify(res, null, 2));
|
|
@@ -402,6 +403,7 @@ function registerScreenshotCommand(program) {
|
|
|
402
403
|
console.error('helper returned no image_data');
|
|
403
404
|
process.exit(1);
|
|
404
405
|
}
|
|
406
|
+
emitComputerAction('screenshot', pid, opts, { display: opts.display, windowId: opts.windowId });
|
|
405
407
|
const buf = Buffer.from(b64, 'base64');
|
|
406
408
|
// Sniff the real format and correct the extension so the filename never
|
|
407
409
|
// lies about its bytes (macOS -> JPEG, Windows helper -> PNG).
|
package/dist/commands/perf.d.ts
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
import type { Command } from 'commander';
|
|
15
15
|
import { type PerfAggregateRow } from '../lib/perf/db.js';
|
|
16
16
|
import { type HookProfileRow } from '../lib/hooks/profile.js';
|
|
17
|
+
interface PerfGlobalOpts {
|
|
18
|
+
days?: string;
|
|
19
|
+
warnMs?: string;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
limit?: string;
|
|
22
|
+
project?: string;
|
|
23
|
+
}
|
|
17
24
|
/** Map warehouse rows shaped like hook.fire into the existing HookProfileRow UI. */
|
|
18
25
|
export declare function asHookRows(rows: PerfAggregateRow[]): HookProfileRow[];
|
|
19
26
|
export declare function renderHookTable(rows: HookProfileRow[], warnMs: number): void;
|
|
@@ -24,4 +31,13 @@ export declare function renderHookTable(rows: HookProfileRow[], warnMs: number):
|
|
|
24
31
|
* hit ignores it (a caller filtering by project has no legacy rows to miss).
|
|
25
32
|
*/
|
|
26
33
|
export declare function loadHookProfile(days: number, project?: string): HookProfileRow[];
|
|
34
|
+
/**
|
|
35
|
+
* Sessions stuck repeatedly hitting the SAME guard block (git-guard,
|
|
36
|
+
* rm-guard, git-require-clean-tree, …) instead of adapting after the first
|
|
37
|
+
* denial. Reads the `friction` event sink (emitFriction in events.ts) that
|
|
38
|
+
* guard hooks self-report into via `agents _internal friction` before they
|
|
39
|
+
* exit 2 — see lib/friction-heuristics.ts for the grouping.
|
|
40
|
+
*/
|
|
41
|
+
export declare function frictionAction(opts: PerfGlobalOpts): void;
|
|
27
42
|
export declare function registerPerfCommand(program: Command): void;
|
|
43
|
+
export {};
|
package/dist/commands/perf.js
CHANGED
|
@@ -175,7 +175,17 @@ function runAction(opts) {
|
|
|
175
175
|
* guard hooks self-report into via `agents _internal friction` before they
|
|
176
176
|
* exit 2 — see lib/friction-heuristics.ts for the grouping.
|
|
177
177
|
*/
|
|
178
|
-
function frictionAction(opts) {
|
|
178
|
+
export function frictionAction(opts) {
|
|
179
|
+
// --project is declared on the shared `perf` parent for hooks/commands/run,
|
|
180
|
+
// where every sample carries a cwd. friction events (emitFriction in
|
|
181
|
+
// events.ts) don't carry one today — agents _internal friction has no
|
|
182
|
+
// --cwd flag — so silently accepting the flag here would look like it
|
|
183
|
+
// filtered when it did nothing. Fail loud instead of no-op.
|
|
184
|
+
if (opts.project) {
|
|
185
|
+
console.error(chalk.red("agents perf friction does not support --project yet — friction events carry no cwd to filter on."));
|
|
186
|
+
process.exitCode = 1;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
179
189
|
const days = parseDays(opts.days);
|
|
180
190
|
const startDate = new Date(Date.now() - days * 86_400_000);
|
|
181
191
|
const events = query({ eventTypes: ['friction'], startDate });
|
|
@@ -20,7 +20,17 @@ export declare const PROJECTS_NO_FANOUT_ENV = "AGENTS_PROJECTS_LOCAL";
|
|
|
20
20
|
* string when everything answered.
|
|
21
21
|
*/
|
|
22
22
|
export declare function formatFleetSkippedNote(skipped: string[]): string;
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Best-effort `owner/repo` from a repo's origin remote.
|
|
25
|
+
*
|
|
26
|
+
* `stderr: 'ignore'` is load-bearing, not tidiness. A checkout with no origin
|
|
27
|
+
* makes git print `error: No such remote 'origin'` on ITS stderr, which is the
|
|
28
|
+
* terminal's — the catch below never sees it. That was invisible while this was
|
|
29
|
+
* called once from `add` inside a real repo, and became noise the moment
|
|
30
|
+
* `import --from-factory` started calling it per row: importing 12 registry
|
|
31
|
+
* rows printed two raw git errors between the progress lines. Absence of a
|
|
32
|
+
* remote is an expected answer here (`undefined`), not something to report.
|
|
33
|
+
*/
|
|
24
34
|
export declare function originSlug(cwd: string): string | undefined;
|
|
25
35
|
/** One `projects list` row, pre-render. */
|
|
26
36
|
export interface ProjectListRow {
|
|
@@ -57,10 +57,24 @@ function parseContextFlag(raw) {
|
|
|
57
57
|
return { path: raw.trim(), purpose: '' };
|
|
58
58
|
return { path: raw.slice(0, i).trim(), purpose: raw.slice(i + 1).trim() };
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Best-effort `owner/repo` from a repo's origin remote.
|
|
62
|
+
*
|
|
63
|
+
* `stderr: 'ignore'` is load-bearing, not tidiness. A checkout with no origin
|
|
64
|
+
* makes git print `error: No such remote 'origin'` on ITS stderr, which is the
|
|
65
|
+
* terminal's — the catch below never sees it. That was invisible while this was
|
|
66
|
+
* called once from `add` inside a real repo, and became noise the moment
|
|
67
|
+
* `import --from-factory` started calling it per row: importing 12 registry
|
|
68
|
+
* rows printed two raw git errors between the progress lines. Absence of a
|
|
69
|
+
* remote is an expected answer here (`undefined`), not something to report.
|
|
70
|
+
*/
|
|
61
71
|
export function originSlug(cwd) {
|
|
62
72
|
try {
|
|
63
|
-
const url = execFileSync('git', ['remote', 'get-url', 'origin'], {
|
|
73
|
+
const url = execFileSync('git', ['remote', 'get-url', 'origin'], {
|
|
74
|
+
cwd,
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
77
|
+
}).trim();
|
|
64
78
|
return parseOwnerRepoFromRemote(url) ?? undefined;
|
|
65
79
|
}
|
|
66
80
|
catch {
|
|
@@ -374,6 +374,13 @@ function formatCompactPreview(events, session) {
|
|
|
374
374
|
let latestTodos;
|
|
375
375
|
let subAgentCount = 0;
|
|
376
376
|
const toolTags = new Set();
|
|
377
|
+
// usedBrowser/usedComputer are computed at scan time from a sessionId-scoped
|
|
378
|
+
// events-log read (session/db.ts detectToolUsage), NOT a transcript regex —
|
|
379
|
+
// undefined means a legacy row this scanner hasn't computed the field for
|
|
380
|
+
// yet, so only THEN does classifySessionTool's transcript-derived guess run
|
|
381
|
+
// below (mirrors directoriesTouched's prefer-persisted/fall-back-to-derived
|
|
382
|
+
// pattern for recentDirectoriesTouched).
|
|
383
|
+
const knownToolUsage = session.usedBrowser !== undefined;
|
|
377
384
|
for (const event of events) {
|
|
378
385
|
if (event.type === 'message') {
|
|
379
386
|
if (event.role === 'user' && !firstUser && event.content) {
|
|
@@ -388,8 +395,10 @@ function formatCompactPreview(events, session) {
|
|
|
388
395
|
else if (event.type === 'tool_use' && !event._local) {
|
|
389
396
|
const tool = event.tool || '';
|
|
390
397
|
const command = event.command || '';
|
|
391
|
-
|
|
392
|
-
|
|
398
|
+
if (!knownToolUsage) {
|
|
399
|
+
for (const tag of classifySessionTool(tool, command))
|
|
400
|
+
toolTags.add(tag);
|
|
401
|
+
}
|
|
393
402
|
if (isSubAgentTool(tool, command))
|
|
394
403
|
subAgentCount++;
|
|
395
404
|
const p = event.path || event.args?.file_path || event.args?.path || '';
|
|
@@ -412,6 +421,12 @@ function formatCompactPreview(events, session) {
|
|
|
412
421
|
toolCalls++;
|
|
413
422
|
}
|
|
414
423
|
}
|
|
424
|
+
// Persisted field wins when computed — it comes from real browser.navigate/
|
|
425
|
+
// browser.screenshot/computer.action events, not a fuzzy tool-name regex.
|
|
426
|
+
if (session.usedBrowser)
|
|
427
|
+
toolTags.add('browser');
|
|
428
|
+
if (session.usedComputer)
|
|
429
|
+
toolTags.add('computer');
|
|
415
430
|
// Prefer the transcript-derived list when we just re-parsed the file (freshest
|
|
416
431
|
// checklist write); fall back to SessionMeta.todos for rows where the scan/
|
|
417
432
|
// fan-out attached progress but the event stream has no TodoWrite yet.
|
|
@@ -70,6 +70,11 @@ interface SessionsOptions extends SessionFilterOptions {
|
|
|
70
70
|
printCmd?: boolean;
|
|
71
71
|
/** Print a compact preview of the matched session and exit (no pager). */
|
|
72
72
|
preview?: boolean;
|
|
73
|
+
/** Only sessions that invoked this skill (#12) — matches a bare name or a
|
|
74
|
+
* namespaced plugin skill's short name (`--skill design` finds `rush:design`). */
|
|
75
|
+
skill?: string;
|
|
76
|
+
/** Only sessions that used a skill/command owned by this plugin (#12). */
|
|
77
|
+
plugin?: string;
|
|
73
78
|
}
|
|
74
79
|
/**
|
|
75
80
|
* Strip terminal/harness noise from a preview so the column stays a single line
|
|
@@ -1172,6 +1172,12 @@ export function hasNoBrowserDisqualifyingFlags(options, query) {
|
|
|
1172
1172
|
!options.markdown &&
|
|
1173
1173
|
!options.until &&
|
|
1174
1174
|
!options.project &&
|
|
1175
|
+
// The interactive browser picker is a fuzzy-search TUI over the discovered
|
|
1176
|
+
// pool, not a SQL-filtered listing — it cannot represent a skill/plugin
|
|
1177
|
+
// scope. Falling through to it here would silently drop the filter and
|
|
1178
|
+
// show the unfiltered pool instead (same reasoning as --project/--sort).
|
|
1179
|
+
!options.skill &&
|
|
1180
|
+
!options.plugin &&
|
|
1175
1181
|
!options.sort &&
|
|
1176
1182
|
!options.artifacts &&
|
|
1177
1183
|
options.artifact === undefined &&
|
|
@@ -1202,6 +1208,10 @@ function canonicalSessionsCommand(query, options) {
|
|
|
1202
1208
|
a.push('--device', h);
|
|
1203
1209
|
if (options.project)
|
|
1204
1210
|
a.push('--project', options.project);
|
|
1211
|
+
if (options.skill)
|
|
1212
|
+
a.push('--skill', options.skill);
|
|
1213
|
+
if (options.plugin)
|
|
1214
|
+
a.push('--plugin', options.plugin);
|
|
1205
1215
|
if (options.all)
|
|
1206
1216
|
a.push('--all');
|
|
1207
1217
|
if (options.since)
|
|
@@ -1557,6 +1567,8 @@ limitSource) {
|
|
|
1557
1567
|
until: options.until,
|
|
1558
1568
|
sortBy,
|
|
1559
1569
|
origin: options.routine ? 'routine' : undefined,
|
|
1570
|
+
skill: options.skill,
|
|
1571
|
+
plugin: options.plugin,
|
|
1560
1572
|
};
|
|
1561
1573
|
let hiddenUnmanaged = 0;
|
|
1562
1574
|
let sessions = await discoverSessions({
|
|
@@ -3306,6 +3318,8 @@ export function registerSessionsCommands(program) {
|
|
|
3306
3318
|
.option('--in-team <name>', "Only this team: the session that spawned it plus (with --teams) its teammates. Spans every directory and all time, since a team's worktrees and history sit outside the default window.")
|
|
3307
3319
|
.option('--routine', 'Show only sessions archived from routine runs')
|
|
3308
3320
|
.option('-p, --project <name>', 'Filter by project name (searches across all directories)')
|
|
3321
|
+
.option('--skill <name>', 'Only sessions that invoked this skill (matches a bare name or a namespaced plugin skill\'s short name, e.g. --skill design finds rush:design)')
|
|
3322
|
+
.option('--plugin <name>', 'Only sessions that used a skill/command owned by this plugin')
|
|
3309
3323
|
.option('--since <time>', 'Only sessions newer than this (e.g., 2h, 7d, 4w, or ISO date)')
|
|
3310
3324
|
.option('--until <time>', 'Only sessions older than this (ISO timestamp)')
|
|
3311
3325
|
.option('-n, --limit <n>', 'Maximum number of sessions to return', DEFAULT_LIMIT)
|
package/dist/index.js
CHANGED
|
@@ -99,6 +99,7 @@ import { applyGlobalHelpConventions } from './lib/help.js';
|
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
101
101
|
import { emit, emitFriction, redactArgs } from './lib/events.js';
|
|
102
|
+
import { stampProvenance } from './lib/event-provenance.js';
|
|
102
103
|
import { die } from './lib/format.js';
|
|
103
104
|
// Transparent shim delegate: the generated Windows `.cmd` shims invoke
|
|
104
105
|
// `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
|
|
@@ -211,12 +212,19 @@ program.hook('postAction', (_thisCommand, actionCommand) => {
|
|
|
211
212
|
}
|
|
212
213
|
// Disposable perf warehouse — fail-soft spool append (no SQLite on this path).
|
|
213
214
|
if (durationMs !== undefined && parts[0] !== 'perf') {
|
|
215
|
+
// sessionId/agent are resolvable here the same way emit() resolves them
|
|
216
|
+
// for command.start/command.end above (the shared provenance floor,
|
|
217
|
+
// event-provenance.ts) — without this, every command.end perf sample
|
|
218
|
+
// was anonymous, unlike the audit log record right next to it.
|
|
219
|
+
const { sessionId, agent } = stampProvenance();
|
|
214
220
|
void import('./lib/perf/spool.js').then(({ recordSample }) => {
|
|
215
221
|
recordSample({
|
|
216
222
|
kind: 'command.end',
|
|
217
223
|
label: command,
|
|
218
224
|
durationMs,
|
|
219
225
|
cwd: process.cwd(),
|
|
226
|
+
sessionId,
|
|
227
|
+
agent,
|
|
220
228
|
});
|
|
221
229
|
}).catch(() => { });
|
|
222
230
|
}
|
|
@@ -486,6 +486,7 @@ export class BrowserService {
|
|
|
486
486
|
const sessionId = await this.getSessionId(conn, cdpTargetId);
|
|
487
487
|
await conn.cdp.send('Page.navigate', { url }, sessionId);
|
|
488
488
|
await this.saveTaskState(task.profile, conn.tasks);
|
|
489
|
+
emit('browser.navigate', { profile: task.profile, task: task.name, url, tabId: currentShortId, created: false });
|
|
489
490
|
return { tabId: currentShortId, url, created: false };
|
|
490
491
|
}
|
|
491
492
|
// No current tab - create one
|
|
@@ -500,6 +501,7 @@ export class BrowserService {
|
|
|
500
501
|
task.tabs[shortId] = cdpTargetId;
|
|
501
502
|
task.currentTabId = shortId;
|
|
502
503
|
await this.saveTaskState(task.profile, conn.tasks);
|
|
504
|
+
emit('browser.navigate', { profile: task.profile, task: task.name, url, tabId: shortId, created: true });
|
|
503
505
|
return { tabId: shortId, url, created: true };
|
|
504
506
|
}
|
|
505
507
|
// Chrome: create new tab
|
|
@@ -511,6 +513,7 @@ export class BrowserService {
|
|
|
511
513
|
task.currentTabId = shortId;
|
|
512
514
|
this.invalidateTargetCache(conn);
|
|
513
515
|
await this.saveTaskState(task.profile, conn.tasks);
|
|
516
|
+
emit('browser.navigate', { profile: task.profile, task: task.name, url, tabId: shortId, created: true });
|
|
514
517
|
return { tabId: shortId, url, created: true };
|
|
515
518
|
}
|
|
516
519
|
async tabAdd(taskId, url, profileName) {
|
|
@@ -705,6 +708,16 @@ export class BrowserService {
|
|
|
705
708
|
await fs.promises.writeFile(finalPath, buffer);
|
|
706
709
|
const dims = (extension === 'png' ? readPngDimensions(buffer) : readJpegDimensions(buffer)) ??
|
|
707
710
|
{ width: 0, height: 0 };
|
|
711
|
+
emit('browser.screenshot', {
|
|
712
|
+
profile: profileName,
|
|
713
|
+
task: task.name,
|
|
714
|
+
tabId: shortId,
|
|
715
|
+
path: finalPath,
|
|
716
|
+
bytes: buffer.length,
|
|
717
|
+
width: dims.width,
|
|
718
|
+
height: dims.height,
|
|
719
|
+
quality,
|
|
720
|
+
});
|
|
708
721
|
return { path: finalPath, bytes: buffer.length, width: dims.width, height: dims.height };
|
|
709
722
|
}
|
|
710
723
|
/**
|
|
@@ -3,6 +3,8 @@ import { type AppInfo } from '../../commands/computer-actions.js';
|
|
|
3
3
|
import type { VerbDispatcher } from './loop.js';
|
|
4
4
|
declare function rpcMethodFor(verb: string): string;
|
|
5
5
|
declare function toRpcParams(verb: string, input: Record<string, unknown>, pid?: number): Record<string, unknown>;
|
|
6
|
-
export declare function makeVerbDispatcher(client: ComputerClient
|
|
6
|
+
export declare function makeVerbDispatcher(client: ComputerClient, context?: {
|
|
7
|
+
host?: string;
|
|
8
|
+
}): VerbDispatcher;
|
|
7
9
|
export { rpcMethodFor, toRpcParams };
|
|
8
10
|
export type { AppInfo };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// it names the same RPC methods the explicit `agents computer <verb>` commands
|
|
6
6
|
// call, so the external-agent verb surface is untouched. The daemon stays the
|
|
7
7
|
// single authority on permissions and targeting.
|
|
8
|
-
import { resolveTargetPidDecision } from '../../commands/computer-actions.js';
|
|
8
|
+
import { resolveTargetPidDecision, emitComputerAction } from '../../commands/computer-actions.js';
|
|
9
9
|
// CLI verb -> daemon RPC method. Only the rename cases need listing; the rest
|
|
10
10
|
// pass through by name.
|
|
11
11
|
const RPC_METHOD = {
|
|
@@ -102,7 +102,14 @@ function toRpcParams(verb, input, pid) {
|
|
|
102
102
|
// Build a dispatcher bound to a live client. Every call is a single daemon
|
|
103
103
|
// round-trip; errors are returned (never thrown) so the loop can feed them
|
|
104
104
|
// back to the model as a tool_result.
|
|
105
|
-
|
|
105
|
+
//
|
|
106
|
+
// `computer run`'s embedded model loop drives every verb (click, type,
|
|
107
|
+
// screenshot, ...) through THIS single seam rather than the explicit
|
|
108
|
+
// `agents computer <verb>` commands in computer-actions.ts, so it must emit
|
|
109
|
+
// the same `computer.action` event those commands emit — otherwise a session
|
|
110
|
+
// that only used the loop reads back usedComputer=false even though it drove
|
|
111
|
+
// real actions (reviewer-flagged regression on #1864).
|
|
112
|
+
export function makeVerbDispatcher(client, context = {}) {
|
|
106
113
|
return async (call) => {
|
|
107
114
|
const verb = call.name;
|
|
108
115
|
const input = call.input ?? {};
|
|
@@ -118,6 +125,7 @@ export function makeVerbDispatcher(client) {
|
|
|
118
125
|
const res = await client.call(method, params);
|
|
119
126
|
if (res.error)
|
|
120
127
|
return { ok: false, error: `${res.error.code}: ${res.error.message}` };
|
|
128
|
+
emitComputerAction(verb, pid, { bundle: input.bundle, host: context.host });
|
|
121
129
|
return { ok: true, result: res.result ?? {} };
|
|
122
130
|
};
|
|
123
131
|
}
|
|
@@ -18,6 +18,8 @@ export interface UnifiedQuery {
|
|
|
18
18
|
eventTypes?: EventType[];
|
|
19
19
|
level?: EventLevel;
|
|
20
20
|
agent?: string;
|
|
21
|
+
/** Only events stamped with this session id (payload `sessionId`, the provenance floor). */
|
|
22
|
+
sessionId?: string;
|
|
21
23
|
caller?: string;
|
|
22
24
|
command?: string;
|
|
23
25
|
module?: string;
|
package/dist/lib/event-stream.js
CHANGED
|
@@ -29,6 +29,8 @@ function matches(r, q) {
|
|
|
29
29
|
return false;
|
|
30
30
|
if (q.agent && r.agent !== q.agent)
|
|
31
31
|
return false;
|
|
32
|
+
if (q.sessionId && r.sessionId !== q.sessionId)
|
|
33
|
+
return false;
|
|
32
34
|
if (q.caller && r.caller !== q.caller)
|
|
33
35
|
return false;
|
|
34
36
|
if (q.command && r.command !== q.command &&
|
|
@@ -51,6 +53,7 @@ export function readUnifiedEvents(q = {}) {
|
|
|
51
53
|
eventTypes: q.eventTypes,
|
|
52
54
|
level: q.level,
|
|
53
55
|
agent: q.agent,
|
|
56
|
+
sessionId: q.sessionId,
|
|
54
57
|
caller: q.caller,
|
|
55
58
|
command: q.command,
|
|
56
59
|
module: q.module,
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { ActorKind } from './actor.js';
|
|
15
15
|
export type EventLevel = 'audit' | 'warn' | 'info' | 'debug';
|
|
16
|
-
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'secrets.get' | 'secrets.unlocked' | 'secrets.create' | 'secrets.import' | 'secrets.export' | 'secrets.view' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'webhook.received' | 'webhook.authorized' | 'webhook.rejected' | 'webhook.matched' | 'webhook.fired' | 'webhook.handler.start' | 'webhook.handler.end' | 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created' | 'status.posted' | 'file.edited' | 'factory.command' | 'factory.action' | 'factory.uri' | 'factory.launch' | 'friction' | 'error' | 'warn' | 'info' | 'debug';
|
|
16
|
+
export type EventType = 'agent.run.start' | 'agent.run.end' | 'agent.spawn.start' | 'agent.spawn.end' | 'version.install' | 'version.switch' | 'version.remove' | 'skill.install' | 'skill.remove' | 'browser.launch' | 'browser.close' | 'browser.navigate' | 'browser.screenshot' | 'computer.action' | 'secrets.get' | 'secrets.unlocked' | 'secrets.create' | 'secrets.import' | 'secrets.export' | 'secrets.view' | 'secrets.set' | 'secrets.delete' | 'secrets.rename' | 'cloud.dispatch' | 'cloud.complete' | 'cloud.cancel' | 'cloud.message' | 'teams.create' | 'teams.add' | 'teams.start' | 'teams.complete' | 'teams.disband' | 'hook.fire' | 'hook.complete' | 'hook.error' | 'mcp.add' | 'mcp.remove' | 'mcp.register' | 'resource.sync' | 'rotation.resolved' | 'command.start' | 'command.end' | 'perf.timing' | 'session.start' | 'session.end' | 'webhook.received' | 'webhook.authorized' | 'webhook.rejected' | 'webhook.matched' | 'webhook.fired' | 'webhook.handler.start' | 'webhook.handler.end' | 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created' | 'status.posted' | 'file.edited' | 'factory.command' | 'factory.action' | 'factory.uri' | 'factory.launch' | 'friction' | 'error' | 'warn' | 'info' | 'debug';
|
|
17
17
|
/** Every known event kind. Derived from {@link EVENT_TYPE_TABLE}, never hand-listed. */
|
|
18
18
|
export declare const EVENT_TYPES: readonly EventType[];
|
|
19
19
|
/** Runtime guard for an event kind arriving from outside this process. */
|
|
@@ -208,6 +208,8 @@ export declare function query(options: {
|
|
|
208
208
|
eventTypes?: EventType[];
|
|
209
209
|
level?: EventLevel;
|
|
210
210
|
agent?: string;
|
|
211
|
+
/** Only events stamped with this session id (payload `sessionId`, the provenance floor). */
|
|
212
|
+
sessionId?: string;
|
|
211
213
|
caller?: string;
|
|
212
214
|
command?: string;
|
|
213
215
|
module?: string;
|
package/dist/lib/events.js
CHANGED
|
@@ -84,6 +84,7 @@ const EVENT_TYPE_TABLE = {
|
|
|
84
84
|
'version.install': true, 'version.switch': true, 'version.remove': true,
|
|
85
85
|
'skill.install': true, 'skill.remove': true,
|
|
86
86
|
'browser.launch': true, 'browser.close': true, 'browser.navigate': true, 'browser.screenshot': true,
|
|
87
|
+
'computer.action': true,
|
|
87
88
|
'secrets.get': true, 'secrets.unlocked': true, 'secrets.create': true, 'secrets.import': true, 'secrets.export': true, 'secrets.view': true, 'secrets.set': true, 'secrets.delete': true, 'secrets.rename': true,
|
|
88
89
|
'cloud.dispatch': true, 'cloud.complete': true, 'cloud.cancel': true, 'cloud.message': true,
|
|
89
90
|
'teams.create': true, 'teams.add': true, 'teams.start': true, 'teams.complete': true, 'teams.disband': true,
|
|
@@ -327,7 +328,6 @@ export function detectCaller(env = process.env, stdoutIsTTY = Boolean(process.st
|
|
|
327
328
|
}
|
|
328
329
|
return { kind: stdoutIsTTY ? 'terminal' : 'script' };
|
|
329
330
|
}
|
|
330
|
-
// ─── Audit attribution ────────────────────────────────────────────────────────
|
|
331
331
|
// ─── Core API ─────────────────────────────────────────────────────────────────
|
|
332
332
|
/**
|
|
333
333
|
* Emit a structured event to the append-only audit log.
|
|
@@ -729,7 +729,7 @@ export function maybeRotate() {
|
|
|
729
729
|
* @returns Array of event records
|
|
730
730
|
*/
|
|
731
731
|
export function query(options) {
|
|
732
|
-
const { startDate, endDate = new Date(), eventTypes, level, agent, caller, command, module, limit } = options;
|
|
732
|
+
const { startDate, endDate = new Date(), eventTypes, level, agent, sessionId, caller, command, module, limit } = options;
|
|
733
733
|
const results = [];
|
|
734
734
|
if (!fs.existsSync(eventsDir()))
|
|
735
735
|
return results;
|
|
@@ -774,6 +774,8 @@ export function query(options) {
|
|
|
774
774
|
continue;
|
|
775
775
|
if (agent && record.agent !== agent)
|
|
776
776
|
continue;
|
|
777
|
+
if (sessionId && record.sessionId !== sessionId)
|
|
778
|
+
continue;
|
|
777
779
|
if (caller && record.caller !== caller)
|
|
778
780
|
continue;
|
|
779
781
|
if (command && record.command !== command &&
|
package/dist/lib/git.d.ts
CHANGED
|
@@ -103,6 +103,20 @@ export interface RepoStateSnapshot {
|
|
|
103
103
|
* result is stable across git versions and never mutates the tree. Returns null
|
|
104
104
|
* when the path is not a git worktree. */
|
|
105
105
|
export declare function readRepoState(repoPath: string): RepoStateSnapshot | null;
|
|
106
|
+
/**
|
|
107
|
+
* The short HEAD sha of the git repo at `repoRoot` (`git -C <repoRoot>
|
|
108
|
+
* rev-parse --short HEAD`), for provenance — "which commit of this DotAgents
|
|
109
|
+
* repo was this resource/plugin resolved from". `undefined` when `repoRoot`
|
|
110
|
+
* isn't a git repo (or has no commits yet), never a throw.
|
|
111
|
+
*
|
|
112
|
+
* Deliberately synchronous + resolved once and cached: callers (resources.ts,
|
|
113
|
+
* plugins.ts) attach this as a lazy getter on the resolved object, so a
|
|
114
|
+
* consumer that never inspects provenance never pays for the git shell-out —
|
|
115
|
+
* see {@link ResolvedResource.snapshotSha} / {@link DiscoveredPlugin.snapshotSha}.
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveSnapshotSha(repoRoot: string): string | undefined;
|
|
118
|
+
/** Test seam: clear the memoized snapshot-sha cache between test cases. */
|
|
119
|
+
export declare function _resetSnapshotShaCacheForTest(): void;
|
|
106
120
|
/**
|
|
107
121
|
* Get the current GitHub username using gh CLI.
|
|
108
122
|
* Returns null if gh is not installed or user is not authenticated.
|