@brainervirus/workit-pi 8.7.0 → 8.8.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/workit.js +353 -103
- package/package.json +1 -1
package/dist/workit.js
CHANGED
|
@@ -19487,12 +19487,48 @@ function writeGate(cwd, paths) {
|
|
|
19487
19487
|
return NONE2;
|
|
19488
19488
|
}
|
|
19489
19489
|
}
|
|
19490
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
19491
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
19492
|
+
|
|
19490
19493
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
19491
19494
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
19495
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
19496
|
+
import fs17 from "node:fs";
|
|
19497
|
+
import os4 from "node:os";
|
|
19498
|
+
import path23 from "node:path";
|
|
19499
|
+
|
|
19500
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
19492
19501
|
import { createHash as createHash12 } from "node:crypto";
|
|
19493
19502
|
import fs16 from "node:fs";
|
|
19494
|
-
import os4 from "node:os";
|
|
19495
19503
|
import path22 from "node:path";
|
|
19504
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
19505
|
+
let dir = store;
|
|
19506
|
+
if (dir === undefined) {
|
|
19507
|
+
const location = resolveStore(input.cwd);
|
|
19508
|
+
if (location instanceof Error || !location.shared)
|
|
19509
|
+
return null;
|
|
19510
|
+
dir = location.dir;
|
|
19511
|
+
}
|
|
19512
|
+
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
19513
|
+
return path22.join(dir, "hooks", `skills-${name}.json`);
|
|
19514
|
+
};
|
|
19515
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
19516
|
+
var readSessionMarker = (file) => {
|
|
19517
|
+
try {
|
|
19518
|
+
const value = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
19519
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
19520
|
+
} catch {
|
|
19521
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
19522
|
+
}
|
|
19523
|
+
};
|
|
19524
|
+
var writeSessionMarker = (file, marker) => {
|
|
19525
|
+
try {
|
|
19526
|
+
fs16.mkdirSync(path22.dirname(file), { recursive: true });
|
|
19527
|
+
fs16.writeFileSync(file, JSON.stringify(marker));
|
|
19528
|
+
} catch {}
|
|
19529
|
+
};
|
|
19530
|
+
|
|
19531
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
19496
19532
|
var NONE3 = { kind: "none" };
|
|
19497
19533
|
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
19498
19534
|
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
@@ -19509,12 +19545,12 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
19509
19545
|
"--config-env",
|
|
19510
19546
|
"--super-prefix"
|
|
19511
19547
|
]);
|
|
19512
|
-
var isAbsolute = (value) =>
|
|
19548
|
+
var isAbsolute = (value) => path23.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
19513
19549
|
var joinDir = (base, next) => {
|
|
19514
19550
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
19515
19551
|
return;
|
|
19516
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
19517
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
19552
|
+
const expanded = next === "~" || next.startsWith("~/") ? path23.join(os4.homedir(), next.slice(1)) : next;
|
|
19553
|
+
return base === null || isAbsolute(expanded) ? expanded : path23.join(base, expanded);
|
|
19518
19554
|
};
|
|
19519
19555
|
var cdTarget = (words) => {
|
|
19520
19556
|
const args = words.slice(1);
|
|
@@ -19610,6 +19646,37 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
|
19610
19646
|
}
|
|
19611
19647
|
return found;
|
|
19612
19648
|
}
|
|
19649
|
+
var LAUNCHERS = new Set([
|
|
19650
|
+
"npx",
|
|
19651
|
+
"bunx",
|
|
19652
|
+
"pnpx",
|
|
19653
|
+
"-y",
|
|
19654
|
+
"--yes",
|
|
19655
|
+
"pnpm",
|
|
19656
|
+
"dlx",
|
|
19657
|
+
"exec",
|
|
19658
|
+
"env",
|
|
19659
|
+
"sudo",
|
|
19660
|
+
"command"
|
|
19661
|
+
]);
|
|
19662
|
+
var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
|
|
19663
|
+
function workitInvocations(command, dialect = "posix", depth = 0) {
|
|
19664
|
+
const found = [];
|
|
19665
|
+
for (const { words } of segmentsOf(command, dialect)) {
|
|
19666
|
+
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
19667
|
+
if (at < 0)
|
|
19668
|
+
continue;
|
|
19669
|
+
if (WORKIT_BIN.test(words[at])) {
|
|
19670
|
+
found.push(words.slice(at + 1));
|
|
19671
|
+
continue;
|
|
19672
|
+
}
|
|
19673
|
+
const shell = SHELLS.has(commandName(words[at]));
|
|
19674
|
+
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
19675
|
+
if (depth < 3 && script !== undefined)
|
|
19676
|
+
found.push(...workitInvocations(script, "posix", depth + 1));
|
|
19677
|
+
}
|
|
19678
|
+
return found;
|
|
19679
|
+
}
|
|
19613
19680
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
19614
19681
|
var pushAction = (args) => {
|
|
19615
19682
|
let force = false;
|
|
@@ -19736,7 +19803,7 @@ var repoAt = (dir) => {
|
|
|
19736
19803
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
19737
19804
|
};
|
|
19738
19805
|
var isWorkitWorkspace = (repo) => {
|
|
19739
|
-
if (
|
|
19806
|
+
if (fs17.existsSync(repo.location.dir))
|
|
19740
19807
|
return true;
|
|
19741
19808
|
try {
|
|
19742
19809
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -19767,17 +19834,17 @@ var headSha2 = (repo) => {
|
|
|
19767
19834
|
if (!git || git.reftable)
|
|
19768
19835
|
return null;
|
|
19769
19836
|
try {
|
|
19770
|
-
const head =
|
|
19837
|
+
const head = fs17.readFileSync(path23.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
19771
19838
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
19772
19839
|
return head;
|
|
19773
19840
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
19774
19841
|
if (!ref)
|
|
19775
19842
|
return null;
|
|
19776
|
-
const common =
|
|
19843
|
+
const common = path23.dirname(repo.location.dir);
|
|
19777
19844
|
try {
|
|
19778
|
-
return
|
|
19845
|
+
return fs17.readFileSync(path23.join(common, ref), "utf8").trim() || null;
|
|
19779
19846
|
} catch {}
|
|
19780
|
-
const packed =
|
|
19847
|
+
const packed = fs17.readFileSync(path23.join(common, "packed-refs"), "utf8");
|
|
19781
19848
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
19782
19849
|
} catch {
|
|
19783
19850
|
return null;
|
|
@@ -19814,7 +19881,7 @@ var actionsIn = (cwd, command, dialect) => {
|
|
|
19814
19881
|
const action = rawAction(invocation);
|
|
19815
19882
|
if (!action)
|
|
19816
19883
|
continue;
|
|
19817
|
-
const dir =
|
|
19884
|
+
const dir = path23.resolve(cwd, invocation.dir ?? ".");
|
|
19818
19885
|
if (!repos.has(dir)) {
|
|
19819
19886
|
const repo = repoAt(dir);
|
|
19820
19887
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -19832,12 +19899,31 @@ var inWorkitWorkspace = (dir) => {
|
|
|
19832
19899
|
var rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read");
|
|
19833
19900
|
var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
|
|
19834
19901
|
var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
|
|
19835
|
-
|
|
19902
|
+
var nudgeKind = (action) => action.kind === "commit" && action.amend ? "commit-amend" : action.kind === "push" && action.blindForce ? "push-force" : action.kind;
|
|
19903
|
+
var unseenNudges = (input, nudges, store) => {
|
|
19904
|
+
const session = input.session.id;
|
|
19905
|
+
const file = session ? sessionMarkerFile(input, session, store) : null;
|
|
19906
|
+
if (!file)
|
|
19907
|
+
return [...nudges.values()];
|
|
19908
|
+
const marker = readSessionMarker(file);
|
|
19909
|
+
const agent = input.session.agentId ? `${input.session.agentId}:` : "";
|
|
19910
|
+
const fresh = [...nudges.keys()].filter((kind) => !marker.raw.includes(agent + kind));
|
|
19911
|
+
if (fresh.length)
|
|
19912
|
+
writeSessionMarker(file, {
|
|
19913
|
+
...marker,
|
|
19914
|
+
raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
|
|
19915
|
+
});
|
|
19916
|
+
return fresh.map((kind) => nudges.get(kind));
|
|
19917
|
+
};
|
|
19918
|
+
function rawGitPre(input, command, options = {}) {
|
|
19836
19919
|
try {
|
|
19837
19920
|
if (!mentionsRawTool(command))
|
|
19838
19921
|
return NONE3;
|
|
19839
|
-
const
|
|
19840
|
-
|
|
19922
|
+
const dialect = dialectOf(input);
|
|
19923
|
+
const nudges = new Map;
|
|
19924
|
+
let store = null;
|
|
19925
|
+
for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
|
|
19926
|
+
store ??= repo.location.dir;
|
|
19841
19927
|
if (action.kind === "merge")
|
|
19842
19928
|
return deny(refusal(action, null));
|
|
19843
19929
|
if (action.kind === "push")
|
|
@@ -19847,10 +19933,15 @@ function rawGitPre(input, command) {
|
|
|
19847
19933
|
return deny(refusal(action, branch));
|
|
19848
19934
|
}
|
|
19849
19935
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
19850
|
-
if (nudge && !nudges.
|
|
19851
|
-
nudges.
|
|
19936
|
+
if (nudge && !nudges.has(nudgeKind(action)))
|
|
19937
|
+
nudges.set(nudgeKind(action), nudge);
|
|
19852
19938
|
}
|
|
19853
|
-
|
|
19939
|
+
if (options.nudge === false || !store || nudges.size === 0)
|
|
19940
|
+
return NONE3;
|
|
19941
|
+
if (workitInvocations(command, dialect).length > 0)
|
|
19942
|
+
return NONE3;
|
|
19943
|
+
const shown = unseenNudges(input, nudges, store);
|
|
19944
|
+
return shown.length ? { kind: "context", text: shown.join(`
|
|
19854
19945
|
`) } : NONE3;
|
|
19855
19946
|
} catch {
|
|
19856
19947
|
return NONE3;
|
|
@@ -19859,8 +19950,8 @@ function rawGitPre(input, command) {
|
|
|
19859
19950
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
19860
19951
|
var NEXT_COMMAND = "next";
|
|
19861
19952
|
var MARKER_TTL_SECONDS = 3600;
|
|
19862
|
-
var markerFile = (repo, session, key) =>
|
|
19863
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
19953
|
+
var markerFile = (repo, session, key) => path23.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
19954
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash13("sha256").update(command).digest("hex")}`;
|
|
19864
19955
|
function noteRawCommit(input, command, key) {
|
|
19865
19956
|
try {
|
|
19866
19957
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -19870,8 +19961,8 @@ function noteRawCommit(input, command, key) {
|
|
|
19870
19961
|
if (action.kind !== "commit")
|
|
19871
19962
|
continue;
|
|
19872
19963
|
const file = markerFile(repo, input.session.id, key);
|
|
19873
|
-
|
|
19874
|
-
|
|
19964
|
+
fs17.mkdirSync(path23.dirname(file), { recursive: true });
|
|
19965
|
+
fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
19875
19966
|
`);
|
|
19876
19967
|
}
|
|
19877
19968
|
} catch {}
|
|
@@ -19894,11 +19985,11 @@ var headCommit = (dir) => {
|
|
|
19894
19985
|
var settle2 = (input, repo, file) => {
|
|
19895
19986
|
let marker;
|
|
19896
19987
|
try {
|
|
19897
|
-
marker = JSON.parse(
|
|
19988
|
+
marker = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
19898
19989
|
} catch {
|
|
19899
19990
|
return;
|
|
19900
19991
|
}
|
|
19901
|
-
|
|
19992
|
+
fs17.rmSync(file, { force: true });
|
|
19902
19993
|
const session = input.session.id;
|
|
19903
19994
|
const now = Date.now() / 1000;
|
|
19904
19995
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -19947,15 +20038,165 @@ function settlePendingCommit(input) {
|
|
|
19947
20038
|
if (!repo)
|
|
19948
20039
|
return;
|
|
19949
20040
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
19950
|
-
if (
|
|
20041
|
+
if (fs17.existsSync(file))
|
|
19951
20042
|
settle2(input, repo, file);
|
|
19952
20043
|
} catch {}
|
|
19953
20044
|
}
|
|
19954
|
-
// packages/workit-core/src/hooks/skill-nudge.ts
|
|
19955
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
19956
|
-
import fs17 from "node:fs";
|
|
19957
|
-
import path23 from "node:path";
|
|
19958
20045
|
|
|
20046
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
20047
|
+
var NONE4 = { kind: "none" };
|
|
20048
|
+
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20049
|
+
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
20050
|
+
var YES_NO = /[[(]\s*y(?:es)?\s*\/\s*n(?:o)?\s*[\])]/i;
|
|
20051
|
+
var LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)]|[a-z][.)])\s+/i;
|
|
20052
|
+
var asksUser = (message) => {
|
|
20053
|
+
if (!message)
|
|
20054
|
+
return false;
|
|
20055
|
+
const paragraphs = message.replace(/```[\s\S]*?(?:```|$)/g, `
|
|
20056
|
+
|
|
20057
|
+
`).split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
20058
|
+
while (paragraphs.length > 1 && paragraphs.at(-1).split(`
|
|
20059
|
+
`).every((line) => LIST_ITEM.test(line)))
|
|
20060
|
+
paragraphs.pop();
|
|
20061
|
+
const tail = paragraphs.slice(-2).join(`
|
|
20062
|
+
`);
|
|
20063
|
+
return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
|
|
20064
|
+
};
|
|
20065
|
+
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
20066
|
+
var gitList = (cwd, args) => {
|
|
20067
|
+
const run = spawnSync16("git", args, {
|
|
20068
|
+
cwd,
|
|
20069
|
+
encoding: "utf8",
|
|
20070
|
+
timeout: 3000,
|
|
20071
|
+
windowsHide: true,
|
|
20072
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
|
|
20073
|
+
});
|
|
20074
|
+
return run.status === 0 ? run.stdout.split(`
|
|
20075
|
+
`).filter(Boolean) : null;
|
|
20076
|
+
};
|
|
20077
|
+
var gitLine = (cwd, args) => gitList(cwd, args)?.[0] ?? null;
|
|
20078
|
+
var sessionOf = (row) => (typeof row.session === "string" ? row.session : null) ?? row.actor.session;
|
|
20079
|
+
var prState = (row) => typeof row.state === "string" ? row.state : "open";
|
|
20080
|
+
var ended = (row) => row.observer === "workit_cli" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) !== "open");
|
|
20081
|
+
var openPr = (rows) => {
|
|
20082
|
+
const seen = rows.findLast((row) => (row.type === "pr.created" || row.type === "pr.status") && row.observer === "workit_cli" && typeof row.pr === "number");
|
|
20083
|
+
if (!seen || ended(seen))
|
|
20084
|
+
return null;
|
|
20085
|
+
const closed = rows.some((row) => ended(row) && row.pr === seen.pr && row.seq > seen.seq);
|
|
20086
|
+
return closed ? null : seen.pr;
|
|
20087
|
+
};
|
|
20088
|
+
var mergedHeads = (rows) => [
|
|
20089
|
+
...new Set(rows.filter((row) => row.observer === "workit_cli" && typeof row.head === "string" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) === "merged")).map((row) => row.head))
|
|
20090
|
+
].slice(-3);
|
|
20091
|
+
var localCommit = (cwd, sha) => /^[0-9a-f]{7,64}$/.test(sha) && gitList(cwd, ["cat-file", "-e", `${sha}^{commit}`]) !== null;
|
|
20092
|
+
var upstreamGone = (cwd, branch) => gitLine(cwd, ["for-each-ref", "--format=%(upstream:track)", `refs/heads/${branch}`]) === "[gone]";
|
|
20093
|
+
var landed = (cwd, branch, rows, head, mine) => {
|
|
20094
|
+
if (!head)
|
|
20095
|
+
return false;
|
|
20096
|
+
const merged = mergedHeads(rows);
|
|
20097
|
+
if (merged.length === 0)
|
|
20098
|
+
return upstreamGone(cwd, branch);
|
|
20099
|
+
if (merged.includes(head))
|
|
20100
|
+
return true;
|
|
20101
|
+
if (mine.size === 0)
|
|
20102
|
+
return false;
|
|
20103
|
+
return merged.some((at) => {
|
|
20104
|
+
const after = gitList(cwd, ["rev-list", `--max-count=${UNPUSHED_SCAN}`, "HEAD", "--not", at]);
|
|
20105
|
+
return after !== null && !after.some((sha) => mine.has(sha));
|
|
20106
|
+
});
|
|
20107
|
+
};
|
|
20108
|
+
var UNPUSHED_SCAN = 500;
|
|
20109
|
+
var unpushed = (cwd, branch, endpoint, mine, merged) => {
|
|
20110
|
+
const remote = pushRemoteName(cwd, branch);
|
|
20111
|
+
if (!remote || mine.size === 0)
|
|
20112
|
+
return null;
|
|
20113
|
+
const listed = gitList(cwd, [
|
|
20114
|
+
"rev-list",
|
|
20115
|
+
`--max-count=${UNPUSHED_SCAN}`,
|
|
20116
|
+
"HEAD",
|
|
20117
|
+
"--not",
|
|
20118
|
+
`--remotes=${remote}`,
|
|
20119
|
+
...merged.filter((sha) => localCommit(cwd, sha))
|
|
20120
|
+
]);
|
|
20121
|
+
const count = (listed ?? []).filter((sha) => mine.has(sha)).length;
|
|
20122
|
+
if (count === 0)
|
|
20123
|
+
return null;
|
|
20124
|
+
return {
|
|
20125
|
+
kind: "unpushed",
|
|
20126
|
+
reason: `${branch} has ${count} commit(s) of this session not on ${remote}, and this workspace's endpoint is \`${endpoint}\` (\`workit grant show\`): push them with \`workit git push\`${endpoint === "pr" ? ", then open or update the PR (`workit pr create --fill`)" : ""}.`
|
|
20127
|
+
};
|
|
20128
|
+
};
|
|
20129
|
+
var redChecks = (rows, pr, head, endpoint) => {
|
|
20130
|
+
const status = rows.findLast((row) => row.type === "pr.status" && row.observer === "workit_cli" && row.pr === pr);
|
|
20131
|
+
const state = status && status.head === head && prState(status) === "open" ? status.checks : null;
|
|
20132
|
+
if (state !== "failing" && state !== "pending")
|
|
20133
|
+
return null;
|
|
20134
|
+
return {
|
|
20135
|
+
kind: "checks",
|
|
20136
|
+
reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
|
|
20137
|
+
};
|
|
20138
|
+
};
|
|
20139
|
+
var unverified = (cwd, branch, all, descriptor) => {
|
|
20140
|
+
const check = checkVerdicts(cwd, branch, all);
|
|
20141
|
+
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
20142
|
+
return null;
|
|
20143
|
+
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
20144
|
+
const verifier = `${descriptor.subagents.agentPrefix}verifier`;
|
|
20145
|
+
return {
|
|
20146
|
+
kind: "verdict",
|
|
20147
|
+
reason: `the head ${short(check.head)} of ${branch}, which this session delivered, has ${has}: if no verifier is already running on it, hand it to a session that did not author it (the ${verifier} agent records \`workit ledger verdict <result> --branch ${branch} --how "<evidence>"\`), or tell the user it is unverified. Never record a verdict on your own work.`
|
|
20148
|
+
};
|
|
20149
|
+
};
|
|
20150
|
+
function stopObligation(input, descriptor) {
|
|
20151
|
+
try {
|
|
20152
|
+
const session = input.session.id;
|
|
20153
|
+
if (!session || input.session.agentId || !inWorkitWorkspace(input.cwd))
|
|
20154
|
+
return null;
|
|
20155
|
+
const branch = currentBranch(input.cwd);
|
|
20156
|
+
if (!branch || guardedBranch(input.cwd, branch))
|
|
20157
|
+
return null;
|
|
20158
|
+
const ledger = readLedger(input.cwd);
|
|
20159
|
+
if (!ledger.ok)
|
|
20160
|
+
return null;
|
|
20161
|
+
const all = ledger.value.rows;
|
|
20162
|
+
const rows = all.filter((row) => row.branch === branch);
|
|
20163
|
+
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
20164
|
+
const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
|
|
20165
|
+
const delivered = mine.some((row) => row.observer === "workit_cli" && (row.type === "pr.created" || row.type === "delivery.verified"));
|
|
20166
|
+
if (commits.size === 0 && !delivered)
|
|
20167
|
+
return null;
|
|
20168
|
+
const head = gitLine(input.cwd, ["rev-parse", "HEAD"]);
|
|
20169
|
+
if (landed(input.cwd, branch, rows, head, commits))
|
|
20170
|
+
return null;
|
|
20171
|
+
const endpoint = resolveAutonomy(input.cwd).effectiveEndpoint;
|
|
20172
|
+
if (endpoint !== "commit") {
|
|
20173
|
+
const left = unpushed(input.cwd, branch, endpoint, commits, mergedHeads(rows));
|
|
20174
|
+
if (left)
|
|
20175
|
+
return left;
|
|
20176
|
+
}
|
|
20177
|
+
const pr = openPr(rows);
|
|
20178
|
+
if ((endpoint === "green" || endpoint === "merged") && pr !== null && head) {
|
|
20179
|
+
const left = redChecks(rows, pr, head, endpoint);
|
|
20180
|
+
if (left)
|
|
20181
|
+
return left;
|
|
20182
|
+
}
|
|
20183
|
+
return delivered ? unverified(input.cwd, branch, all, descriptor) : null;
|
|
20184
|
+
} catch {
|
|
20185
|
+
return null;
|
|
20186
|
+
}
|
|
20187
|
+
}
|
|
20188
|
+
function stopDecision(input, descriptor) {
|
|
20189
|
+
const event = input.event;
|
|
20190
|
+
if (event.kind !== "stop" || event.stopHookActive)
|
|
20191
|
+
return NONE4;
|
|
20192
|
+
if (!event.lastMessage?.trim() || asksUser(event.lastMessage))
|
|
20193
|
+
return NONE4;
|
|
20194
|
+
const left = stopObligation(input, descriptor);
|
|
20195
|
+
return left ? {
|
|
20196
|
+
kind: "continue",
|
|
20197
|
+
reason: `Workit: before stopping, ${left.reason} If this cannot be done now, say so to the user and stop.`
|
|
20198
|
+
} : NONE4;
|
|
20199
|
+
}
|
|
19959
20200
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
19960
20201
|
var WORKIT_METHOD_SKILLS = [
|
|
19961
20202
|
"workit-shape",
|
|
@@ -20062,27 +20303,6 @@ var promptTrigger = (prompt) => {
|
|
|
20062
20303
|
}
|
|
20063
20304
|
return null;
|
|
20064
20305
|
};
|
|
20065
|
-
var markerFile2 = (input, session) => {
|
|
20066
|
-
const location = resolveStore(input.cwd);
|
|
20067
|
-
if (location instanceof Error || !location.shared)
|
|
20068
|
-
return null;
|
|
20069
|
-
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
20070
|
-
return path23.join(location.dir, "hooks", `skills-${name}.json`);
|
|
20071
|
-
};
|
|
20072
|
-
var readMarker = (file) => {
|
|
20073
|
-
try {
|
|
20074
|
-
const value = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
20075
|
-
return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
|
|
20076
|
-
} catch {
|
|
20077
|
-
return { nudged: [], loaded: [] };
|
|
20078
|
-
}
|
|
20079
|
-
};
|
|
20080
|
-
var writeMarker = (file, marker) => {
|
|
20081
|
-
try {
|
|
20082
|
-
fs17.mkdirSync(path23.dirname(file), { recursive: true });
|
|
20083
|
-
fs17.writeFileSync(file, JSON.stringify(marker));
|
|
20084
|
-
} catch {}
|
|
20085
|
-
};
|
|
20086
20306
|
var loadedSkills = (cwd, session) => {
|
|
20087
20307
|
const ledger = readLedger(cwd);
|
|
20088
20308
|
const loaded = new Set;
|
|
@@ -20130,13 +20350,13 @@ function recordSkillLoad(input, name, via) {
|
|
|
20130
20350
|
return;
|
|
20131
20351
|
if (!inWorkitWorkspace(input.cwd))
|
|
20132
20352
|
return;
|
|
20133
|
-
const file =
|
|
20353
|
+
const file = sessionMarkerFile(input, session);
|
|
20134
20354
|
if (!file)
|
|
20135
20355
|
return;
|
|
20136
|
-
const marker =
|
|
20356
|
+
const marker = readSessionMarker(file);
|
|
20137
20357
|
if (marker.loaded.includes(skill))
|
|
20138
20358
|
return;
|
|
20139
|
-
|
|
20359
|
+
writeSessionMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
|
|
20140
20360
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
20141
20361
|
return;
|
|
20142
20362
|
appendHookObserved(input.cwd, {
|
|
@@ -20148,23 +20368,24 @@ function recordSkillLoad(input, name, via) {
|
|
|
20148
20368
|
} catch {}
|
|
20149
20369
|
}
|
|
20150
20370
|
var nudgeOnce = (input, session, skill, line) => {
|
|
20151
|
-
const file =
|
|
20371
|
+
const file = sessionMarkerFile(input, session);
|
|
20152
20372
|
if (!file)
|
|
20153
20373
|
return null;
|
|
20154
|
-
const marker =
|
|
20374
|
+
const marker = readSessionMarker(file);
|
|
20155
20375
|
if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
|
|
20156
20376
|
return null;
|
|
20157
20377
|
if (loadedSkills(input.cwd, session).has(skill))
|
|
20158
20378
|
return null;
|
|
20159
|
-
|
|
20379
|
+
writeSessionMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
|
|
20160
20380
|
return line;
|
|
20161
20381
|
};
|
|
20162
20382
|
var nudgeText = (input, skill, why) => USER_INVOKED.has(skill) ? `Workit: ${why} looks like ${skill} work, which the user starts; offer it (/${skill.replace(/^workit-/, "wk-")}) rather than loading it.` : `Workit: ${why} looks like ${skill} work; if it is, ${skillLoadWording(SKILL_HOST[input.host], skill)} first.`;
|
|
20163
20383
|
var promptLoads = (prompt) => /^\s*\/(?:skill:)?([\w:-]+)/.exec(prompt)?.[1] ?? /^\s*<skill name="([\w:-]+)"/.exec(prompt)?.[1] ?? /^\s*Load the (workit-[a-z-]+) skill with the skill tool\b/.exec(prompt)?.[1] ?? null;
|
|
20384
|
+
var relayedMessage = (prompt) => /^\s*(?:Another Claude session sent a message\b|<agent-message\s+from=|\[SYSTEM NOTIFICATION\b|<task-notification>)/.test(prompt);
|
|
20164
20385
|
function promptNudge(input, prompt) {
|
|
20165
20386
|
try {
|
|
20166
20387
|
const session = input.session.id;
|
|
20167
|
-
if (!prompt || !session || input.session.agentId)
|
|
20388
|
+
if (!prompt || !session || input.session.agentId || relayedMessage(prompt))
|
|
20168
20389
|
return null;
|
|
20169
20390
|
const loads = promptLoads(prompt);
|
|
20170
20391
|
if (loads) {
|
|
@@ -20180,32 +20401,7 @@ function promptNudge(input, prompt) {
|
|
|
20180
20401
|
}
|
|
20181
20402
|
}
|
|
20182
20403
|
var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
|
|
20183
|
-
var
|
|
20184
|
-
var LAUNCHERS = new Set([
|
|
20185
|
-
"npx",
|
|
20186
|
-
"bunx",
|
|
20187
|
-
"pnpx",
|
|
20188
|
-
"-y",
|
|
20189
|
-
"--yes",
|
|
20190
|
-
"pnpm",
|
|
20191
|
-
"dlx",
|
|
20192
|
-
"exec",
|
|
20193
|
-
"env",
|
|
20194
|
-
"sudo",
|
|
20195
|
-
"command"
|
|
20196
|
-
]);
|
|
20197
|
-
var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
|
|
20198
|
-
const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
|
|
20199
|
-
if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
|
|
20200
|
-
const args = words.slice(at + 1);
|
|
20201
|
-
if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
|
|
20202
|
-
return false;
|
|
20203
|
-
return WORKIT_DELIVERY.test(args.join(" "));
|
|
20204
|
-
}
|
|
20205
|
-
const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
|
|
20206
|
-
const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
|
|
20207
|
-
return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
|
|
20208
|
-
});
|
|
20404
|
+
var runsWorkitDelivery = (command, dialect) => workitInvocations(command, dialect).some((args) => !args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run") && WORKIT_DELIVERY.test(args.join(" ")));
|
|
20209
20405
|
var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
|
|
20210
20406
|
function shipNudge(input, command) {
|
|
20211
20407
|
try {
|
|
@@ -20234,7 +20430,7 @@ var shellNudge = (input, command) => withContextLine(rawGitPre(input, command),
|
|
|
20234
20430
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20235
20431
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20236
20432
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20237
|
-
var
|
|
20433
|
+
var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
20238
20434
|
var WRITE_TOOLS = new Set([
|
|
20239
20435
|
"write",
|
|
20240
20436
|
"edit",
|
|
@@ -20268,7 +20464,7 @@ var writePaths = (input) => {
|
|
|
20268
20464
|
};
|
|
20269
20465
|
// packages/workit-core/src/hooks/handle.ts
|
|
20270
20466
|
var usable2 = (value) => value === "native" || value === "partial";
|
|
20271
|
-
var
|
|
20467
|
+
var NONE5 = { kind: "none" };
|
|
20272
20468
|
var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
|
|
20273
20469
|
var offered = new Set;
|
|
20274
20470
|
var ROLES = {
|
|
@@ -20282,13 +20478,20 @@ var workitRole = (descriptor, agentType) => {
|
|
|
20282
20478
|
return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
|
|
20283
20479
|
};
|
|
20284
20480
|
var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
|
|
20285
|
-
var
|
|
20481
|
+
var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
|
|
20482
|
+
var ISOLATED_GIT = 'Run git as plain, separate commands inside your worktree (`git switch -c <branch> <base>`, `git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a commit-msg hook that adds the trailer), never `workit git …`: Claude Code\'s worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (`workit check`, `workit ledger`, `workit pr`).';
|
|
20483
|
+
var subagentStartText = (input, descriptor, event) => {
|
|
20484
|
+
const session = input.session.id ?? null;
|
|
20286
20485
|
const role = workitRole(descriptor, event.agentType);
|
|
20287
20486
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
return `Workit observed ${who}
|
|
20291
|
-
|
|
20487
|
+
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
20488
|
+
if (role === "implementer" && isolated)
|
|
20489
|
+
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Use a policy-compliant branch (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
20490
|
+
if (isolated)
|
|
20491
|
+
return `${ISOLATED_GIT}
|
|
20492
|
+
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
20493
|
+
if (role === "implementer")
|
|
20494
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
20292
20495
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
20293
20496
|
if (role !== "judge" || !event.agentId)
|
|
20294
20497
|
return readOnly;
|
|
@@ -20301,7 +20504,7 @@ function handleHook(input, deps) {
|
|
|
20301
20504
|
switch (event.kind) {
|
|
20302
20505
|
case "session.start": {
|
|
20303
20506
|
if (!usable2(descriptor.context.sessionStart))
|
|
20304
|
-
return
|
|
20507
|
+
return NONE5;
|
|
20305
20508
|
const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
|
|
20306
20509
|
const offer = event.source === "startup" && (key === null || !offered.has(key));
|
|
20307
20510
|
if (offer && key !== null)
|
|
@@ -20316,42 +20519,44 @@ function handleHook(input, deps) {
|
|
|
20316
20519
|
}
|
|
20317
20520
|
case "context.turn": {
|
|
20318
20521
|
if (!usable2(descriptor.context.perTurn))
|
|
20319
|
-
return
|
|
20522
|
+
return NONE5;
|
|
20320
20523
|
const current = turnContextText(input, descriptor);
|
|
20321
20524
|
const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
|
|
20322
|
-
return withContextLine(text ? { kind: "context", text } :
|
|
20525
|
+
return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
|
|
20323
20526
|
}
|
|
20324
20527
|
case "shell.pre": {
|
|
20325
20528
|
const skillRead = skillReadIn(event.command, event.dialect);
|
|
20326
20529
|
if (skillRead)
|
|
20327
20530
|
recordSkillLoad(input, skillRead, "read");
|
|
20328
20531
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
20329
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) :
|
|
20532
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
20330
20533
|
if (policy.kind !== "none")
|
|
20331
20534
|
return policy;
|
|
20332
20535
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
20333
20536
|
if (!postTool)
|
|
20334
20537
|
settlePendingCommit(input);
|
|
20335
|
-
const raw = canDeny ? rawGitPre(input, event.command) :
|
|
20538
|
+
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
20336
20539
|
if (raw.kind === "deny")
|
|
20337
20540
|
return raw;
|
|
20338
20541
|
const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
|
|
20339
|
-
const gate = writes.writes ? writeGate(input.cwd, writes.targets) :
|
|
20542
|
+
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
|
|
20340
20543
|
if (gate.kind === "deny")
|
|
20341
20544
|
return gate;
|
|
20342
20545
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
20343
|
-
|
|
20546
|
+
if (descriptor.transport !== "hook-process")
|
|
20547
|
+
return NONE5;
|
|
20548
|
+
return withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, shipNudge(input, event.command));
|
|
20344
20549
|
}
|
|
20345
20550
|
case "shell.post":
|
|
20346
20551
|
if (usable2(descriptor.events["shell.post"].support))
|
|
20347
20552
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
20348
|
-
return
|
|
20553
|
+
return NONE5;
|
|
20349
20554
|
case "write.pre":
|
|
20350
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) :
|
|
20555
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
|
|
20351
20556
|
case "subagent.start":
|
|
20352
20557
|
return {
|
|
20353
20558
|
kind: "context",
|
|
20354
|
-
text: subagentStartText(input
|
|
20559
|
+
text: subagentStartText(input, descriptor, event)
|
|
20355
20560
|
};
|
|
20356
20561
|
case "compact.pre":
|
|
20357
20562
|
return {
|
|
@@ -20361,11 +20566,12 @@ function handleHook(input, deps) {
|
|
|
20361
20566
|
case "tool.pre":
|
|
20362
20567
|
if (event.skill)
|
|
20363
20568
|
recordSkillLoad(input, event.skill, "tool");
|
|
20364
|
-
return
|
|
20569
|
+
return NONE5;
|
|
20570
|
+
case "stop":
|
|
20571
|
+
return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
|
|
20365
20572
|
case "subagent.stop":
|
|
20366
20573
|
case "prompt.submit":
|
|
20367
|
-
|
|
20368
|
-
return NONE4;
|
|
20574
|
+
return NONE5;
|
|
20369
20575
|
}
|
|
20370
20576
|
}
|
|
20371
20577
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
@@ -20384,6 +20590,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
|
20384
20590
|
var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
20385
20591
|
|
|
20386
20592
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
20593
|
+
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
20387
20594
|
var BLOCKING2 = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
20388
20595
|
|
|
20389
20596
|
// packages/workit-core/src/hooks/run.ts
|
|
@@ -20406,7 +20613,7 @@ var PI_DESCRIPTOR = {
|
|
|
20406
20613
|
"subagent.stop": { support: "none", native: null },
|
|
20407
20614
|
"prompt.submit": { support: "undocumented", native: null },
|
|
20408
20615
|
"compact.pre": { support: "native", native: "session_before_compact" },
|
|
20409
|
-
stop: { support: "
|
|
20616
|
+
stop: { support: "native", native: "agent_end" }
|
|
20410
20617
|
},
|
|
20411
20618
|
shellPolicy: { deny: "native", channel: "block", failClosed: false },
|
|
20412
20619
|
context: {
|
|
@@ -20426,7 +20633,7 @@ var PI_DESCRIPTOR = {
|
|
|
20426
20633
|
},
|
|
20427
20634
|
provenance: { sessionId: "native", agentIdOnTool: "none", postToolObserve: "native" },
|
|
20428
20635
|
interaction: { questions: "native", writeBoundary: "native" },
|
|
20429
|
-
stopControl: "
|
|
20636
|
+
stopControl: "native",
|
|
20430
20637
|
shellAvailable: "native",
|
|
20431
20638
|
perEventCost: "low",
|
|
20432
20639
|
capabilities: [
|
|
@@ -21244,9 +21451,29 @@ var persistUncertainCancel = (store, ctx, taskId, workerId, handle, exit) => {
|
|
|
21244
21451
|
};
|
|
21245
21452
|
return observeWorkerExit(lostBinding, handle, exit).ok;
|
|
21246
21453
|
};
|
|
21454
|
+
var lastAssistantText = (messages) => {
|
|
21455
|
+
for (const message of (messages ?? []).toReversed()) {
|
|
21456
|
+
if (typeof message !== "object" || message === null)
|
|
21457
|
+
continue;
|
|
21458
|
+
const { role, content, stopReason } = message;
|
|
21459
|
+
if (role !== "assistant")
|
|
21460
|
+
continue;
|
|
21461
|
+
if (stopReason === "aborted" || stopReason === "error")
|
|
21462
|
+
return null;
|
|
21463
|
+
if (typeof content === "string")
|
|
21464
|
+
return content;
|
|
21465
|
+
if (!Array.isArray(content))
|
|
21466
|
+
return null;
|
|
21467
|
+
const text = content.filter((part) => typeof part === "object" && part !== null && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
|
|
21468
|
+
`);
|
|
21469
|
+
return text || null;
|
|
21470
|
+
}
|
|
21471
|
+
return null;
|
|
21472
|
+
};
|
|
21247
21473
|
function extension(pi) {
|
|
21248
21474
|
const sessions = new Set;
|
|
21249
21475
|
const historyOfferSessions = new Set;
|
|
21476
|
+
const continuedRuns = new Set;
|
|
21250
21477
|
const workers = new Map;
|
|
21251
21478
|
const bindings = new Map;
|
|
21252
21479
|
const childWorker = process.env.WORKIT_PI_WORKER_ID;
|
|
@@ -21551,6 +21778,28 @@ ${offer}` : ""}`;
|
|
|
21551
21778
|
${nudge}` : content) };
|
|
21552
21779
|
return nudge ? { message: { customType: "workit-skill", content: nudge, display: false } } : undefined;
|
|
21553
21780
|
});
|
|
21781
|
+
pi.on("agent_end", (event, ctx) => {
|
|
21782
|
+
try {
|
|
21783
|
+
const id = ctx.sessionManager.getSessionId();
|
|
21784
|
+
if (continuedRuns.delete(id) || childWorker || !ctx.isProjectTrusted())
|
|
21785
|
+
return;
|
|
21786
|
+
const send = pi.sendMessage;
|
|
21787
|
+
if (typeof send !== "function")
|
|
21788
|
+
return;
|
|
21789
|
+
const decision = stopDecision({
|
|
21790
|
+
...turnInput(ctx),
|
|
21791
|
+
event: {
|
|
21792
|
+
kind: "stop",
|
|
21793
|
+
lastMessage: lastAssistantText(event.messages),
|
|
21794
|
+
stopHookActive: false
|
|
21795
|
+
}
|
|
21796
|
+
}, PI_DESCRIPTOR);
|
|
21797
|
+
if (decision.kind !== "continue")
|
|
21798
|
+
return;
|
|
21799
|
+
continuedRuns.add(id);
|
|
21800
|
+
send.call(pi, { customType: "workit-stop", content: decision.reason, display: true }, { triggerTurn: true, deliverAs: "followUp" });
|
|
21801
|
+
} catch {}
|
|
21802
|
+
});
|
|
21554
21803
|
pi.on("session_before_compact", () => {
|
|
21555
21804
|
return;
|
|
21556
21805
|
});
|
|
@@ -21590,5 +21839,6 @@ ${nudge}` : content) };
|
|
|
21590
21839
|
}
|
|
21591
21840
|
export {
|
|
21592
21841
|
extension as default,
|
|
21842
|
+
lastAssistantText,
|
|
21593
21843
|
persistUncertainCancel
|
|
21594
21844
|
};
|