@brainervirus/workit-core 8.29.0 → 8.29.1
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/package.json +1 -1
- package/src/core/heavy.ts +5 -5
- package/src/hooks/handle.ts +13 -5
- package/src/hooks/raw-git.ts +22 -4
- package/src/host-session.ts +25 -10
package/package.json
CHANGED
package/src/core/heavy.ts
CHANGED
|
@@ -446,8 +446,8 @@ export type HeavyPlan =
|
|
|
446
446
|
| { kind: "rewrite"; command: string; reason: string }
|
|
447
447
|
/** A chain with heavy parts: run each part alone (`unblock` is the first heavy one). */
|
|
448
448
|
| { kind: "split"; reason: string; unblock: string | null }
|
|
449
|
-
/** A host rule names it, or wrapping would raise it: it runs as typed. */
|
|
450
|
-
| { kind: "passthrough"; note:
|
|
449
|
+
/** A host rule names it, or wrapping would raise it: it runs as typed, saying nothing. */
|
|
450
|
+
| { kind: "passthrough"; note: "" };
|
|
451
451
|
|
|
452
452
|
export type PlanOptions = HeavyOptions & {
|
|
453
453
|
/** The host's permission rules; null where the host has none to read (Pi). */
|
|
@@ -476,7 +476,7 @@ export function heavyPlan(command: string, options: PlanOptions): HeavyPlan {
|
|
|
476
476
|
if (!lines) return { kind: "none" };
|
|
477
477
|
const heavy = heavyLines(lines, options);
|
|
478
478
|
if (!heavy.length) return { kind: "none" };
|
|
479
|
-
const simple = simpleCommand(command, dialect);
|
|
479
|
+
const simple = simpleCommand(command, dialect, 0, true);
|
|
480
480
|
const single = simple && !WORKIT.test(baseName(simple[0])) && heavyLines([simple], options)[0];
|
|
481
481
|
const judged = single
|
|
482
482
|
? [{ words: lines.flat(), text: command }]
|
|
@@ -490,7 +490,7 @@ export function heavyPlan(command: string, options: PlanOptions): HeavyPlan {
|
|
|
490
490
|
if (!verdict.ok)
|
|
491
491
|
return {
|
|
492
492
|
kind: "passthrough",
|
|
493
|
-
note:
|
|
493
|
+
note: "",
|
|
494
494
|
};
|
|
495
495
|
}
|
|
496
496
|
if (single) {
|
|
@@ -502,7 +502,7 @@ export function heavyPlan(command: string, options: PlanOptions): HeavyPlan {
|
|
|
502
502
|
const parts = heavy.map((match) => leaseCommand(match.line));
|
|
503
503
|
return {
|
|
504
504
|
kind: "split",
|
|
505
|
-
reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run
|
|
505
|
+
reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run the heavy part alone, now: \`${parts[0]}\`; then run the other parts as their own commands${parts.length > 1 ? ", each heavy one the same way" : ""}.`,
|
|
506
506
|
unblock: parts[0] ?? null,
|
|
507
507
|
};
|
|
508
508
|
}
|
package/src/hooks/handle.ts
CHANGED
|
@@ -127,18 +127,26 @@ const subagentStartText = (
|
|
|
127
127
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
128
128
|
if (role !== "judge" || !event.agentId) return readOnly;
|
|
129
129
|
const own = subagentSession(session, event.agentId);
|
|
130
|
+
const cwd = input.cwd || process.cwd();
|
|
131
|
+
const judge = event.agentType.endsWith("reviewer") ? "reviewer" : "verifier";
|
|
132
|
+
const pick = judge === "verifier" ? ", --kind live when you drove the real surface" : "";
|
|
133
|
+
const branch = judgeBranch(cwd);
|
|
134
|
+
// Codex runs a subagent's shell with its own CODEX_THREAD_ID: the CLI already
|
|
135
|
+
// acts as that session, so the line names no --session and no --token (a
|
|
136
|
+
// token on a command line lands in the rollout, #427) and nothing is issued.
|
|
137
|
+
if (descriptor.host === "codex_cli" || descriptor.host === "codex_desktop")
|
|
138
|
+
return `${readOnly} Its shell already acts as its own Workit session, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${verdictCommand({ branch, judge })}\`. Add no session or token option to it, and never set WORKIT_SESSION_ID by hand.`;
|
|
130
139
|
// `ledger verdict` accepts `<lead>:<agent id>` only when this hook issued it,
|
|
131
|
-
// and only with the token it hands to this subagent alone.
|
|
140
|
+
// and only with the token it hands to this subagent alone. Claude Code
|
|
141
|
+
// subagents share the lead's environment, so no per-agent env var or file
|
|
142
|
+
// can carry it: the token stays on the line (docs/agents/hosts.md, #427).
|
|
132
143
|
// (An isolated judge's text is rebuilt with cwd "": the hook's own cwd then.)
|
|
133
144
|
// Issued once: a replayed SubagentStart for the same id gets no token.
|
|
134
|
-
const cwd = input.cwd || process.cwd();
|
|
135
145
|
const token = session ? recordIssuedSession(cwd, session, own) : null;
|
|
136
146
|
// Already issued: without its token the id is refused (unbound_session).
|
|
137
147
|
if (session && !token)
|
|
138
148
|
return `${readOnly} ${own} was already issued to an earlier subagent, so no token is handed out again and a verdict under it would be refused (unbound_session). Record no verdict as ${own}: spawn a fresh verifier or reviewer, which gets its own session and token.`;
|
|
139
|
-
const
|
|
140
|
-
const line = verdictCommand({ branch: judgeBranch(cwd), judge, session: own, token });
|
|
141
|
-
const pick = judge === "verifier" ? ", --kind live when you drove the real surface" : "";
|
|
149
|
+
const line = verdictCommand({ branch, judge, session: own, token });
|
|
142
150
|
return `${readOnly} Its own Workit session is ${own}, so the ledger tells its verdicts apart from the author's. Record the verdict with exactly this command (pick the result${pick}, name the candidate branch if it differs, say what you ran): \`${line}\`.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
|
|
143
151
|
};
|
|
144
152
|
|
package/src/hooks/raw-git.ts
CHANGED
|
@@ -727,15 +727,19 @@ export function workitCheckTail(
|
|
|
727
727
|
: { argv: tail, cwd };
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
+
const OUTPUT_FILTERS = new Set(["tail", "head", "grep"]);
|
|
731
|
+
|
|
730
732
|
/**
|
|
731
733
|
* The one command `command` runs after its wrappers (env prefixes, `timeout`,
|
|
732
734
|
* `nice`, `flock`, `bash -c '<one command>'`), or null when it is a chain,
|
|
733
|
-
* pipe, redirect, substitution, group or heredoc anywhere
|
|
735
|
+
* pipe, redirect, substitution, group or heredoc anywhere (`shaped` allows
|
|
736
|
+
* redirects and one trailing `| tail|head|grep` filter).
|
|
734
737
|
*/
|
|
735
738
|
export function simpleCommand(
|
|
736
739
|
command: string,
|
|
737
740
|
dialect: ShellDialect = "posix",
|
|
738
741
|
depth = 0,
|
|
742
|
+
shaped = false,
|
|
739
743
|
): string[] | null {
|
|
740
744
|
let segments: Segment[];
|
|
741
745
|
try {
|
|
@@ -743,13 +747,27 @@ export function simpleCommand(
|
|
|
743
747
|
} catch {
|
|
744
748
|
return null;
|
|
745
749
|
}
|
|
746
|
-
|
|
750
|
+
// `shaped`: output shaping around the one command is not a chain: any
|
|
751
|
+
// redirects, and at most one trailing `| tail|head|grep …` filter.
|
|
752
|
+
const filter = shaped && segments.length === 2 ? segments[1] : null;
|
|
753
|
+
if (filter) {
|
|
754
|
+
if (
|
|
755
|
+
!segments[0].pipe ||
|
|
756
|
+
!OUTPUT_FILTERS.has(commandName(filter.words[0])) ||
|
|
757
|
+
filter.group ||
|
|
758
|
+
filter.pipe ||
|
|
759
|
+
filter.unanalyzable ||
|
|
760
|
+
filter.subs?.length ||
|
|
761
|
+
filter.stdin?.length
|
|
762
|
+
)
|
|
763
|
+
return null;
|
|
764
|
+
} else if (segments.length !== 1) return null;
|
|
747
765
|
const [segment] = segments;
|
|
748
766
|
if (
|
|
749
767
|
segment.group ||
|
|
750
|
-
segment.pipe ||
|
|
768
|
+
(segment.pipe && !filter) ||
|
|
751
769
|
segment.unanalyzable ||
|
|
752
|
-
segment.redirects.length ||
|
|
770
|
+
(segment.redirects.length && !shaped) ||
|
|
753
771
|
segment.subs?.length ||
|
|
754
772
|
segment.stdin?.length
|
|
755
773
|
)
|
package/src/host-session.ts
CHANGED
|
@@ -381,6 +381,20 @@ export type SessionClaimRefusal = { error: string; unblock: string };
|
|
|
381
381
|
* Anything else is a forged id: a verdict recorded under it would make author
|
|
382
382
|
* is not verifier a naming convention.
|
|
383
383
|
*/
|
|
384
|
+
const isOwnShellClaim = (real: RealSession, claimed: string): boolean =>
|
|
385
|
+
(claimed === real.session && !(real.kind === "declared" && claimed.includes(":"))) ||
|
|
386
|
+
(real.kind === "derived" && claimed.endsWith(`:${real.session}`));
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* `claimed` is the shell's own derived session (OPENCODE_SESSION_ID,
|
|
390
|
+
* PI_SESSION_ID, CODEX_THREAD_ID), alone or as `<lead>:<it>`: a `--session`
|
|
391
|
+
* naming it adds nothing, so the CLI acts as the shell's session instead.
|
|
392
|
+
*/
|
|
393
|
+
export const namesOwnDerivedSession = (env: NodeJS.ProcessEnv, claimed: string): boolean => {
|
|
394
|
+
const real = realHostSession(env);
|
|
395
|
+
return real.kind === "derived" && isOwnShellClaim(real, claimed);
|
|
396
|
+
};
|
|
397
|
+
|
|
384
398
|
export function sessionClaimRefusal(
|
|
385
399
|
env: NodeJS.ProcessEnv,
|
|
386
400
|
claimed: string,
|
|
@@ -397,16 +411,17 @@ export function sessionClaimRefusal(
|
|
|
397
411
|
unblock: `record the verdict from the ${real.host} session's own shell, where ${HOST_SET_VARIABLE[host]} is set`,
|
|
398
412
|
};
|
|
399
413
|
if (real.kind === "none" || !real.session) return null;
|
|
400
|
-
const ownShell =
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
414
|
+
const ownShell = isOwnShellClaim(real, claimed);
|
|
415
|
+
if (ownShell) {
|
|
416
|
+
// A derived session (the host's own shell variable) cannot be replaced by
|
|
417
|
+
// a flag, so naming it is a no-op, not an error: a printed line that
|
|
418
|
+
// carries it still records. Declared sessions keep refusing the flag.
|
|
419
|
+
if (!options.flag || real.kind === "derived") return null;
|
|
420
|
+
return {
|
|
421
|
+
error: `session_flag: --session ${claimed} names this shell's own ${real.host} session; on a host only an issued subagent id is passed as --session`,
|
|
422
|
+
unblock: "drop --session: the shell's own session is already the acting one",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
410
425
|
const issuedClaim = claimed === real.session || claimed.startsWith(`${real.session}:`);
|
|
411
426
|
if (issuedClaim) {
|
|
412
427
|
const binding = issuedBinding(cwd, claimed);
|