harnery 0.46.1 → 0.47.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/README.md +24 -0
- package/bin/README.md +10 -0
- package/bin/agent-coord.mjs +3 -0
- package/bin/agent-hook.mjs +3 -0
- package/bin/harn.mjs +3 -0
- package/bin/run.mjs +43 -0
- package/dist/commander.d.ts +18 -6
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +74 -2
- package/dist/commands/agents.d.ts +25 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +70 -27
- package/dist/commands/autoupdate.d.ts +12 -0
- package/dist/commands/autoupdate.d.ts.map +1 -0
- package/dist/commands/autoupdate.js +190 -0
- package/dist/commands/computer.d.ts +45 -0
- package/dist/commands/computer.d.ts.map +1 -0
- package/dist/commands/computer.js +509 -0
- package/dist/commands/docs.js +3 -1
- package/dist/commands/fetch.js +11 -1
- package/dist/commands/grep.d.ts +72 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +200 -47
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +15 -9
- package/dist/commands/rm.js +2 -2
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +2 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -3
- package/dist/core/agents/coord-bin.d.ts +3 -0
- package/dist/core/agents/coord-bin.d.ts.map +1 -1
- package/dist/core/agents/coord-bin.js +6 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +6 -2
- package/dist/core/agents/render/prompt-context.d.ts +55 -7
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +122 -48
- package/dist/core/agents/state/proc-start.d.ts +3 -1
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +26 -4
- package/dist/core/autoupdate/engine.d.ts +97 -0
- package/dist/core/autoupdate/engine.d.ts.map +1 -0
- package/dist/core/autoupdate/engine.js +548 -0
- package/dist/core/autoupdate/fingerprint.d.ts +16 -0
- package/dist/core/autoupdate/fingerprint.d.ts.map +1 -0
- package/dist/core/autoupdate/fingerprint.js +73 -0
- package/dist/core/autoupdate/git.d.ts +30 -0
- package/dist/core/autoupdate/git.d.ts.map +1 -0
- package/dist/core/autoupdate/git.js +82 -0
- package/dist/core/autoupdate/index.d.ts +6 -0
- package/dist/core/autoupdate/index.d.ts.map +1 -0
- package/dist/core/autoupdate/index.js +4 -0
- package/dist/core/autoupdate/state.d.ts +59 -0
- package/dist/core/autoupdate/state.d.ts.map +1 -0
- package/dist/core/autoupdate/state.js +214 -0
- package/dist/core/autoupdate/types.d.ts +113 -0
- package/dist/core/autoupdate/types.d.ts.map +1 -0
- package/dist/core/autoupdate/types.js +35 -0
- package/dist/core/config.d.ts +53 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +114 -1
- package/dist/core/events/v3/bootstrap.js +1 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +1 -0
- package/dist/core/hooks/adapter/wiring.d.ts +12 -2
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +38 -8
- package/dist/core/hooks/autoupdate-cue.d.ts +29 -0
- package/dist/core/hooks/autoupdate-cue.d.ts.map +1 -0
- package/dist/core/hooks/autoupdate-cue.js +53 -0
- package/dist/core/hooks/cli.js +28 -3
- package/dist/core/hooks/effects/index.js +4 -4
- package/dist/core/hooks/resolve/coord-root.js +1 -0
- package/dist/core/semantic/contract.d.ts +3 -3
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +4 -2
- package/dist/core/storage/modes.d.ts +1 -1
- package/dist/core/storage/modes.d.ts.map +1 -1
- package/dist/core/storage/modes.js +5 -1
- package/dist/core/workflow/durable-record.d.ts +2 -0
- package/dist/core/workflow/durable-record.d.ts.map +1 -1
- package/dist/core/workflow/durable-record.js +13 -1
- package/dist/core/workflow/engine.js +4 -0
- package/dist/core/workflow/spawn-claude.d.ts +7 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +16 -4
- package/dist/core/workflow/specialists.d.ts.map +1 -1
- package/dist/core/workflow/specialists.js +1 -0
- package/dist/core/workflow/types.d.ts +10 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +1 -9
- package/dist/lib/computer/cua.d.ts +50 -0
- package/dist/lib/computer/cua.d.ts.map +1 -0
- package/dist/lib/computer/cua.js +474 -0
- package/dist/lib/computer/host.d.ts +19 -0
- package/dist/lib/computer/host.d.ts.map +1 -0
- package/dist/lib/computer/host.js +72 -0
- package/dist/lib/computer/lease.d.ts +13 -0
- package/dist/lib/computer/lease.d.ts.map +1 -0
- package/dist/lib/computer/lease.js +269 -0
- package/dist/lib/computer/types.d.ts +163 -0
- package/dist/lib/computer/types.d.ts.map +1 -0
- package/dist/lib/computer/types.js +10 -0
- package/dist/lib/docs-metadata-v2.d.ts.map +1 -1
- package/dist/lib/docs-metadata-v2.js +6 -5
- package/dist/lib/exec.d.ts.map +1 -1
- package/dist/lib/exec.js +1 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +5 -4
- package/dist/lib/headless/index.d.ts +33 -0
- package/dist/lib/headless/index.d.ts.map +1 -1
- package/dist/lib/headless/index.js +38 -2
- package/dist/lib/http/client.d.ts +2 -9
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +30 -9
- package/dist/lib/instructions/apply.d.ts +9 -0
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +92 -14
- package/dist/lib/instructions/git-hooks.d.ts.map +1 -1
- package/dist/lib/instructions/git-hooks.js +8 -4
- package/dist/lib/instructions/templates.d.ts +11 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +81 -1
- package/package.json +5 -5
- package/schemas/config.schema.json +89 -1
- package/src/commander.ts +98 -8
- package/src/commands/agents.ts +100 -32
- package/src/commands/autoupdate.ts +250 -0
- package/src/commands/computer.ts +682 -0
- package/src/commands/docs.ts +3 -1
- package/src/commands/fetch.ts +11 -1
- package/src/commands/grep.ts +274 -59
- package/src/commands/init.ts +17 -7
- package/src/commands/rm.ts +2 -2
- package/src/commands/web.ts +2 -0
- package/src/core/agents/canonical-emit.ts +2 -3
- package/src/core/agents/coord-bin.ts +30 -0
- package/src/core/agents/coord-client.ts +6 -2
- package/src/core/agents/render/prompt-context.ts +152 -53
- package/src/core/agents/state/proc-start.ts +29 -5
- package/src/core/autoupdate/engine.ts +691 -0
- package/src/core/autoupdate/fingerprint.ts +89 -0
- package/src/core/autoupdate/git.ts +120 -0
- package/src/core/autoupdate/index.ts +29 -0
- package/src/core/autoupdate/state.ts +269 -0
- package/src/core/autoupdate/types.ts +135 -0
- package/src/core/config.ts +142 -1
- package/src/core/events/v3/bootstrap.ts +1 -0
- package/src/core/events/v3/runtime-build.ts +1 -0
- package/src/core/hooks/adapter/wiring.ts +51 -6
- package/src/core/hooks/autoupdate-cue.ts +72 -0
- package/src/core/hooks/cli.ts +27 -1
- package/src/core/hooks/effects/index.ts +4 -4
- package/src/core/hooks/resolve/coord-root.ts +1 -0
- package/src/core/semantic/service.ts +6 -2
- package/src/core/storage/modes.ts +7 -1
- package/src/core/workflow/durable-record.ts +15 -1
- package/src/core/workflow/engine.ts +3 -0
- package/src/core/workflow/spawn-claude.ts +16 -4
- package/src/core/workflow/specialists.ts +1 -0
- package/src/core/workflow/types.ts +10 -0
- package/src/core/workflow/workspaces/leases.ts +1 -9
- package/src/lib/computer/cua.ts +572 -0
- package/src/lib/computer/host.ts +87 -0
- package/src/lib/computer/lease.ts +299 -0
- package/src/lib/computer/types.ts +177 -0
- package/src/lib/docs-metadata-v2.ts +24 -5
- package/src/lib/exec.ts +1 -0
- package/src/lib/guarded-remove.ts +5 -3
- package/src/lib/headless/index.ts +66 -2
- package/src/lib/http/client.ts +33 -19
- package/src/lib/instructions/apply.ts +100 -12
- package/src/lib/instructions/git-hooks.ts +8 -4
- package/src/lib/instructions/templates.ts +95 -1
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* PATH" is not "working", and under parallel load the first backend can
|
|
27
27
|
* flake on a fraction of calls while the second runs clean.
|
|
28
28
|
*
|
|
29
|
+
* - A claude-code call loads NO MCP servers and no claude.ai connectors by
|
|
30
|
+
* default (`claudeMcpLaunch`). Connectors attach about 130 tool schemas to
|
|
31
|
+
* every turn of a one-shot call that never uses them; a caller that needs
|
|
32
|
+
* one opts in per request with `mcp: "inherit"` or an explicit server map.
|
|
33
|
+
*
|
|
29
34
|
* Env knobs (read through coordEnv, so the HARNERY_ prefix stays in one
|
|
30
35
|
* place): HARNERY_HEADLESS_BACKEND forces one backend and disables the
|
|
31
36
|
* fallback walk; HARNERY_HEADLESS_MODEL overrides the model passed to
|
|
@@ -38,6 +43,33 @@ import { delimiter, isAbsolute, join } from "node:path";
|
|
|
38
43
|
import { promisify } from "node:util";
|
|
39
44
|
import { coordEnv } from "../env.js";
|
|
40
45
|
const execFileAsync = promisify(execFile);
|
|
46
|
+
/** The two guards that keep a `claude` child free of MCP servers: strict MCP
|
|
47
|
+
* config with an empty server map, plus the env switch that stops claude.ai
|
|
48
|
+
* connectors attaching mid-session (the flag alone does not cover them). */
|
|
49
|
+
export const CLAUDE_NO_MCP_ARGS = [
|
|
50
|
+
"--strict-mcp-config",
|
|
51
|
+
"--mcp-config",
|
|
52
|
+
'{"mcpServers":{}}',
|
|
53
|
+
];
|
|
54
|
+
export const CLAUDE_NO_CONNECTORS_ENV = {
|
|
55
|
+
ENABLE_CLAUDEAI_MCP_SERVERS: "false",
|
|
56
|
+
};
|
|
57
|
+
/** Resolve an MCP mode to the argv and env additions for a `claude` child.
|
|
58
|
+
* Throws on a malformed server map rather than launching with a guess. */
|
|
59
|
+
export function claudeMcpLaunch(mode = "none") {
|
|
60
|
+
if (mode === "inherit")
|
|
61
|
+
return { args: [], env: {} };
|
|
62
|
+
if (mode === "none")
|
|
63
|
+
return { args: [...CLAUDE_NO_MCP_ARGS], env: { ...CLAUDE_NO_CONNECTORS_ENV } };
|
|
64
|
+
const servers = mode?.mcpServers;
|
|
65
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
66
|
+
throw new Error('mcp must be "none", "inherit", or { mcpServers: { <name>: <server config> } }');
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
args: ["--strict-mcp-config", "--mcp-config", JSON.stringify({ mcpServers: servers })],
|
|
70
|
+
env: { ...CLAUDE_NO_CONNECTORS_ENV },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
41
73
|
const MAX_BUFFER = 32 * 1024 * 1024;
|
|
42
74
|
const DEFAULT_TIMEOUT_MS = 240_000;
|
|
43
75
|
const DEFAULT_MAX_TURNS = 4;
|
|
@@ -90,15 +122,17 @@ function stageCall(req) {
|
|
|
90
122
|
model: coordEnv("HEADLESS_MODEL") ?? req.model,
|
|
91
123
|
timeoutMs: req.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
92
124
|
maxTurns: req.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
125
|
+
...(req.mcp ? { mcp: req.mcp } : {}),
|
|
93
126
|
};
|
|
94
127
|
}
|
|
95
128
|
/** Run a harness CLI from the neutral dir with stdin CLOSED (see module
|
|
96
129
|
* comment: an open stdin pipe deadlocks codex). */
|
|
97
|
-
function execHarness(bin, args, call) {
|
|
130
|
+
function execHarness(bin, args, call, extraEnv = {}) {
|
|
98
131
|
const pending = execFileAsync(bin, args, {
|
|
99
132
|
cwd: call.dir,
|
|
100
133
|
timeout: call.timeoutMs,
|
|
101
134
|
maxBuffer: MAX_BUFFER,
|
|
135
|
+
...(Object.keys(extraEnv).length ? { env: { ...process.env, ...extraEnv } } : {}),
|
|
102
136
|
});
|
|
103
137
|
pending.child.stdin?.end();
|
|
104
138
|
return pending;
|
|
@@ -114,7 +148,9 @@ async function runClaudeCode(bin, call) {
|
|
|
114
148
|
args.push("--allowedTools", "Read");
|
|
115
149
|
if (call.model)
|
|
116
150
|
args.push("--model", call.model);
|
|
117
|
-
const
|
|
151
|
+
const mcp = claudeMcpLaunch(call.mcp);
|
|
152
|
+
args.push(...mcp.args);
|
|
153
|
+
const { stdout } = await execHarness(bin, args, call, mcp.env);
|
|
118
154
|
const envelope = JSON.parse(stdout);
|
|
119
155
|
if (envelope.is_error) {
|
|
120
156
|
throw new Error(typeof envelope.result === "string" ? envelope.result : "claude-code: headless error");
|
|
@@ -44,15 +44,6 @@ export interface FetchOptions<TResponseType extends FetchResponseType = "text">
|
|
|
44
44
|
extraCookies?: ExtraCookies;
|
|
45
45
|
/** Response body representation. Default `text`; use `bytes` for lossless file output. */
|
|
46
46
|
responseType?: TResponseType;
|
|
47
|
-
/**
|
|
48
|
-
* With `redirect: "follow"`, follow redirects in this client instead of the
|
|
49
|
-
* runtime's fetch. Every hop then gets its own cookie header, `extraHeaders`
|
|
50
|
-
* call, and Set-Cookie persistence, so a caller that inspects those can stop
|
|
51
|
-
* a chain between hops. Credential headers are dropped on a cross-origin hop.
|
|
52
|
-
* Default: on whenever a `jar` is passed (host-only cookies must follow the
|
|
53
|
-
* jar's exact-host rule on every hop), off without one.
|
|
54
|
-
*/
|
|
55
|
-
clientRedirects?: boolean;
|
|
56
47
|
}
|
|
57
48
|
type FetchBody<TResponseType extends FetchResponseType> = TResponseType extends "bytes" ? Uint8Array : string;
|
|
58
49
|
export interface FetchResult<TBody = string> {
|
|
@@ -64,6 +55,8 @@ export interface FetchResult<TBody = string> {
|
|
|
64
55
|
/** Number of cookies persisted back into the jar (0 if no jar passed). */
|
|
65
56
|
cookiesSaved: number;
|
|
66
57
|
}
|
|
58
|
+
/** Caller headers that survive a redirect to a different origin (lowercase). */
|
|
59
|
+
export declare const CROSS_ORIGIN_REDIRECT_HEADERS: ReadonlySet<string>;
|
|
67
60
|
export declare function fetchWithJar<TResponseType extends FetchResponseType = "text">(url: string, opts?: FetchOptions<TResponseType>): Promise<FetchResult<FetchBody<TResponseType>>>;
|
|
68
61
|
export {};
|
|
69
62
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/http/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,MAAM,WAAW,YAAY,CAAC,aAAa,SAAS,iBAAiB,GAAG,MAAM;IAC5E,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,GAAG,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/http/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;GASG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,MAAM,WAAW,YAAY,CAAC,aAAa,SAAS,iBAAiB,GAAG,MAAM;IAC5E,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,GAAG,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD;;;;OAIG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,KAAK,SAAS,CAAC,aAAa,SAAS,iBAAiB,IAAI,aAAa,SAAS,OAAO,GACnF,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,WAAW,WAAW,CAAC,KAAK,GAAG,MAAM;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,KAAK,CAAC;IACZ,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;CACtB;AA2BD,gFAAgF;AAChF,eAAO,MAAM,6BAA6B,EAAE,WAAW,CAAC,MAAM,CAK5D,CAAC;AAEH,wBAAsB,YAAY,CAAC,aAAa,SAAS,iBAAiB,GAAG,MAAM,EACjF,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,YAAY,CAAC,aAAa,CAAM,GACrC,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAuChD"}
|
package/dist/lib/http/client.js
CHANGED
|
@@ -6,19 +6,35 @@ import { applyExtraCookies, domainMatches, violatesCookiePrefix, } from "../cook
|
|
|
6
6
|
* Pass `responseType: "bytes"` when the caller must preserve the body exactly.
|
|
7
7
|
* Streams aren't supported; this is a CLI helper, not a streaming HTTP
|
|
8
8
|
* client. Use Bun's `fetch` directly for streaming workloads.
|
|
9
|
+
*
|
|
10
|
+
* With `redirect: "follow"` (the default) this client follows redirects
|
|
11
|
+
* itself instead of the runtime's fetch, with or without a jar, so one
|
|
12
|
+
* policy governs every hop:
|
|
13
|
+
* - Each hop gets its own jar Cookie header (exact-host for host-only
|
|
14
|
+
* cookies), its own `extraHeaders(url)` call for the hop's URL, and its
|
|
15
|
+
* own Set-Cookie persistence. A callback that throws stops the chain
|
|
16
|
+
* before the next request.
|
|
17
|
+
* - A same-origin hop keeps every caller header.
|
|
18
|
+
* - A cross-origin hop keeps only the caller headers in
|
|
19
|
+
* `CROSS_ORIGIN_REDIRECT_HEADERS`; credentials, bypass tokens, and custom
|
|
20
|
+
* `X-*` headers are dropped for the rest of the chain.
|
|
21
|
+
* - A cross-origin 307 or 308 on a request that is not GET or HEAD throws
|
|
22
|
+
* instead of resending the body to the new origin.
|
|
9
23
|
*/
|
|
10
24
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
11
25
|
const MAX_CLIENT_REDIRECTS = 20;
|
|
26
|
+
/** Caller headers that survive a redirect to a different origin (lowercase). */
|
|
27
|
+
export const CROSS_ORIGIN_REDIRECT_HEADERS = new Set([
|
|
28
|
+
"user-agent",
|
|
29
|
+
"accept",
|
|
30
|
+
"accept-language",
|
|
31
|
+
"range",
|
|
32
|
+
]);
|
|
12
33
|
export async function fetchWithJar(url, opts = {}) {
|
|
13
|
-
|
|
14
|
-
// header from the jar (exact-host for host-only cookies) and its own
|
|
15
|
-
// Set-Cookie persistence; the runtime would otherwise carry or drop the
|
|
16
|
-
// first hop's header on its own rules. Pass `clientRedirects: false` to opt out.
|
|
17
|
-
const clientRedirects = opts.clientRedirects ?? Boolean(opts.jar);
|
|
18
|
-
if (!clientRedirects || (opts.redirect ?? "follow") !== "follow")
|
|
34
|
+
if ((opts.redirect ?? "follow") !== "follow")
|
|
19
35
|
return fetchOnce(url, opts);
|
|
20
36
|
let current = url;
|
|
21
|
-
let method = opts.method ?? "GET";
|
|
37
|
+
let method = (opts.method ?? "GET").toUpperCase();
|
|
22
38
|
let body = opts.body;
|
|
23
39
|
let headers = opts.headers;
|
|
24
40
|
let cookiesSaved = 0;
|
|
@@ -32,13 +48,18 @@ export async function fetchWithJar(url, opts = {}) {
|
|
|
32
48
|
throw new Error(`Too many redirects (more than ${MAX_CLIENT_REDIRECTS}) starting at ${url}`);
|
|
33
49
|
}
|
|
34
50
|
const next = new URL(location, current);
|
|
51
|
+
const crossOrigin = next.origin !== new URL(current).origin;
|
|
35
52
|
if (result.status === 303 ||
|
|
36
53
|
((result.status === 301 || result.status === 302) && method !== "GET" && method !== "HEAD")) {
|
|
37
54
|
method = "GET";
|
|
38
55
|
body = undefined;
|
|
39
56
|
}
|
|
40
|
-
if (
|
|
41
|
-
|
|
57
|
+
else if (crossOrigin && method !== "GET" && method !== "HEAD") {
|
|
58
|
+
throw new Error(`Refusing to follow a ${result.status} redirect of a ${method} request from ${new URL(current).origin} to ${next.origin}: ` +
|
|
59
|
+
"it would resend the request body to a different origin. Send the request to the new URL directly if that is intended.");
|
|
60
|
+
}
|
|
61
|
+
if (crossOrigin && headers) {
|
|
62
|
+
headers = Object.fromEntries(Object.entries(headers).filter(([name]) => CROSS_ORIGIN_REDIRECT_HEADERS.has(name.toLowerCase())));
|
|
42
63
|
}
|
|
43
64
|
current = next.href;
|
|
44
65
|
}
|
|
@@ -37,6 +37,15 @@ export interface ApplyResult {
|
|
|
37
37
|
* writes nothing. `dryRun` reports without touching the fs.
|
|
38
38
|
*/
|
|
39
39
|
export declare function applyInstructions(projectRoot: string, opts: ApplyOpts): ApplyResult;
|
|
40
|
+
/**
|
|
41
|
+
* Warn when instructions are being installed in a folder that is not a Git
|
|
42
|
+
* work tree but holds projects carrying their own Harnery instructions block.
|
|
43
|
+
* Harnesses that read instruction files from parent folders (Claude Code walks
|
|
44
|
+
* up for CLAUDE.md) would load this block on top of each project's own, and
|
|
45
|
+
* with the wrong command name when a project pins a different one. Installing
|
|
46
|
+
* in a non-Git folder stays supported; this only names the overlap.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parentScopeWarning(projectRoot: string): string | null;
|
|
40
49
|
interface RemoveOpts {
|
|
41
50
|
adapter: string;
|
|
42
51
|
dryRun: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAiGH,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAYlE;AA0CD,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CAiJnF;AAYD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA2BrE;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,CAqFrF;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC,WAAW,CAmEb"}
|
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
* `skills.exclude` in `.harnery/config.jsonc`. Claude Code uses
|
|
21
21
|
* `.claude/skills/`; Cursor and Codex use `.agents/skills/`.
|
|
22
22
|
*/
|
|
23
|
+
import { spawnSync } from "node:child_process";
|
|
23
24
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
24
25
|
import { dirname, join } from "node:path";
|
|
25
|
-
import { endOfTurnStatusCommand, stripJsonComments } from "../../core/config.js";
|
|
26
|
+
import { endOfTurnStatusCommand, readComputerConfig, stripJsonComments, } from "../../core/config.js";
|
|
26
27
|
import { readHostAddendum } from "./host-addendum.js";
|
|
27
28
|
import { checkOwnedSkill, checkRegion, isOwnedFile, removeRegion, shortHash, spliceRegion, } from "./splice.js";
|
|
28
29
|
import { HOST_ADDENDUM_REGION, IMPORT_REGION, INSTRUCTIONS_REGION, renderInstructionsBlock, SKILLS, } from "./templates.js";
|
|
@@ -101,12 +102,36 @@ export function readSkillsExclude(projectRoot) {
|
|
|
101
102
|
function blockSkills(projectRoot, adapter) {
|
|
102
103
|
const supported = adapterSkillsDir(adapter) !== null;
|
|
103
104
|
const exclude = readSkillsExclude(projectRoot);
|
|
104
|
-
|
|
105
|
+
const skills = {
|
|
105
106
|
decide: supported && !exclude.has("harn-decide"),
|
|
106
107
|
council: supported && !exclude.has("harn-council"),
|
|
107
108
|
end: supported && !exclude.has("harn-end"),
|
|
108
109
|
team: supported && !exclude.has("harn-team"),
|
|
109
110
|
};
|
|
111
|
+
// Only an enabled project's block mentions the opt-in skill, so every other
|
|
112
|
+
// project's block stays byte-identical.
|
|
113
|
+
if (supported && !exclude.has("harn-computer") && readComputerConfig(projectRoot).enabled) {
|
|
114
|
+
skills.computer = true;
|
|
115
|
+
}
|
|
116
|
+
return skills;
|
|
117
|
+
}
|
|
118
|
+
/** False for an opt-in skill whose config gate is off in this project. */
|
|
119
|
+
function skillOptedIn(projectRoot, skill) {
|
|
120
|
+
if (skill.optIn === "computer")
|
|
121
|
+
return readComputerConfig(projectRoot).enabled;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
/** Delete an owned skill file and its now-empty harn-* dir (adapter root stays). */
|
|
125
|
+
function removeOwnedSkillFile(skillPath) {
|
|
126
|
+
rmSync(skillPath);
|
|
127
|
+
const dir = dirname(skillPath);
|
|
128
|
+
try {
|
|
129
|
+
if (readdirSync(dir).length === 0)
|
|
130
|
+
rmdirSync(dir);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* dir not empty or gone → leave it */
|
|
134
|
+
}
|
|
110
135
|
}
|
|
111
136
|
/**
|
|
112
137
|
* Inject / refresh the instructions block, the CLAUDE.md import shim (claude-code),
|
|
@@ -123,6 +148,9 @@ export function applyInstructions(projectRoot, opts) {
|
|
|
123
148
|
// Resolve the host addendum before touching a single file, so a bad path
|
|
124
149
|
// aborts the whole run instead of leaving AGENTS.md half-updated.
|
|
125
150
|
const addendum = readHostAddendum(projectRoot);
|
|
151
|
+
const parentScope = parentScopeWarning(projectRoot);
|
|
152
|
+
if (parentScope)
|
|
153
|
+
warnings.push(parentScope);
|
|
126
154
|
// ── AGENTS.md orientation block ─────────────────────────────────────────
|
|
127
155
|
const agentsPath = join(projectRoot, AGENTS_FILE);
|
|
128
156
|
const agentsExisted = existsSync(agentsPath);
|
|
@@ -221,6 +249,15 @@ export function applyInstructions(projectRoot, opts) {
|
|
|
221
249
|
continue;
|
|
222
250
|
}
|
|
223
251
|
const skillPath = join(projectRoot, skillsDir, skill.relPath);
|
|
252
|
+
if (!skillOptedIn(projectRoot, skill)) {
|
|
253
|
+
// Turning the gate off takes back a skill an earlier init wrote.
|
|
254
|
+
if (existsSync(skillPath) && isOwnedFile(readFileSync(skillPath, "utf8"))) {
|
|
255
|
+
if (!opts.dryRun)
|
|
256
|
+
removeOwnedSkillFile(skillPath);
|
|
257
|
+
actions.push(`+ ${verbed("remove", "removed")} skill ${skill.id} (${skill.optIn}.enabled is off)`);
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
224
261
|
const content = skill.render(opts.binName);
|
|
225
262
|
const before = existsSync(skillPath) ? readFileSync(skillPath, "utf8") : null;
|
|
226
263
|
if (before === content) {
|
|
@@ -237,6 +274,51 @@ export function applyInstructions(projectRoot, opts) {
|
|
|
237
274
|
}
|
|
238
275
|
return { actions, warnings };
|
|
239
276
|
}
|
|
277
|
+
/** True when `dir` is inside a real Git work tree (an empty `.git` folder does not count). */
|
|
278
|
+
function insideGitWorkTree(dir) {
|
|
279
|
+
const r = spawnSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
|
|
280
|
+
encoding: "utf8",
|
|
281
|
+
});
|
|
282
|
+
return r.status === 0 && r.stdout.trim() === "true";
|
|
283
|
+
}
|
|
284
|
+
const PARENT_SCAN_SKIP = new Set(["node_modules"]);
|
|
285
|
+
/**
|
|
286
|
+
* Warn when instructions are being installed in a folder that is not a Git
|
|
287
|
+
* work tree but holds projects carrying their own Harnery instructions block.
|
|
288
|
+
* Harnesses that read instruction files from parent folders (Claude Code walks
|
|
289
|
+
* up for CLAUDE.md) would load this block on top of each project's own, and
|
|
290
|
+
* with the wrong command name when a project pins a different one. Installing
|
|
291
|
+
* in a non-Git folder stays supported; this only names the overlap.
|
|
292
|
+
*/
|
|
293
|
+
export function parentScopeWarning(projectRoot) {
|
|
294
|
+
if (insideGitWorkTree(projectRoot))
|
|
295
|
+
return null;
|
|
296
|
+
let entries;
|
|
297
|
+
try {
|
|
298
|
+
entries = readdirSync(projectRoot, { withFileTypes: true })
|
|
299
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !PARENT_SCAN_SKIP.has(e.name))
|
|
300
|
+
.map((e) => e.name)
|
|
301
|
+
.sort();
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
const marker = `harnery:begin ${INSTRUCTIONS_REGION}`;
|
|
307
|
+
const children = entries.filter((name) => {
|
|
308
|
+
try {
|
|
309
|
+
return readFileSync(join(projectRoot, name, AGENTS_FILE), "utf8").includes(marker);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
if (children.length === 0)
|
|
316
|
+
return null;
|
|
317
|
+
const listed = children.slice(0, 5).join(", ") + (children.length > 5 ? `, +${children.length - 5} more` : "");
|
|
318
|
+
return (`${projectRoot} is not a Git work tree and contains ${children.length} project(s) with their own ` +
|
|
319
|
+
`Harnery instructions (${listed}). Harnesses that read parent folders, such as Claude Code, will load ` +
|
|
320
|
+
`this block on top of theirs. Run init inside each project instead, or pass --project-root <project>.`);
|
|
321
|
+
}
|
|
240
322
|
/**
|
|
241
323
|
* Reverse {@link applyInstructions}: strip the AGENTS.md block, the CLAUDE.md
|
|
242
324
|
* import shim, and delete the shipped skill files (only ones harnery generated).
|
|
@@ -317,18 +399,8 @@ export function removeInstructions(projectRoot, opts) {
|
|
|
317
399
|
warnings.push(`left ${skill.relPath} (hand-edited; no harnery ownership marker)`);
|
|
318
400
|
continue;
|
|
319
401
|
}
|
|
320
|
-
if (!opts.dryRun)
|
|
321
|
-
|
|
322
|
-
// Drop the now-empty harn-* dir, leaving the adapter skills root intact.
|
|
323
|
-
const dir = dirname(skillPath);
|
|
324
|
-
try {
|
|
325
|
-
if (readdirSync(dir).length === 0)
|
|
326
|
-
rmdirSync(dir);
|
|
327
|
-
}
|
|
328
|
-
catch {
|
|
329
|
-
/* dir not empty or gone → leave it */
|
|
330
|
-
}
|
|
331
|
-
}
|
|
402
|
+
if (!opts.dryRun)
|
|
403
|
+
removeOwnedSkillFile(skillPath);
|
|
332
404
|
actions.push(`+ ${opts.dryRun ? "would delete" : "deleted"} skill ${skill.id}`);
|
|
333
405
|
}
|
|
334
406
|
}
|
|
@@ -370,6 +442,12 @@ export function checkInstructions(projectRoot, opts) {
|
|
|
370
442
|
continue;
|
|
371
443
|
const skillPath = join(projectRoot, skillsDir, skill.relPath);
|
|
372
444
|
const c = existsSync(skillPath) ? readFileSync(skillPath, "utf8") : "";
|
|
445
|
+
if (!skillOptedIn(projectRoot, skill)) {
|
|
446
|
+
if (isOwnedFile(c)) {
|
|
447
|
+
issues.push(`skill ${skill.id}: present but ${skill.optIn}.enabled is off (re-run init)`);
|
|
448
|
+
}
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
373
451
|
note(`skill ${skill.id}`, checkOwnedSkill(c, skillBody(skill.render, opts.binName)));
|
|
374
452
|
}
|
|
375
453
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-hooks.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/git-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAEL,KAAK,aAAa,EAInB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,yDAIgB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,CAAC;AAM7B;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"git-hooks.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/git-hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAEL,KAAK,aAAa,EAInB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,eAAe,yDAIgB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,CAAC;AAM7B;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAqB7D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWlE;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,CAoE7F;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAa9D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CA2B9F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,CA8B9F"}
|
|
@@ -41,17 +41,20 @@ function regionName(event) {
|
|
|
41
41
|
*/
|
|
42
42
|
export function renderGitHookBody(event) {
|
|
43
43
|
const blocking = event === "pre-commit";
|
|
44
|
+
const native = process.platform === "win32";
|
|
45
|
+
const command = `${native ? "node " : ""}"$harnery_hook_bin"`;
|
|
46
|
+
const extension = native ? ".mjs" : "";
|
|
44
47
|
const invoke = blocking
|
|
45
|
-
? `
|
|
48
|
+
? ` ${command} git-hook ${event} "$@"\n` +
|
|
46
49
|
` harnery_hook_rc=$?\n` +
|
|
47
50
|
` [ "$harnery_hook_rc" -ne 0 ] && exit "$harnery_hook_rc"\n`
|
|
48
|
-
: `
|
|
51
|
+
: ` ${command} git-hook ${event} "$@" >/dev/null 2>&1 || true\n`;
|
|
49
52
|
return (`# Coordination for this hook lives in harnery (agent-coord git-hook ${event});\n` +
|
|
50
53
|
`# upgrading harnery upgrades the behavior. Managed region: do not edit by hand.\n` +
|
|
51
54
|
`harnery_hook_root=$(git rev-parse --show-superproject-working-tree 2>/dev/null)\n` +
|
|
52
55
|
`[ -n "$harnery_hook_root" ] || harnery_hook_root=$(git rev-parse --show-toplevel 2>/dev/null)\n` +
|
|
53
|
-
`for harnery_hook_bin in "$harnery_hook_root/harnery/bin/agent-coord" "$harnery_hook_root/node_modules/harnery/bin/agent-coord"; do\n` +
|
|
54
|
-
` [
|
|
56
|
+
`for harnery_hook_bin in "$harnery_hook_root/harnery/bin/agent-coord${extension}" "$harnery_hook_root/node_modules/harnery/bin/agent-coord${extension}"; do\n` +
|
|
57
|
+
` [ -${native ? "f" : "x"} "$harnery_hook_bin" ] || continue\n` +
|
|
55
58
|
invoke +
|
|
56
59
|
` break\n` +
|
|
57
60
|
`done`);
|
|
@@ -64,6 +67,7 @@ export function renderGitHookBody(event) {
|
|
|
64
67
|
*/
|
|
65
68
|
export function resolveHooksDir(projectRoot) {
|
|
66
69
|
const r = spawnSync("git", ["rev-parse", "--git-path", "hooks"], {
|
|
70
|
+
windowsHide: true,
|
|
67
71
|
cwd: projectRoot,
|
|
68
72
|
encoding: "utf8",
|
|
69
73
|
timeout: 5000,
|
|
@@ -31,6 +31,11 @@ export interface BlockSkills {
|
|
|
31
31
|
end: boolean;
|
|
32
32
|
/** the `harn-team` skill file is present */
|
|
33
33
|
team: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* the opt-in `harn-computer` skill file is present (only when the project
|
|
36
|
+
* sets `computer.enabled`); absent from the default block entirely
|
|
37
|
+
*/
|
|
38
|
+
computer?: boolean;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* The always-on orientation spliced into `AGENTS.md`. Target ≤ 80 rendered
|
|
@@ -51,6 +56,12 @@ export interface SkillTemplate {
|
|
|
51
56
|
/** path under the adapter skill dir, e.g. `harn-decide/SKILL.md` */
|
|
52
57
|
relPath: string;
|
|
53
58
|
render: (binName: string) => string;
|
|
59
|
+
/**
|
|
60
|
+
* Config gate for an opt-in skill. `"computer"` renders only when the project
|
|
61
|
+
* sets `computer.enabled: true`; ungated skills always render (minus
|
|
62
|
+
* `skills.exclude`).
|
|
63
|
+
*/
|
|
64
|
+
optIn?: "computer";
|
|
54
65
|
}
|
|
55
66
|
export declare const SKILLS: SkillTemplate[];
|
|
56
67
|
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,GAAG,EAAE,OAAO,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,GAAG,EAAE,OAAO,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,WAAoE,GAC3E,MAAM,CAsHR;AAID,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC;;;;OAIG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAuZD,eAAO,MAAM,MAAM,EAAE,aAAa,EA8DjC,CAAC"}
|
|
@@ -58,6 +58,11 @@ export function renderInstructionsBlock(binName, skills = { decide: true, counci
|
|
|
58
58
|
const teamPointer = skills.team
|
|
59
59
|
? "The `harn-team` skill owns the tier choice, the governor artifacts, and the drive loop."
|
|
60
60
|
: `See \`${b} governor --help\` for the tier choice and the drive loop.`;
|
|
61
|
+
// Opt-in surface: the default block stays byte-identical unless the project
|
|
62
|
+
// enabled computer use and the skill is actually installed.
|
|
63
|
+
const computerPointer = skills.computer
|
|
64
|
+
? `\n\n**Desktop control.** This project enables \`${b} computer\` for driving native desktop apps and real browsers as a last resort. The \`harn-computer\` skill has the loop, and the desktop is one shared resource held by one agent at a time.`
|
|
65
|
+
: "";
|
|
61
66
|
// Render the journal categories from the canonical enum so this prose can
|
|
62
67
|
// never drift from what `journal add` actually accepts (the "note, plan…" list
|
|
63
68
|
// silently lagged the tool by two categories before this).
|
|
@@ -143,7 +148,7 @@ can't resolve from the repo, file it instead. \`${b} decision file "<question>"\
|
|
|
143
148
|
records it and lets you proceed on a stated default; \`${b} decision search "<terms>"\`
|
|
144
149
|
surfaces prior decisions, so check for precedent before re-deciding. ${decidePointer}
|
|
145
150
|
|
|
146
|
-
**Councils.** For a hard or contested decision, convene a council of agents. \`${b} council create "<objective>"\` runs structured rounds toward a decision. ${councilPointer}`;
|
|
151
|
+
**Councils.** For a hard or contested decision, convene a council of agents. \`${b} council create "<objective>"\` runs structured rounds toward a decision. ${councilPointer}${computerPointer}`;
|
|
147
152
|
}
|
|
148
153
|
function decideBody(b) {
|
|
149
154
|
return `The decision docket is a persistent queue for decisions you would otherwise
|
|
@@ -481,6 +486,70 @@ Tell the operator which tier you chose and why, the goal id, the roles, and the
|
|
|
481
486
|
single next action. If you declined to build a team because one already existed,
|
|
482
487
|
say that first; it is the most useful sentence in the reply.`;
|
|
483
488
|
}
|
|
489
|
+
function computerBody(b) {
|
|
490
|
+
return `\`${b} computer\` drives this machine's desktop through a pinned desktop driver.
|
|
491
|
+
It is the last resort: use a vendor API, a CLI, or the browser tools first.
|
|
492
|
+
Reach for it only for a native desktop app, or a page that needs a real
|
|
493
|
+
browser you cannot drive any other way.
|
|
494
|
+
|
|
495
|
+
## Setup check
|
|
496
|
+
|
|
497
|
+
\`\`\`bash
|
|
498
|
+
${b} computer status
|
|
499
|
+
\`\`\`
|
|
500
|
+
|
|
501
|
+
It shows the host, the pinned driver and whether it is installed, the daemon,
|
|
502
|
+
and who holds the desktop. When the driver is missing, the project must enable
|
|
503
|
+
computer use and run \`${b} computer install --yes\`. \`${b} computer doctor\`
|
|
504
|
+
reports the driver's own health checks.
|
|
505
|
+
|
|
506
|
+
## Core loop
|
|
507
|
+
|
|
508
|
+
Every step is one call: \`${b} computer call <tool> '<json>'\`. Look up a tool's
|
|
509
|
+
arguments with \`${b} computer describe <tool>\` rather than guessing; \`${b} computer tools\`
|
|
510
|
+
lists them.
|
|
511
|
+
|
|
512
|
+
1. **Launch** the app in the background with \`launch_app\`.
|
|
513
|
+
2. **Read state** with \`get_window_state\` and \`include_screenshot: false\`. When
|
|
514
|
+
you need to see the window, pass \`screenshot_out_file\` with a file path
|
|
515
|
+
and read that file. Inline screenshots are redacted from the output unless
|
|
516
|
+
you pass \`--keep-screenshot\`, because they flood your context.
|
|
517
|
+
3. **Act by element token** \`"<snapshot_id>:<row>"\` from the latest state:
|
|
518
|
+
click, \`set_value\`, or invoke, batched through \`run_steps\` when several
|
|
519
|
+
actions belong together.
|
|
520
|
+
4. **Read back** the state after acting. Confirm the change landed before you
|
|
521
|
+
retry anything; never repeat an action blind.
|
|
522
|
+
|
|
523
|
+
## Rules
|
|
524
|
+
|
|
525
|
+
- Prefer accessibility routes (\`set_value\`, invoke) over \`type_text\` and
|
|
526
|
+
coordinate clicks.
|
|
527
|
+
- Never pass \`delivery_mode: "foreground"\` unless the driver returned
|
|
528
|
+
\`background_unavailable\` or \`delivery_failed\` for that action.
|
|
529
|
+
- For a browser, call \`browser_prepare\` with \`allow_launch: true\` and
|
|
530
|
+
\`profile: {"mode": "isolated_new"}\`. Never attach to the user's real
|
|
531
|
+
profile unless the operator asks for it.
|
|
532
|
+
- Never type a password, username, or one-time code into a sign-in page. Stop
|
|
533
|
+
and ask the operator to sign in.
|
|
534
|
+
- Only \`kill_app\` processes you launched.
|
|
535
|
+
|
|
536
|
+
## One desktop, one holder
|
|
537
|
+
|
|
538
|
+
Every agent on this machine shares one desktop. \`call\` takes a lease for you
|
|
539
|
+
and refreshes it on every call; it lapses after a short idle period.
|
|
540
|
+
|
|
541
|
+
- A refusal naming another holder means **wait**, then try once more later. Do
|
|
542
|
+
not retry in a loop. \`--wait <seconds>\` waits longer on one call.
|
|
543
|
+
- Check the holder with \`${b} computer lease status\`.
|
|
544
|
+
- Release the desktop as soon as you finish:
|
|
545
|
+
|
|
546
|
+
\`\`\`bash
|
|
547
|
+
${b} computer lease release
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
550
|
+
Report what you did on the desktop, which apps you launched and closed, and
|
|
551
|
+
anything you left open.`;
|
|
552
|
+
}
|
|
484
553
|
export const SKILLS = [
|
|
485
554
|
{
|
|
486
555
|
id: "harn-decide",
|
|
@@ -522,4 +591,15 @@ export const SKILLS = [
|
|
|
522
591
|
body: teamBody(binName),
|
|
523
592
|
}),
|
|
524
593
|
},
|
|
594
|
+
{
|
|
595
|
+
id: "harn-computer",
|
|
596
|
+
relPath: "harn-computer/SKILL.md",
|
|
597
|
+
optIn: "computer",
|
|
598
|
+
render: (binName) => buildOwnedSkill({
|
|
599
|
+
name: "harn-computer",
|
|
600
|
+
description: "Drive a native desktop app or a real browser through the project's pinned desktop driver, as a last resort after APIs, CLIs, and browser tools. Covers the setup check, the launch / read state / act by element token / read back loop, safety rules, and the shared desktop lease.",
|
|
601
|
+
binName,
|
|
602
|
+
body: computerBody(binName),
|
|
603
|
+
}),
|
|
604
|
+
},
|
|
525
605
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harnery",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryan Kelly",
|
|
@@ -224,9 +224,9 @@
|
|
|
224
224
|
}
|
|
225
225
|
},
|
|
226
226
|
"bin": {
|
|
227
|
-
"harn": "./bin/harn",
|
|
228
|
-
"agent-coord": "./bin/agent-coord",
|
|
229
|
-
"agent-hook": "./bin/agent-hook"
|
|
227
|
+
"harn": "./bin/harn.mjs",
|
|
228
|
+
"agent-coord": "./bin/agent-coord.mjs",
|
|
229
|
+
"agent-hook": "./bin/agent-hook.mjs"
|
|
230
230
|
},
|
|
231
231
|
"//files": "src ships as TS so the `bun` export condition resolves to source; negate co-located tests, fixture directories, and shared test helpers so they don't ride into the tarball (a .npmignore is inert for paths already inside the files allowlist).",
|
|
232
232
|
"files": [
|
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
"scripts": {
|
|
257
257
|
"build": "npm run clean && tsc -p tsconfig.build.json && node scripts/rewrite-dts-extensions.mjs",
|
|
258
258
|
"build:openclaw-plugin": "mkdir -p openclaw-plugin/dist && bun build openclaw-plugin/src/index.ts openclaw-plugin/src/record-worker.ts --target=node --format=esm --external openclaw --outdir openclaw-plugin/dist --entry-naming '[name].js' && cp openclaw-plugin/package.json openclaw-plugin/openclaw.plugin.json openclaw-plugin/dist/",
|
|
259
|
-
"clean": "
|
|
259
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
260
260
|
"prepublishOnly": "npm run build",
|
|
261
261
|
"typecheck": "bun x tsc --noEmit",
|
|
262
262
|
"lint": "bun x @biomejs/biome check src openclaw-plugin opencode-plugin",
|