harnery 0.31.5 → 0.32.0
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/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +70 -95
- package/dist/commands/browse-session.d.ts +21 -0
- package/dist/commands/browse-session.d.ts.map +1 -0
- package/dist/commands/browse-session.js +157 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +205 -37
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +40 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +100 -17
- package/dist/commands/init.d.ts +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -5
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +1 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +1 -2
- package/dist/core/agents/cli.js +123 -88
- package/dist/core/agents/coord-client.d.ts +11 -4
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +27 -9
- package/dist/core/agents/finalization.d.ts +68 -0
- package/dist/core/agents/finalization.d.ts.map +1 -0
- package/dist/core/agents/finalization.js +443 -0
- package/dist/core/agents/git-hook.d.ts +51 -0
- package/dist/core/agents/git-hook.d.ts.map +1 -0
- package/dist/core/agents/git-hook.js +118 -0
- package/dist/core/agents/render/prompt-context.d.ts +6 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +26 -13
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +15 -2
- package/dist/core/agents/rules/claim-conflict.js +3 -3
- package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +21 -5
- package/dist/core/agents/rules/stop-hook.d.ts +3 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +59 -23
- package/dist/core/agents/session-events.d.ts +8 -16
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -26
- package/dist/core/agents/state/heartbeat-projector.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +44 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +32 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +54 -17
- package/dist/core/agents/state/names.d.ts +22 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +40 -2
- package/dist/core/config.d.ts +30 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +74 -11
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +12 -13
- package/dist/core/hooks/adapter/detect.d.ts +2 -4
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +4 -14
- package/dist/core/hooks/adapter/output.d.ts +1 -1
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +5 -4
- package/dist/core/hooks/adapter/parse.d.ts +1 -1
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +22 -5
- package/dist/core/hooks/adapter/wiring.d.ts +23 -0
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +32 -0
- package/dist/core/hooks/cli.d.ts +1 -2
- package/dist/core/hooks/cli.d.ts.map +1 -1
- package/dist/core/hooks/cli.js +169 -27
- package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
- package/dist/core/hooks/codex-wsl-bridge.js +137 -0
- package/dist/core/hooks/effects/index.d.ts +1 -1
- package/dist/core/hooks/effects/index.js +3 -3
- package/dist/core/hooks/events/schema.d.ts +27 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +7 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +15 -0
- package/dist/core/hooks/resolve/transcript.d.ts +44 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +176 -1
- package/dist/core/hooks/session-name-presence.d.ts +30 -0
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
- package/dist/core/hooks/session-name-presence.js +43 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
- package/dist/core/hooks/unsafe-cross-shell.js +151 -0
- package/dist/core/work/state.d.ts +4 -5
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +5 -8
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +2 -2
- package/dist/core/workflow/run-state.d.ts +2 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +2 -2
- package/dist/core/workflow/types.d.ts +3 -3
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.d.ts +2 -2
- package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.js +5 -0
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +1 -2
- package/dist/core/workflow/workspaces/paths.d.ts +2 -2
- package/dist/core/workflow/workspaces/types.d.ts +0 -6
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.d.ts +0 -2
- package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.js +0 -2
- package/dist/lib/browser/client.d.ts +118 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +435 -6
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +180 -37
- package/dist/lib/browser/index.d.ts +5 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +5 -1
- package/dist/lib/browser/netscape-cookies.d.ts +6 -0
- package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
- package/dist/lib/browser/netscape-cookies.js +31 -0
- package/dist/lib/browser/proxy.d.ts +24 -0
- package/dist/lib/browser/proxy.d.ts.map +1 -0
- package/dist/lib/browser/proxy.js +84 -0
- package/dist/lib/browser/runts.d.ts +6 -0
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +21 -4
- package/dist/lib/browser/session-control.d.ts +112 -0
- package/dist/lib/browser/session-control.d.ts.map +1 -0
- package/dist/lib/browser/session-control.js +670 -0
- package/dist/lib/docs-links.d.ts +108 -0
- package/dist/lib/docs-links.d.ts.map +1 -0
- package/dist/lib/docs-links.js +555 -0
- package/dist/lib/exec.d.ts +1 -1
- package/dist/lib/exec.js +1 -1
- package/dist/lib/identities/assume.d.ts +4 -2
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +15 -2
- package/dist/lib/instructions/git-hooks.d.ts +75 -0
- package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
- package/dist/lib/instructions/git-hooks.js +238 -0
- package/dist/lib/instructions/splice.d.ts +10 -4
- package/dist/lib/instructions/splice.d.ts.map +1 -1
- package/dist/lib/instructions/splice.js +19 -11
- package/package.json +1 -1
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +2 -0
- package/src/commands/agents.ts +96 -115
- package/src/commands/browse-session.ts +245 -0
- package/src/commands/browse.ts +287 -44
- package/src/commands/checkpoint.ts +1 -1
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +100 -18
- package/src/commands/init.ts +17 -9
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +1 -2
- package/src/core/agents/cli.ts +137 -88
- package/src/core/agents/coord-client.ts +38 -9
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/render/prompt-context.ts +28 -13
- package/src/core/agents/render/session-context.ts +15 -2
- package/src/core/agents/rules/claim-conflict.ts +3 -3
- package/src/core/agents/rules/commit-conflict.ts +35 -8
- package/src/core/agents/rules/stop-hook.ts +71 -23
- package/src/core/agents/session-events.ts +14 -41
- package/src/core/agents/state/heartbeat-projector.ts +45 -1
- package/src/core/agents/state/heartbeat-writer.ts +78 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/config.ts +92 -12
- package/src/core/governor/planning.ts +17 -12
- package/src/core/hooks/adapter/detect.ts +4 -13
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/parse.ts +24 -5
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +201 -23
- package/src/core/hooks/codex-wsl-bridge.ts +188 -0
- package/src/core/hooks/effects/index.ts +5 -5
- package/src/core/hooks/events/schema.ts +27 -0
- package/src/core/hooks/resolve/owner.ts +22 -1
- package/src/core/hooks/resolve/transcript.ts +164 -1
- package/src/core/hooks/session-name-presence.ts +52 -0
- package/src/core/hooks/unsafe-cross-shell.ts +160 -0
- package/src/core/work/state.ts +8 -12
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/proof.ts +4 -4
- package/src/core/workflow/run-state.ts +5 -5
- package/src/core/workflow/types.ts +3 -3
- package/src/core/workflow/workspaces/execution.ts +11 -4
- package/src/core/workflow/workspaces/index.ts +0 -3
- package/src/core/workflow/workspaces/local-git.ts +1 -2
- package/src/core/workflow/workspaces/paths.ts +2 -2
- package/src/core/workflow/workspaces/types.ts +0 -9
- package/src/core/workflow/workspaces/validate.ts +1 -5
- package/src/lib/browser/client.ts +528 -6
- package/src/lib/browser/geometry.ts +197 -37
- package/src/lib/browser/index.ts +39 -0
- package/src/lib/browser/netscape-cookies.ts +39 -0
- package/src/lib/browser/proxy.ts +105 -0
- package/src/lib/browser/runts.ts +27 -3
- package/src/lib/browser/session-control.ts +892 -0
- package/src/lib/docs-links.ts +674 -0
- package/src/lib/exec.ts +1 -1
- package/src/lib/identities/assume.ts +22 -1
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
package/dist/commander.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyCpC,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAEhC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;IAEnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;CAC7C;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;AAEnD,MAAM,WAAW,qBAAqB;IACpC,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACzC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,2BAA2B,EAAE,gBAAgB,CAAC;IACxE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,KAAK,MAAM,GAAG,SAAS,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACjE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACxC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,KAAK,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACpE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,eAAO,MAAM,WAAW,EAAE,WAkCzB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAsD3E"}
|
package/dist/commander.js
CHANGED
|
@@ -23,6 +23,7 @@ import { registerArtifactsCommand } from "./commands/artifacts.js";
|
|
|
23
23
|
import { registerBackupCommand } from "./commands/backup.js";
|
|
24
24
|
import { registerBrowseCommand } from "./commands/browse.js";
|
|
25
25
|
import { registerBrowseAiCommand } from "./commands/browse-ai.js";
|
|
26
|
+
import { registerBrowseSessionCommand } from "./commands/browse-session.js";
|
|
26
27
|
import { registerCallersCommand } from "./commands/callers.js";
|
|
27
28
|
import { registerCheckpointCommand } from "./commands/checkpoint.js";
|
|
28
29
|
import { registerClaudeDesktopCommand } from "./commands/claude-desktop.js";
|
|
@@ -117,6 +118,7 @@ export function createHarneryProgram(opts = {}) {
|
|
|
117
118
|
registerFetchCommand(program, emit, opts.context);
|
|
118
119
|
registerReadCommand(program, emit);
|
|
119
120
|
registerBrowseCommand(program, emit, opts.context);
|
|
121
|
+
registerBrowseSessionCommand(program, emit);
|
|
120
122
|
registerBrowseAiCommand(program, emit);
|
|
121
123
|
registerCompletionCommand(program, emit, opts.context);
|
|
122
124
|
registerCheckpointCommand(program, emit, opts.context);
|
|
@@ -31,13 +31,4 @@ export declare function coordHelperOpts(root: string): {
|
|
|
31
31
|
};
|
|
32
32
|
export declare function registerAgentsCommand(program: Command, emitParam: EmitContext, programContext?: HarneryProgramContext): void;
|
|
33
33
|
export declare function registerCouncilCommands(parent: Command): void;
|
|
34
|
-
/**
|
|
35
|
-
* Build the copy-pasteable session name from the coord identity + the agent's
|
|
36
|
-
* description parts. Pure (no coord-state reads) so it's unit-testable; collapses
|
|
37
|
-
* internal whitespace and trims. Returns null when the description is empty.
|
|
38
|
-
*/
|
|
39
|
-
export declare function buildSuggestedName(agentName: string, descriptionParts: string[]): {
|
|
40
|
-
suggestedName: string;
|
|
41
|
-
description: string;
|
|
42
|
-
} | null;
|
|
43
34
|
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAkF1E;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,CAErF;AAsED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,cAAc,CAAC,EAAE,qBAAqB,GACrC,IAAI,CAmQN;AAsKD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyO7D"}
|
package/dist/commands/agents.js
CHANGED
|
@@ -18,7 +18,9 @@ import { homedir, tmpdir } from "node:os";
|
|
|
18
18
|
import { basename, join, resolve } from "node:path";
|
|
19
19
|
import { coordBinPath } from "../core/agents/coord-bin.js";
|
|
20
20
|
import { readStreamTailBounded } from "../core/agents/events/consume.js";
|
|
21
|
+
import { checkGitFinalization, formatGitFinalizationFailure, readSessionWriteClaims, } from "../core/agents/finalization.js";
|
|
21
22
|
import { emitCanonical, monorepoRoot, normalizeAdapter, readHeartbeat, resolveOwner, resolveOwnerBySessionEnv, resolveOwnerWithSource, } from "../core/agents/index.js";
|
|
23
|
+
import { buildSuggestedName } from "../core/agents/state/heartbeat-writer.js";
|
|
22
24
|
import { coordFreshnessSeconds, resolveBinName } from "../core/config.js";
|
|
23
25
|
import { readRemoteMachines } from "../core/presence/index.js";
|
|
24
26
|
import { registerContextCommand } from "./context.js";
|
|
@@ -126,6 +128,7 @@ export function registerAgentsCommand(program, emitParam, programContext) {
|
|
|
126
128
|
.command("status")
|
|
127
129
|
.description("End-of-turn status box (name + session age + files held + peer count)")
|
|
128
130
|
.option("--json", "JSON output instead of the box")
|
|
131
|
+
.option("--end-turn", "Treat this as the turn's closing status: issue the box only when this session's held paths are committed and their repositories are pushed")
|
|
129
132
|
.option("--session-id <id>", "Lookup heartbeat by session_id directly, bypassing the ppid walk. " +
|
|
130
133
|
"Use this when calling from a hook (the hook's process tree may not lead back to Claude Code's session pid). " +
|
|
131
134
|
"The Stop hook payload includes session_id; pass it through.")
|
|
@@ -203,7 +206,7 @@ export function registerAgentsCommand(program, emitParam, programContext) {
|
|
|
203
206
|
.command("wait <name>")
|
|
204
207
|
.description(`Block until a peer agent releases files (their \`files_touched\` becomes empty, OR they exit). Pair with \`${resolveBinName()} agents ping\` to coordinate hand-offs.`)
|
|
205
208
|
.option("--file <path>", "Wait only for these specific paths (repeatable)", collectPath, [])
|
|
206
|
-
.option("--timeout <dur>", "Give up after this duration; suffix s/m/h/d (e.g. 30s, 5m, 1h).
|
|
209
|
+
.option("--timeout <dur>", "Give up after this duration; suffix s/m/h/d is required (e.g. 30s, 5m, 1h). Default 60m.", "60m")
|
|
207
210
|
.option("--poll-secs <n>", "Poll interval in seconds (default 5)", "5")
|
|
208
211
|
.option("--quiet", "Suppress progress lines")
|
|
209
212
|
.option("--json", "JSON output (terminal status, only printed at exit)")
|
|
@@ -223,8 +226,8 @@ export function registerAgentsCommand(program, emitParam, programContext) {
|
|
|
223
226
|
});
|
|
224
227
|
cmd
|
|
225
228
|
.command("health")
|
|
226
|
-
.description("One-screen coord-layer health rollup: heal events,
|
|
227
|
-
"
|
|
229
|
+
.description("One-screen coord-layer health rollup: heal events, council activity, " +
|
|
230
|
+
"zombie detection, and anomalies. Designed for " +
|
|
228
231
|
"daily glance + dashboard ingestion. Reads .harnery/.")
|
|
229
232
|
.option("--since <window>", "Window (Nh | Nd). Default: 24h.", "24h")
|
|
230
233
|
.option("--json", "JSON envelope output")
|
|
@@ -236,7 +239,7 @@ export function registerAgentsCommand(program, emitParam, programContext) {
|
|
|
236
239
|
.description("Adapter wiring probe: ppid chain, comm names, pid-map anchor, sample payload paths. " +
|
|
237
240
|
"With --replay-samples, also replays every checked-in sample payload against the live " +
|
|
238
241
|
"adapter in an isolated sandbox to catch adapter / payload-shape drift. " +
|
|
239
|
-
"Complements heal-events (drift telemetry). Id:
|
|
242
|
+
"Complements heal-events (drift telemetry). Id: claude-code | cursor.")
|
|
240
243
|
.option("--json", "JSON envelope output")
|
|
241
244
|
.option("--replay-samples", "Replay docs/api/<adapter>-hooks/samples/*.json against the live adapter in an isolated sandbox. " +
|
|
242
245
|
"Exits non-zero if any sample crashes the adapter.")
|
|
@@ -359,6 +362,9 @@ function registerIdentityCommands(parent) {
|
|
|
359
362
|
"the event ledger, and the heartbeat.")
|
|
360
363
|
.option("--json", "JSON envelope output")
|
|
361
364
|
.option("--session-id <id>", "Bind the heartbeat with this session_id directly, bypassing the ppid walk.")
|
|
365
|
+
.option("--force-ancestor", "Allow assuming a persona this session's fork lineage descends from " +
|
|
366
|
+
"(default: refused, because a fork's inherited transcript asserting that " +
|
|
367
|
+
"name is not a role handoff).")
|
|
362
368
|
.action((target, opts) => {
|
|
363
369
|
runIdentityAssume(target, opts);
|
|
364
370
|
});
|
|
@@ -385,7 +391,9 @@ function runIdentityAssume(target, opts) {
|
|
|
385
391
|
process.exit(1);
|
|
386
392
|
}
|
|
387
393
|
try {
|
|
388
|
-
const result = assumeIdentity(root, owner, target
|
|
394
|
+
const result = assumeIdentity(root, owner, target, {
|
|
395
|
+
forceAncestor: opts.forceAncestor ?? false,
|
|
396
|
+
});
|
|
389
397
|
emit.data({
|
|
390
398
|
...result,
|
|
391
399
|
display_name: displayAgentName(result.name),
|
|
@@ -592,7 +600,7 @@ function runWhoami(opts) {
|
|
|
592
600
|
task: hb.task ?? null,
|
|
593
601
|
turn_summary: hb.turn_summary ?? null,
|
|
594
602
|
turn_summary_updated_at: hb.turn_summary_updated_at ?? null,
|
|
595
|
-
platform: hb.platform ?? "
|
|
603
|
+
platform: hb.platform ?? "claude-code",
|
|
596
604
|
};
|
|
597
605
|
emit.data({ ...row, resolution_source: resolved.source, note: SUBAGENT_NOTE });
|
|
598
606
|
if (process.stdout.isTTY && !opts.json) {
|
|
@@ -681,7 +689,7 @@ function runList(opts) {
|
|
|
681
689
|
task: h.task ?? null,
|
|
682
690
|
turn_summary: h.turn_summary ?? null,
|
|
683
691
|
turn_summary_updated_at: h.turn_summary_updated_at ?? null,
|
|
684
|
-
platform: h.platform ?? "
|
|
692
|
+
platform: h.platform ?? "claude-code",
|
|
685
693
|
});
|
|
686
694
|
continue;
|
|
687
695
|
}
|
|
@@ -706,7 +714,7 @@ function runList(opts) {
|
|
|
706
714
|
task: h.task ?? null,
|
|
707
715
|
turn_summary: h.turn_summary ?? null,
|
|
708
716
|
turn_summary_updated_at: h.turn_summary_updated_at ?? null,
|
|
709
|
-
platform: h.platform ?? "
|
|
717
|
+
platform: h.platform ?? "claude-code",
|
|
710
718
|
});
|
|
711
719
|
}
|
|
712
720
|
// Guard missing started_at: a heartbeat seeded from a stray event can lack it
|
|
@@ -732,7 +740,7 @@ function runList(opts) {
|
|
|
732
740
|
task: a.task ?? null,
|
|
733
741
|
turn_summary: a.turn_summary ?? null,
|
|
734
742
|
turn_summary_updated_at: null,
|
|
735
|
-
platform: a.platform ?? "
|
|
743
|
+
platform: a.platform ?? "claude-code",
|
|
736
744
|
machine: rm.machine,
|
|
737
745
|
});
|
|
738
746
|
}
|
|
@@ -1168,15 +1176,13 @@ function runSetTask(task, opts) {
|
|
|
1168
1176
|
});
|
|
1169
1177
|
process.exit(1);
|
|
1170
1178
|
}
|
|
1171
|
-
// Snapshot BEFORE the mutation:
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1175
|
-
//
|
|
1176
|
-
//
|
|
1177
|
-
// (a deliberate clear), which carries a stamp and does NOT re-trigger.
|
|
1179
|
+
// Snapshot BEFORE the mutation: the writer stamps `suggested_session_name`
|
|
1180
|
+
// on the session's first NON-EMPTY declaration (one source of truth: the
|
|
1181
|
+
// focus you declare names the session). A bare clear never consumes the
|
|
1182
|
+
// naming window, and subagent/workflow kinds are never named — see
|
|
1183
|
+
// heartbeat-writer.setTask. This call is the naming call exactly when the
|
|
1184
|
+
// stamp appears across the mutation.
|
|
1178
1185
|
const priorHb = readHeartbeat(myOwner);
|
|
1179
|
-
const firstOfSession = !priorHb?.task_updated_at;
|
|
1180
1186
|
// Heartbeat mutation goes through agent-coord (atomic temp+rename).
|
|
1181
1187
|
const helper = agentCoordOrExit(root);
|
|
1182
1188
|
const result = spawnSync(helper, ["set-task", myOwner, task], {
|
|
@@ -1191,39 +1197,42 @@ function runSetTask(task, opts) {
|
|
|
1191
1197
|
process.exit(1);
|
|
1192
1198
|
}
|
|
1193
1199
|
const hb = readHeartbeat(myOwner);
|
|
1200
|
+
// The naming call: the mutation just stamped `suggested_session_name` for
|
|
1201
|
+
// the first time. `first_of_session: true` means exactly "this call produced
|
|
1202
|
+
// the session name" — it is never true with a null name (an empty first
|
|
1203
|
+
// declaration or a `--session-id` relay both keep the window open instead).
|
|
1204
|
+
const suggestedName = !priorHb?.suggested_session_name && hb?.suggested_session_name
|
|
1205
|
+
? hb.suggested_session_name
|
|
1206
|
+
: null;
|
|
1207
|
+
const firstOfSession = suggestedName !== null;
|
|
1194
1208
|
emitCanonical({
|
|
1195
1209
|
type: "state.task_set",
|
|
1196
1210
|
owner: myOwner,
|
|
1197
1211
|
session: hb?.session_id ?? myOwner,
|
|
1198
1212
|
adapter: normalizeAdapter(hb?.platform),
|
|
1199
|
-
data: {
|
|
1213
|
+
data: {
|
|
1214
|
+
task,
|
|
1215
|
+
cleared: !task || task.length === 0,
|
|
1216
|
+
first_of_session: firstOfSession,
|
|
1217
|
+
suggested_session_name: suggestedName,
|
|
1218
|
+
},
|
|
1200
1219
|
});
|
|
1201
|
-
// On the first focus declaration of a session, surface a suggested session
|
|
1202
|
-
// name for the operator to set as their adapter tab title. Read the result
|
|
1203
|
-
// (`suggested_session_name`) and reproduce it in a fenced code block so the
|
|
1204
|
-
// UI's Copy button hands over the exact string. null on every later call.
|
|
1205
|
-
const built = firstOfSession && !opts?.sessionId ? buildSuggestedName(hb?.name ?? "unknown", [task]) : null;
|
|
1206
1220
|
emit.data({
|
|
1207
1221
|
instance_id: myOwner,
|
|
1208
1222
|
name: hb?.name ?? null,
|
|
1209
1223
|
task: hb?.task ?? null,
|
|
1210
1224
|
cleared: !task || task.length === 0,
|
|
1211
1225
|
first_of_session: firstOfSession,
|
|
1212
|
-
suggested_session_name:
|
|
1226
|
+
suggested_session_name: suggestedName,
|
|
1227
|
+
// Right-time instruction: the UserPromptSubmit nudge fires before the name
|
|
1228
|
+
// exists; this result is the moment the model holds the string.
|
|
1229
|
+
...(suggestedName
|
|
1230
|
+
? {
|
|
1231
|
+
note: "Reproduce suggested_session_name verbatim, by itself, in a fenced code block at the top of your reply — the operator copies it as the session/tab title.",
|
|
1232
|
+
}
|
|
1233
|
+
: {}),
|
|
1213
1234
|
});
|
|
1214
1235
|
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Build the copy-pasteable session name from the coord identity + the agent's
|
|
1217
|
-
* description parts. Pure (no coord-state reads) so it's unit-testable; collapses
|
|
1218
|
-
* internal whitespace and trims. Returns null when the description is empty.
|
|
1219
|
-
*/
|
|
1220
|
-
export function buildSuggestedName(agentName, descriptionParts) {
|
|
1221
|
-
const description = descriptionParts.join(" ").replace(/\s+/g, " ").trim();
|
|
1222
|
-
if (!description)
|
|
1223
|
-
return null;
|
|
1224
|
-
const name = agentName?.trim() || "unknown";
|
|
1225
|
-
return { suggestedName: `Agent ${name} - ${description}`, description };
|
|
1226
|
-
}
|
|
1227
1236
|
function runSuggestName(descriptionParts, opts) {
|
|
1228
1237
|
const root = monorepoRoot();
|
|
1229
1238
|
if (!root) {
|
|
@@ -1308,20 +1317,21 @@ function runStatus(opts) {
|
|
|
1308
1317
|
});
|
|
1309
1318
|
process.exit(1);
|
|
1310
1319
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
spawnSync(helper, ["stamp-status-call", myOwner], {
|
|
1318
|
-
encoding: "utf8",
|
|
1319
|
-
timeout: 2000,
|
|
1320
|
-
...coordHelperOpts(root),
|
|
1320
|
+
let finalization = null;
|
|
1321
|
+
if (opts.endTurn) {
|
|
1322
|
+
const history = readSessionWriteClaims(root, hb.instance_id, hb.session_id ?? myOwner);
|
|
1323
|
+
const touchedPaths = [...new Set([...(hb.files_touched ?? []), ...history.paths])];
|
|
1324
|
+
finalization = checkGitFinalization(root, touchedPaths, {
|
|
1325
|
+
claimHistoryComplete: history.complete,
|
|
1321
1326
|
});
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1327
|
+
if (!finalization.ok) {
|
|
1328
|
+
emit.error({
|
|
1329
|
+
code: "git_not_finalized",
|
|
1330
|
+
message: formatGitFinalizationFailure(finalization, resolveBinName(root)),
|
|
1331
|
+
});
|
|
1332
|
+
process.exitCode = 1;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1325
1335
|
}
|
|
1326
1336
|
emitCanonical({
|
|
1327
1337
|
type: "state.status_checked",
|
|
@@ -1330,6 +1340,7 @@ function runStatus(opts) {
|
|
|
1330
1340
|
adapter: normalizeAdapter(hb.platform),
|
|
1331
1341
|
data: {
|
|
1332
1342
|
format: opts.json ? "json" : "box",
|
|
1343
|
+
git_finalization_checked: opts.endTurn === true,
|
|
1333
1344
|
agent_count: 0, // computed below, not yet available here; Phase 5 verdict reads owner-scope only
|
|
1334
1345
|
included_self: true,
|
|
1335
1346
|
},
|
|
@@ -1432,6 +1443,7 @@ function runStatus(opts) {
|
|
|
1432
1443
|
context_window: ctxUsage?.window ?? null,
|
|
1433
1444
|
timestamp_iso: new Date().toISOString(),
|
|
1434
1445
|
timestamp_local: timeStr,
|
|
1446
|
+
...(finalization ? { finalization } : {}),
|
|
1435
1447
|
};
|
|
1436
1448
|
if (opts.json) {
|
|
1437
1449
|
emit.config({ format: "json" });
|
|
@@ -1794,16 +1806,15 @@ function readCanonicalEventsInWindow(root, cutoffMs) {
|
|
|
1794
1806
|
}
|
|
1795
1807
|
return out;
|
|
1796
1808
|
}
|
|
1797
|
-
/** adapter
|
|
1798
|
-
* existing formatPlatformLabel rendering keeps working unchanged. */
|
|
1809
|
+
/** Normalize adapter event data into the heartbeat platform value. */
|
|
1799
1810
|
function adapterToPlatform(adapter) {
|
|
1800
1811
|
if (adapter === "claude-code")
|
|
1801
|
-
return "
|
|
1812
|
+
return "claude-code";
|
|
1802
1813
|
if (adapter === "cursor")
|
|
1803
1814
|
return "cursor";
|
|
1804
1815
|
if (adapter === "codex")
|
|
1805
1816
|
return "codex";
|
|
1806
|
-
return "
|
|
1817
|
+
return "claude-code";
|
|
1807
1818
|
}
|
|
1808
1819
|
/** Project a canonical health.* event into the HealEvent shape the aggregators
|
|
1809
1820
|
* already consume. Returns null for non-heal events. instance_id → display name
|
|
@@ -2292,18 +2303,7 @@ function runHealth(opts) {
|
|
|
2292
2303
|
const councilsDir = resolve(root, ".harnery/councils");
|
|
2293
2304
|
// Coordination telemetry reads from the canonical events.ndjson stream.
|
|
2294
2305
|
// Heals come from health.*; council window-activity from council.*. The
|
|
2295
|
-
// commit-guard + schema-invalid counters below have NO canonical equivalent
|
|
2296
|
-
// yet. Their future home is the decision.* events (defined in schema.ts, not
|
|
2297
|
-
// yet wired). Until then they report 0 (fields kept for output-shape
|
|
2298
|
-
// compatibility).
|
|
2299
2306
|
const heal = [];
|
|
2300
|
-
const schemaInvalid = 0;
|
|
2301
|
-
const schemaSamples = [];
|
|
2302
|
-
const commitBlocked = 0;
|
|
2303
|
-
const commitBypassed = 0;
|
|
2304
|
-
const commitSuppressed = 0;
|
|
2305
|
-
const editBlocked = 0;
|
|
2306
|
-
const shellCandidates = 0;
|
|
2307
2307
|
let councilAdvanced = 0;
|
|
2308
2308
|
let councilClosed = 0;
|
|
2309
2309
|
let councilArchived = 0;
|
|
@@ -2420,10 +2420,6 @@ function runHealth(opts) {
|
|
|
2420
2420
|
}
|
|
2421
2421
|
// Anomaly detection.
|
|
2422
2422
|
const anomalies = [];
|
|
2423
|
-
if (schemaInvalid > 0) {
|
|
2424
|
-
const sampleList = schemaSamples.slice(0, 3).join(", ");
|
|
2425
|
-
anomalies.push(`HEARTBEAT_SCHEMA_INVALID fired ${schemaInvalid}x; heartbeat shape failed validation${sampleList ? ` (samples: ${sampleList})` : ""}`);
|
|
2426
|
-
}
|
|
2427
2423
|
for (const { agent, count } of healTopAgents) {
|
|
2428
2424
|
if (count >= 5) {
|
|
2429
2425
|
anomalies.push(`${agent} self-healed ${count}x in ${opts.since}; possible idle-prune loop or PID instability`);
|
|
@@ -2477,14 +2473,6 @@ function runHealth(opts) {
|
|
|
2477
2473
|
by_platform: healByPlatform,
|
|
2478
2474
|
top_agents: healTopAgents,
|
|
2479
2475
|
},
|
|
2480
|
-
schema_invalid: { count: schemaInvalid, samples: schemaSamples },
|
|
2481
|
-
commit_guards: {
|
|
2482
|
-
blocked: commitBlocked,
|
|
2483
|
-
bypassed: commitBypassed,
|
|
2484
|
-
suppressed: commitSuppressed,
|
|
2485
|
-
edit_blocked: editBlocked,
|
|
2486
|
-
shell_candidates: shellCandidates,
|
|
2487
|
-
},
|
|
2488
2476
|
councils: {
|
|
2489
2477
|
active: activeCouncils,
|
|
2490
2478
|
archived_in_window: councilArchived,
|
|
@@ -2523,17 +2511,6 @@ function renderHealthBox(report) {
|
|
|
2523
2511
|
}
|
|
2524
2512
|
const topHealer = report.heal_events.top_agents[0];
|
|
2525
2513
|
const topHealerStr = topHealer ? `${topHealer.agent} x${topHealer.count}` : "(none)";
|
|
2526
|
-
const guardParts = [];
|
|
2527
|
-
if (report.commit_guards.blocked > 0)
|
|
2528
|
-
guardParts.push(`blocked ${report.commit_guards.blocked}`);
|
|
2529
|
-
if (report.commit_guards.bypassed > 0)
|
|
2530
|
-
guardParts.push(`bypassed ${report.commit_guards.bypassed}`);
|
|
2531
|
-
if (report.commit_guards.suppressed > 0)
|
|
2532
|
-
guardParts.push(`suppressed ${report.commit_guards.suppressed}`);
|
|
2533
|
-
if (report.commit_guards.edit_blocked > 0)
|
|
2534
|
-
guardParts.push(`edit-blocked ${report.commit_guards.edit_blocked}`);
|
|
2535
|
-
if (report.commit_guards.shell_candidates > 0)
|
|
2536
|
-
guardParts.push(`shell ${report.commit_guards.shell_candidates}`);
|
|
2537
2514
|
const councilParts = [`${report.councils.active} active`];
|
|
2538
2515
|
if (report.councils.advanced_in_window > 0)
|
|
2539
2516
|
councilParts.push(`${report.councils.advanced_in_window} advanced`);
|
|
@@ -2566,8 +2543,6 @@ function renderHealthBox(report) {
|
|
|
2566
2543
|
? "0"
|
|
2567
2544
|
: `${report.zombies.count} (${report.zombies.samples.join(", ")})`,
|
|
2568
2545
|
],
|
|
2569
|
-
["schema invalid", String(report.schema_invalid.count)],
|
|
2570
|
-
["commit guards", guardParts.length ? guardParts.join(", ") : "0"],
|
|
2571
2546
|
["councils", councilParts.join(", ")],
|
|
2572
2547
|
["anomalies", report.anomalies.length === 0 ? "(clean)" : `${report.anomalies.length} flagged`],
|
|
2573
2548
|
];
|
|
@@ -2585,10 +2560,10 @@ function runAdapterProbe(id, opts) {
|
|
|
2585
2560
|
if (opts.json)
|
|
2586
2561
|
emit.config({ format: "json" });
|
|
2587
2562
|
const adapter = id.trim();
|
|
2588
|
-
if (adapter !== "
|
|
2563
|
+
if (adapter !== "claude-code" && adapter !== "cursor") {
|
|
2589
2564
|
emit.error({
|
|
2590
2565
|
code: "bad_adapter",
|
|
2591
|
-
message: "adapter id must be
|
|
2566
|
+
message: "adapter id must be claude-code or cursor",
|
|
2592
2567
|
});
|
|
2593
2568
|
process.exit(1);
|
|
2594
2569
|
}
|
|
@@ -2784,7 +2759,7 @@ function replayAdapterSamples(adapter, root, relativeSampleDir, filter) {
|
|
|
2784
2759
|
stopFailure: "stop-failure",
|
|
2785
2760
|
StopFailure: "stop-failure",
|
|
2786
2761
|
};
|
|
2787
|
-
const adapterFlag = adapter
|
|
2762
|
+
const adapterFlag = adapter;
|
|
2788
2763
|
const sandbox = mkdtempSync(join(tmpdir(), "harn-adapter-probe-"));
|
|
2789
2764
|
const results = [];
|
|
2790
2765
|
try {
|
|
@@ -2909,15 +2884,15 @@ function extractEventAndPayload(parsed, filename) {
|
|
|
2909
2884
|
function collectPath(value, prev) {
|
|
2910
2885
|
return [...prev, value];
|
|
2911
2886
|
}
|
|
2912
|
-
/** Parse "
|
|
2887
|
+
/** Parse "30s", "5m", "1h", "2d" into milliseconds. */
|
|
2913
2888
|
function parseDurationToMs(input) {
|
|
2914
|
-
const match = input.trim().match(/^(\d+)([smhd]
|
|
2889
|
+
const match = input.trim().match(/^(\d+)([smhd])$/i);
|
|
2915
2890
|
if (!match)
|
|
2916
2891
|
return null;
|
|
2917
2892
|
const n = Number.parseInt(match[1], 10);
|
|
2918
2893
|
if (!Number.isFinite(n) || n <= 0)
|
|
2919
2894
|
return null;
|
|
2920
|
-
const unit =
|
|
2895
|
+
const unit = match[2].toLowerCase();
|
|
2921
2896
|
const mult = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
2922
2897
|
return n * mult[unit];
|
|
2923
2898
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import type { EmitContext } from "../commander.js";
|
|
4
|
+
import type { BrowserSessionLocator } from "../lib/browser/client.js";
|
|
5
|
+
interface ControlFileOpts {
|
|
6
|
+
controlFile: string;
|
|
7
|
+
}
|
|
8
|
+
interface LocatorOpts extends ControlFileOpts {
|
|
9
|
+
selector?: string;
|
|
10
|
+
role?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
partial?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function registerBrowseSessionCommand(program: Command, emit: EmitContext): void;
|
|
17
|
+
export declare function parseLocatorOptions(opts: LocatorOpts, required: true): BrowserSessionLocator;
|
|
18
|
+
export declare function parseLocatorOptions(opts: LocatorOpts, required: false): BrowserSessionLocator | undefined;
|
|
19
|
+
export declare function readFillValue(input: Readable): Promise<string>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=browse-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browse-session.d.ts","sourceRoot":"","sources":["../../src/commands/browse-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAOtE,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,WAAY,SAAQ,eAAe;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA4GtF;AAYD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,GAAG,qBAAqB,CAAC;AAC9F,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,KAAK,GACd,qBAAqB,GAAG,SAAS,CAAC;AAoDrC,wBAAsB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBpE"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { BROWSER_SESSION_MAX_FILL_BYTES, BrowserSessionError, sendBrowserSessionRequest, } from "../lib/browser/session-control.js";
|
|
2
|
+
export function registerBrowseSessionCommand(program, emit) {
|
|
3
|
+
const session = program
|
|
4
|
+
.command("browse-session")
|
|
5
|
+
.description("Control an opted-in headed browse session through an owner-only descriptor");
|
|
6
|
+
session
|
|
7
|
+
.command("status")
|
|
8
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
9
|
+
.action((opts) => runAction(emit, opts.controlFile, "status", {}));
|
|
10
|
+
addLocatorOptions(session
|
|
11
|
+
.command("inspect")
|
|
12
|
+
.description("Inspect sanitized text, controls, focus, and active-tab state")
|
|
13
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")).action((opts) => {
|
|
14
|
+
const locator = parseLocatorOptions(opts, false);
|
|
15
|
+
return runAction(emit, opts.controlFile, "inspect", locator ? { locator } : {});
|
|
16
|
+
});
|
|
17
|
+
session
|
|
18
|
+
.command("screenshot")
|
|
19
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
20
|
+
.requiredOption("--out <path>", "New PNG path in an existing directory")
|
|
21
|
+
.action((opts) => runAction(emit, opts.controlFile, "screenshot", { out: opts.out }));
|
|
22
|
+
session
|
|
23
|
+
.command("tabs")
|
|
24
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
25
|
+
.action((opts) => runAction(emit, opts.controlFile, "tabs", {}));
|
|
26
|
+
session
|
|
27
|
+
.command("select-tab")
|
|
28
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
29
|
+
.requiredOption("--index <n>", "Stable session-local tab index", parseIndex)
|
|
30
|
+
.action((opts) => runAction(emit, opts.controlFile, "select_tab", { index: opts.index }));
|
|
31
|
+
session
|
|
32
|
+
.command("open-tab <url>")
|
|
33
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
34
|
+
.action((url, opts) => runAction(emit, opts.controlFile, "open_tab", { url }));
|
|
35
|
+
session
|
|
36
|
+
.command("close-tab")
|
|
37
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
38
|
+
.requiredOption("--index <n>", "Stable session-local tab index", parseIndex)
|
|
39
|
+
.action((opts) => runAction(emit, opts.controlFile, "close_tab", { index: opts.index }));
|
|
40
|
+
session
|
|
41
|
+
.command("goto <url>")
|
|
42
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
43
|
+
.action((url, opts) => runAction(emit, opts.controlFile, "goto", { url }));
|
|
44
|
+
session
|
|
45
|
+
.command("reload")
|
|
46
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
47
|
+
.action((opts) => runAction(emit, opts.controlFile, "reload", {}));
|
|
48
|
+
addLocatorOptions(session
|
|
49
|
+
.command("click")
|
|
50
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")).action((opts) => runAction(emit, opts.controlFile, "click", { locator: parseLocatorOptions(opts, true) }));
|
|
51
|
+
addLocatorOptions(session
|
|
52
|
+
.command("fill")
|
|
53
|
+
.description("Fill one strict locator with a value read once from standard input")
|
|
54
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")).action(async (opts) => {
|
|
55
|
+
const value = await readFillValue(process.stdin);
|
|
56
|
+
return runAction(emit, opts.controlFile, "fill", {
|
|
57
|
+
locator: parseLocatorOptions(opts, true),
|
|
58
|
+
value,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
session
|
|
62
|
+
.command("press <key>")
|
|
63
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
64
|
+
.action((key, opts) => runAction(emit, opts.controlFile, "press", { key }));
|
|
65
|
+
addLocatorOptions(session
|
|
66
|
+
.command("wait")
|
|
67
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")).action((opts) => runAction(emit, opts.controlFile, "wait", { locator: parseLocatorOptions(opts, true) }));
|
|
68
|
+
session
|
|
69
|
+
.command("close")
|
|
70
|
+
.requiredOption("--control-file <path>", "Owner-only session descriptor")
|
|
71
|
+
.action((opts) => runAction(emit, opts.controlFile, "close", {}));
|
|
72
|
+
}
|
|
73
|
+
function addLocatorOptions(command) {
|
|
74
|
+
return command
|
|
75
|
+
.option("--selector <css>", "CSS locator")
|
|
76
|
+
.option("--role <role>", "Accessible role locator")
|
|
77
|
+
.option("--name <name>", "Accessible name used with --role")
|
|
78
|
+
.option("--label <text>", "Associated label locator")
|
|
79
|
+
.option("--text <text>", "Visible text locator")
|
|
80
|
+
.option("--partial", "Allow partial accessible-name, label, or text matching");
|
|
81
|
+
}
|
|
82
|
+
export function parseLocatorOptions(opts, required) {
|
|
83
|
+
const forms = [opts.selector, opts.role, opts.label, opts.text].filter((value) => value !== undefined);
|
|
84
|
+
if (forms.length === 0) {
|
|
85
|
+
if (!required) {
|
|
86
|
+
if (opts.name !== undefined || opts.partial) {
|
|
87
|
+
throw new BrowserSessionError("invalid_locator", "--name and --partial require a locator form.");
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
throw new BrowserSessionError("invalid_locator", "Choose exactly one locator: --selector, --role, --label, or --text.");
|
|
92
|
+
}
|
|
93
|
+
if (forms.length !== 1) {
|
|
94
|
+
throw new BrowserSessionError("invalid_locator", "Choose exactly one locator: --selector, --role, --label, or --text.");
|
|
95
|
+
}
|
|
96
|
+
if (opts.name !== undefined && opts.role === undefined) {
|
|
97
|
+
throw new BrowserSessionError("invalid_locator", "--name is valid only with --role.");
|
|
98
|
+
}
|
|
99
|
+
if (opts.selector !== undefined) {
|
|
100
|
+
if (opts.partial) {
|
|
101
|
+
throw new BrowserSessionError("invalid_locator", "--partial is not valid with --selector.");
|
|
102
|
+
}
|
|
103
|
+
return { kind: "selector", value: opts.selector, partial: false };
|
|
104
|
+
}
|
|
105
|
+
if (opts.role !== undefined) {
|
|
106
|
+
return {
|
|
107
|
+
kind: "role",
|
|
108
|
+
value: opts.role,
|
|
109
|
+
...(opts.name === undefined ? {} : { name: opts.name }),
|
|
110
|
+
partial: Boolean(opts.partial),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (opts.label !== undefined) {
|
|
114
|
+
return { kind: "label", value: opts.label, partial: Boolean(opts.partial) };
|
|
115
|
+
}
|
|
116
|
+
return { kind: "text", value: opts.text, partial: Boolean(opts.partial) };
|
|
117
|
+
}
|
|
118
|
+
export async function readFillValue(input) {
|
|
119
|
+
const chunks = [];
|
|
120
|
+
let size = 0;
|
|
121
|
+
for await (const chunk of input) {
|
|
122
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
123
|
+
size += bytes.length;
|
|
124
|
+
if (size > BROWSER_SESSION_MAX_FILL_BYTES) {
|
|
125
|
+
throw new BrowserSessionError("fill_too_large", "Fill input exceeds the 64 KiB limit.");
|
|
126
|
+
}
|
|
127
|
+
chunks.push(bytes);
|
|
128
|
+
}
|
|
129
|
+
const value = Buffer.concat(chunks)
|
|
130
|
+
.toString("utf8")
|
|
131
|
+
.replace(/\r?\n$/, "");
|
|
132
|
+
if (value.length === 0) {
|
|
133
|
+
throw new BrowserSessionError("fill_empty", "Fill input from standard input was empty.");
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
async function runAction(emit, controlFile, action, args) {
|
|
138
|
+
try {
|
|
139
|
+
emit.data(await sendBrowserSessionRequest(controlFile, action, args));
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (error instanceof BrowserSessionError) {
|
|
143
|
+
emit.error({ code: error.code, message: error.message });
|
|
144
|
+
emit.setExitCode(1);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
emit.error({ code: "browser_session_failed", message: "Browser session command failed." });
|
|
148
|
+
emit.setExitCode(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function parseIndex(value) {
|
|
152
|
+
const parsed = Number(value);
|
|
153
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
154
|
+
throw new BrowserSessionError("invalid_index", "Tab index must be a non-negative integer.");
|
|
155
|
+
}
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"browse.d.ts","sourceRoot":"","sources":["../../src/commands/browse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAmM1E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAkZN"}
|