@open-mercato/cezar 0.1.5-pr506.357
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 +573 -0
- package/dist/config.d.ts +144 -0
- package/dist/config.js +106 -0
- package/dist/config.js.map +1 -0
- package/dist/core/agent-runner.d.ts +169 -0
- package/dist/core/agent-runner.js +23 -0
- package/dist/core/agent-runner.js.map +1 -0
- package/dist/core/backend-detect.d.ts +16 -0
- package/dist/core/backend-detect.js +118 -0
- package/dist/core/backend-detect.js.map +1 -0
- package/dist/core/claude-cli-runner.d.ts +56 -0
- package/dist/core/claude-cli-runner.js +416 -0
- package/dist/core/claude-cli-runner.js.map +1 -0
- package/dist/core/claude-ui-mapper.d.ts +71 -0
- package/dist/core/claude-ui-mapper.js +567 -0
- package/dist/core/claude-ui-mapper.js.map +1 -0
- package/dist/core/codex-app-server-runner.d.ts +29 -0
- package/dist/core/codex-app-server-runner.js +489 -0
- package/dist/core/codex-app-server-runner.js.map +1 -0
- package/dist/core/codex-ui-mapper.d.ts +63 -0
- package/dist/core/codex-ui-mapper.js +491 -0
- package/dist/core/codex-ui-mapper.js.map +1 -0
- package/dist/core/ndjson.d.ts +6 -0
- package/dist/core/ndjson.js +23 -0
- package/dist/core/ndjson.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +30 -0
- package/dist/core/opencode-server-runner.js +488 -0
- package/dist/core/opencode-server-runner.js.map +1 -0
- package/dist/core/opencode-ui-mapper.d.ts +105 -0
- package/dist/core/opencode-ui-mapper.js +588 -0
- package/dist/core/opencode-ui-mapper.js.map +1 -0
- package/dist/core/process-usage.d.ts +63 -0
- package/dist/core/process-usage.js +181 -0
- package/dist/core/process-usage.js.map +1 -0
- package/dist/core/runner-factory.d.ts +8 -0
- package/dist/core/runner-factory.js +22 -0
- package/dist/core/runner-factory.js.map +1 -0
- package/dist/core/tool-display.d.ts +30 -0
- package/dist/core/tool-display.js +156 -0
- package/dist/core/tool-display.js.map +1 -0
- package/dist/core/ui-events.d.ts +308 -0
- package/dist/core/ui-events.js +25 -0
- package/dist/core/ui-events.js.map +1 -0
- package/dist/core/usage.d.ts +11 -0
- package/dist/core/usage.js +15 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/git-worktree.d.ts +88 -0
- package/dist/git-worktree.js +297 -0
- package/dist/git-worktree.js.map +1 -0
- package/dist/handoff.d.ts +62 -0
- package/dist/handoff.js +147 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +466 -0
- package/dist/index.js.map +1 -0
- package/dist/install-as-command.d.ts +39 -0
- package/dist/install-as-command.js +51 -0
- package/dist/install-as-command.js.map +1 -0
- package/dist/pack-check.d.ts +17 -0
- package/dist/pack-check.js +27 -0
- package/dist/pack-check.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.js +34 -0
- package/dist/paths.js.map +1 -0
- package/dist/planner.d.ts +17 -0
- package/dist/planner.js +248 -0
- package/dist/planner.js.map +1 -0
- package/dist/release/snapshot.d.ts +64 -0
- package/dist/release/snapshot.js +75 -0
- package/dist/release/snapshot.js.map +1 -0
- package/dist/release/stable.d.ts +46 -0
- package/dist/release/stable.js +57 -0
- package/dist/release/stable.js.map +1 -0
- package/dist/runs/auto-name.d.ts +75 -0
- package/dist/runs/auto-name.js +168 -0
- package/dist/runs/auto-name.js.map +1 -0
- package/dist/runs/retention.d.ts +60 -0
- package/dist/runs/retention.js +86 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/review-gate.d.ts +18 -0
- package/dist/runs/review-gate.js +21 -0
- package/dist/runs/review-gate.js.map +1 -0
- package/dist/runs/store.d.ts +408 -0
- package/dist/runs/store.js +526 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/task-refs.d.ts +24 -0
- package/dist/runs/task-refs.js +74 -0
- package/dist/runs/task-refs.js.map +1 -0
- package/dist/runs/ui-event-sink.d.ts +81 -0
- package/dist/runs/ui-event-sink.js +210 -0
- package/dist/runs/ui-event-sink.js.map +1 -0
- package/dist/server/capabilities.d.ts +33 -0
- package/dist/server/capabilities.js +43 -0
- package/dist/server/capabilities.js.map +1 -0
- package/dist/server/forge/github.d.ts +70 -0
- package/dist/server/forge/github.js +375 -0
- package/dist/server/forge/github.js.map +1 -0
- package/dist/server/forge/index.d.ts +24 -0
- package/dist/server/forge/index.js +45 -0
- package/dist/server/forge/index.js.map +1 -0
- package/dist/server/forge/types.d.ts +80 -0
- package/dist/server/forge/types.js +2 -0
- package/dist/server/forge/types.js.map +1 -0
- package/dist/server/git-changes.d.ts +195 -0
- package/dist/server/git-changes.js +497 -0
- package/dist/server/git-changes.js.map +1 -0
- package/dist/server/git.d.ts +26 -0
- package/dist/server/git.js +103 -0
- package/dist/server/git.js.map +1 -0
- package/dist/server/github.d.ts +8 -0
- package/dist/server/github.js +8 -0
- package/dist/server/github.js.map +1 -0
- package/dist/server/launch-key.d.ts +7 -0
- package/dist/server/launch-key.js +33 -0
- package/dist/server/launch-key.js.map +1 -0
- package/dist/server/open-in-app.d.ts +51 -0
- package/dist/server/open-in-app.js +189 -0
- package/dist/server/open-in-app.js.map +1 -0
- package/dist/server/open-in-terminal.d.ts +25 -0
- package/dist/server/open-in-terminal.js +120 -0
- package/dist/server/open-in-terminal.js.map +1 -0
- package/dist/server/pr.d.ts +7 -0
- package/dist/server/pr.js +7 -0
- package/dist/server/pr.js.map +1 -0
- package/dist/server/server.d.ts +41 -0
- package/dist/server/server.js +1390 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/static-ui.d.ts +49 -0
- package/dist/server/static-ui.js +92 -0
- package/dist/server/static-ui.js.map +1 -0
- package/dist/server/wsl.d.ts +25 -0
- package/dist/server/wsl.js +92 -0
- package/dist/server/wsl.js.map +1 -0
- package/dist/server-install/engine.d.ts +33 -0
- package/dist/server-install/engine.js +314 -0
- package/dist/server-install/engine.js.map +1 -0
- package/dist/server-install/platforms/macosx-ngrok.d.ts +6 -0
- package/dist/server-install/platforms/macosx-ngrok.js +345 -0
- package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps.d.ts +39 -0
- package/dist/server-install/platforms/ubuntu-vps.js +746 -0
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
- package/dist/server-install/state.d.ts +26 -0
- package/dist/server-install/state.js +125 -0
- package/dist/server-install/state.js.map +1 -0
- package/dist/server-install/steps.d.ts +109 -0
- package/dist/server-install/steps.js +330 -0
- package/dist/server-install/steps.js.map +1 -0
- package/dist/server-install/strategies.d.ts +4 -0
- package/dist/server-install/strategies.js +20 -0
- package/dist/server-install/strategies.js.map +1 -0
- package/dist/server-install/types.d.ts +597 -0
- package/dist/server-install/types.js +98 -0
- package/dist/server-install/types.js.map +1 -0
- package/dist/server-install/ui.d.ts +42 -0
- package/dist/server-install/ui.js +130 -0
- package/dist/server-install/ui.js.map +1 -0
- package/dist/skills-banner.d.ts +21 -0
- package/dist/skills-banner.js +40 -0
- package/dist/skills-banner.js.map +1 -0
- package/dist/skills-remote.d.ts +35 -0
- package/dist/skills-remote.js +266 -0
- package/dist/skills-remote.js.map +1 -0
- package/dist/skills.d.ts +45 -0
- package/dist/skills.js +182 -0
- package/dist/skills.js.map +1 -0
- package/dist/todos.d.ts +63 -0
- package/dist/todos.js +168 -0
- package/dist/todos.js.map +1 -0
- package/dist/ui-state.d.ts +12 -0
- package/dist/ui-state.js +25 -0
- package/dist/ui-state.js.map +1 -0
- package/dist/update-check.d.ts +12 -0
- package/dist/update-check.js +41 -0
- package/dist/update-check.js.map +1 -0
- package/dist/workflows/load.d.ts +15 -0
- package/dist/workflows/load.js +56 -0
- package/dist/workflows/load.js.map +1 -0
- package/dist/workflows/run.d.ts +281 -0
- package/dist/workflows/run.js +1511 -0
- package/dist/workflows/run.js.map +1 -0
- package/dist/workflows/types.d.ts +307 -0
- package/dist/workflows/types.js +171 -0
- package/dist/workflows/types.js.map +1 -0
- package/package.json +90 -0
- package/scripts/check-pack.mjs +46 -0
- package/scripts/dev.mjs +74 -0
- package/scripts/install-as-command.mjs +120 -0
- package/scripts/mock-claude.mjs +310 -0
- package/scripts/release-snapshot.mjs +130 -0
- package/scripts/release.mjs +119 -0
- package/scripts/test-process-usage.mjs +47 -0
- package/web/dist/assets/arrow-left-BE957rJs.js +1 -0
- package/web/dist/assets/bundle-mjs-DLPpRFyl.js +1 -0
- package/web/dist/assets/centered-state-BddhEEtO.js +43 -0
- package/web/dist/assets/chunk-BO2N2NFS-f5igTtpB.js +131 -0
- package/web/dist/assets/command-CjhFbABo.js +1 -0
- package/web/dist/assets/compare-variants-Cs52IAEG.js +1 -0
- package/web/dist/assets/core-BuMqAF_1.js +12 -0
- package/web/dist/assets/css-BsVw1vtW.js +1 -0
- package/web/dist/assets/dialog-DFvXTkFv.js +1 -0
- package/web/dist/assets/diff-view-C9b8AITq.js +4 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/dist-FBs4wmIY.js +1 -0
- package/web/dist/assets/dist-o_kVMXjg.js +1 -0
- package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
- package/web/dist/assets/file-BVuztAOd.js +1 -0
- package/web/dist/assets/folder-0pJ1NZ_S.js +1 -0
- package/web/dist/assets/git-pull-request-DJkAMT0f.js +1 -0
- package/web/dist/assets/git-toolbar-CwhQdx3J.js +1 -0
- package/web/dist/assets/github-D0rAX4-r.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/highlighted-body-OFNGDK62-DGHHie_q.js +1 -0
- package/web/dist/assets/highlighter-BKVEsACy.js +3 -0
- package/web/dist/assets/html-BY76lXLH.js +1 -0
- package/web/dist/assets/index-CEkI7QxN.css +2 -0
- package/web/dist/assets/index-CO2lH2et.js +11 -0
- package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web/dist/assets/json-qhed-kSA.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
- package/web/dist/assets/lib-CNGa3Dig.js +1 -0
- package/web/dist/assets/loader-circle-BV3DO9mE.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/markdown-D4-ADgH9.js +1 -0
- package/web/dist/assets/mermaid-GHXKKRXX-DM2VsGBG.js +1 -0
- package/web/dist/assets/open-mercato-toBr6SOa.svg +11 -0
- package/web/dist/assets/prompt-templates-C7ptKvDd.js +15 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/react-dom-Ddtik4qM.js +1 -0
- package/web/dist/assets/refresh-cw-DCa-Rlwz.js +1 -0
- package/web/dist/assets/repo-git-DIhryGDx.js +1 -0
- package/web/dist/assets/run-diff-Dyow8B1q.js +3 -0
- package/web/dist/assets/run-header-jx9-18b8.js +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/search-x-CoCyl7zT.js +1 -0
- package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
- package/web/dist/assets/skill-detail-DB7x_FQp.js +1 -0
- package/web/dist/assets/skills-98vCroU8.js +1 -0
- package/web/dist/assets/skills-BKIoqD6K.js +1 -0
- package/web/dist/assets/sql-BsFa4tDR.js +1 -0
- package/web/dist/assets/square-terminal-B6PZX4Qp.js +1 -0
- package/web/dist/assets/tab-link-C5--YMQd.js +1 -0
- package/web/dist/assets/task-changes-ejxPGVJw.js +1 -0
- package/web/dist/assets/task-commits-C6GO5qT-.js +1 -0
- package/web/dist/assets/task-files-CatpCBXa.js +2 -0
- package/web/dist/assets/task-thread-BMbXDM3d.js +5 -0
- package/web/dist/assets/tasks-table-dgezlCcg.js +1 -0
- package/web/dist/assets/trash-2-CDtxmmc8.js +1 -0
- package/web/dist/assets/triangle-alert-6su4Js5O.js +1 -0
- package/web/dist/assets/tsx-udAQXfEw.js +1 -0
- package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
- package/web/dist/assets/upload-Bp7hQxll.js +1 -0
- package/web/dist/assets/use-desktop-CvTwojOJ.js +3 -0
- package/web/dist/assets/utils-rQGbOrwc.js +1 -0
- package/web/dist/assets/workflows-Dy2c6790.js +11 -0
- package/web/dist/assets/x-Br_jIDBw.js +1 -0
- package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
- package/web/dist/index.html +70 -0
- package/web/open-mercato.svg +11 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { readdir, rm } from 'node:fs/promises';
|
|
4
|
+
import { basename, join, resolve } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Git worktree per task (spec 006). Each run gets its own branch
|
|
7
|
+
* `cez/<id8>` checked out into `.ai/cezar/worktrees/<runId>` so agents never
|
|
8
|
+
* touch the user's working tree. Pattern ported from github-janitor's
|
|
9
|
+
* `git.ts` (createWorktree / autosaveCommit), minus the bare clone — we're
|
|
10
|
+
* already inside a working copy. Everything degrades: helpers never throw
|
|
11
|
+
* except `createWorktree`, whose failure the caller turns into a note.
|
|
12
|
+
*/
|
|
13
|
+
/** Repo-relative home of all task worktrees (gitignored via .ai/cezar/.gitignore). */
|
|
14
|
+
export const WORKTREES_DIR = '.ai/cezar/worktrees';
|
|
15
|
+
const DIFF_CAP = 400_000;
|
|
16
|
+
/** Run git, never throw — degradation is the caller's policy. */
|
|
17
|
+
function git(cwd, args) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
execFile('git', args, { cwd, maxBuffer: 32 * 1024 * 1024, encoding: 'utf8' }, (err, stdout, stderr) => resolve({ ok: !err, stdout: stdout ?? '', stderr: stderr ?? '' }));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function branchFor(runId) {
|
|
23
|
+
return `cez/${runId.slice(0, 8)}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the configured base branch to something `git worktree add` can
|
|
27
|
+
* fork from: the branch itself, else its remote-tracking ref (base exists
|
|
28
|
+
* only on origin), else null — the caller falls back to the current branch
|
|
29
|
+
* with a note. Never throws.
|
|
30
|
+
*/
|
|
31
|
+
export async function resolveBaseRef(repoRoot, base) {
|
|
32
|
+
for (const ref of [base, `origin/${base}`]) {
|
|
33
|
+
const res = await git(repoRoot, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
34
|
+
if (res.ok)
|
|
35
|
+
return ref;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function worktreePathFor(repoRoot, runId) {
|
|
40
|
+
return join(repoRoot, WORKTREES_DIR, runId);
|
|
41
|
+
}
|
|
42
|
+
/** Parse `git worktree list --porcelain` without assuming paths contain no spaces. */
|
|
43
|
+
async function registeredWorktrees(repoRoot) {
|
|
44
|
+
const res = await git(repoRoot, ['worktree', 'list', '--porcelain']);
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
return [];
|
|
47
|
+
const worktrees = [];
|
|
48
|
+
let current;
|
|
49
|
+
for (const line of res.stdout.split('\n')) {
|
|
50
|
+
if (line.startsWith('worktree ')) {
|
|
51
|
+
if (current)
|
|
52
|
+
worktrees.push(current);
|
|
53
|
+
current = { path: line.slice('worktree '.length) };
|
|
54
|
+
}
|
|
55
|
+
else if (current && line.startsWith('branch ')) {
|
|
56
|
+
current.branch = line.slice('branch '.length);
|
|
57
|
+
}
|
|
58
|
+
else if (!line && current) {
|
|
59
|
+
worktrees.push(current);
|
|
60
|
+
current = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (current)
|
|
64
|
+
worktrees.push(current);
|
|
65
|
+
return worktrees;
|
|
66
|
+
}
|
|
67
|
+
function worktreeInfo(path, branch, baseBranch) {
|
|
68
|
+
return { path, branch, baseBranch };
|
|
69
|
+
}
|
|
70
|
+
/** Git canonicalizes symlinked path prefixes (macOS `/var` → `/private/var`). */
|
|
71
|
+
function canonicalPath(path) {
|
|
72
|
+
try {
|
|
73
|
+
return realpathSync(path);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return resolve(path);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Establish the task worktree idempotently. Besides the fresh
|
|
81
|
+
* `git worktree add -b` path, recover the two normal restart/deletion cases:
|
|
82
|
+
* reuse an already-registered task worktree, or reattach a surviving task
|
|
83
|
+
* branch after its directory/registration was removed. Existing non-empty
|
|
84
|
+
* unregistered paths are never deleted because they may hold recoverable
|
|
85
|
+
* uncommitted work.
|
|
86
|
+
*/
|
|
87
|
+
export async function createWorktree(repoRoot, runId, baseBranch) {
|
|
88
|
+
let base = baseBranch;
|
|
89
|
+
if (!base || base === 'HEAD') {
|
|
90
|
+
// Detached HEAD — pin the base to the current commit so the record and
|
|
91
|
+
// later diffs stay meaningful.
|
|
92
|
+
const head = await git(repoRoot, ['rev-parse', 'HEAD']);
|
|
93
|
+
if (!head.ok)
|
|
94
|
+
throw new Error(`git rev-parse HEAD failed: ${head.stderr.trim()}`);
|
|
95
|
+
base = head.stdout.trim();
|
|
96
|
+
}
|
|
97
|
+
const branch = branchFor(runId);
|
|
98
|
+
const absolutePath = join(canonicalPath(repoRoot), WORKTREES_DIR, runId);
|
|
99
|
+
const branchRef = `refs/heads/${branch}`;
|
|
100
|
+
// A missing directory can leave stale administrative metadata behind.
|
|
101
|
+
// Prune first so the checks below describe the filesystem as it exists now.
|
|
102
|
+
await git(repoRoot, ['worktree', 'prune']);
|
|
103
|
+
const canonicalTarget = canonicalPath(absolutePath);
|
|
104
|
+
let registered = await registeredWorktrees(repoRoot);
|
|
105
|
+
let atPath = registered.find((item) => canonicalPath(item.path) === canonicalTarget);
|
|
106
|
+
if (atPath) {
|
|
107
|
+
if (atPath.branch !== branchRef) {
|
|
108
|
+
throw new Error(`managed worktree path is registered to ${atPath.branch ?? 'a detached HEAD'}, expected ${branchRef}`);
|
|
109
|
+
}
|
|
110
|
+
return worktreeInfo(atPath.path, branch, base);
|
|
111
|
+
}
|
|
112
|
+
// If the directory survived but its administrative entry did not, let Git
|
|
113
|
+
// repair it. This is non-destructive; an unrepairable non-empty directory is
|
|
114
|
+
// preserved and reported instead of being recursively removed.
|
|
115
|
+
if (existsSync(absolutePath)) {
|
|
116
|
+
await git(repoRoot, ['worktree', 'repair', absolutePath]);
|
|
117
|
+
registered = await registeredWorktrees(repoRoot);
|
|
118
|
+
atPath = registered.find((item) => canonicalPath(item.path) === canonicalTarget);
|
|
119
|
+
if (atPath) {
|
|
120
|
+
if (atPath.branch !== branchRef) {
|
|
121
|
+
throw new Error(`managed worktree path is registered to ${atPath.branch ?? 'a detached HEAD'}, expected ${branchRef}`);
|
|
122
|
+
}
|
|
123
|
+
return worktreeInfo(atPath.path, branch, base);
|
|
124
|
+
}
|
|
125
|
+
const entries = await readdir(absolutePath).catch(() => ['unreadable']);
|
|
126
|
+
if (entries.length > 0) {
|
|
127
|
+
throw new Error(`managed worktree path already exists and could not be repaired: ${absolutePath}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const branchExists = await git(repoRoot, ['show-ref', '--verify', '--quiet', branchRef]);
|
|
131
|
+
if (branchExists.ok) {
|
|
132
|
+
// The task branch may still be checked out after its directory was moved.
|
|
133
|
+
// Reuse only a path whose basename is the full run id; a same-prefix UUID
|
|
134
|
+
// collision must fail rather than point two tasks at one worktree.
|
|
135
|
+
const byBranch = registered.find((item) => item.branch === branchRef);
|
|
136
|
+
if (byBranch) {
|
|
137
|
+
if (basename(byBranch.path) !== runId) {
|
|
138
|
+
throw new Error(`task branch ${branch} is already checked out at ${byBranch.path}`);
|
|
139
|
+
}
|
|
140
|
+
return worktreeInfo(byBranch.path, branch, base);
|
|
141
|
+
}
|
|
142
|
+
const attach = await git(repoRoot, ['worktree', 'add', absolutePath, branch]);
|
|
143
|
+
if (!attach.ok) {
|
|
144
|
+
throw new Error(`git worktree reattach failed: ${attach.stderr.trim() || attach.stdout.trim()}`);
|
|
145
|
+
}
|
|
146
|
+
return worktreeInfo(absolutePath, branch, base);
|
|
147
|
+
}
|
|
148
|
+
const create = await git(repoRoot, ['worktree', 'add', '-b', branch, absolutePath, base]);
|
|
149
|
+
if (!create.ok) {
|
|
150
|
+
throw new Error(`git worktree add failed: ${create.stderr.trim() || create.stdout.trim()}`);
|
|
151
|
+
}
|
|
152
|
+
return worktreeInfo(absolutePath, branch, base);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Best-effort on-disk size of a worktree directory in bytes, via POSIX
|
|
156
|
+
* `du -sk` (kibibytes → bytes). Returns `null` when `du` is unavailable —
|
|
157
|
+
* including all of Windows, where `du` is not a command — or on any error.
|
|
158
|
+
* Never throws and never blocks: worktree retention is count-based, so a null
|
|
159
|
+
* size only blanks the panel's size column, it does not affect reclamation.
|
|
160
|
+
*/
|
|
161
|
+
export function worktreeSizeBytes(path) {
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
execFile('du', ['-sk', path], { encoding: 'utf8' }, (err, stdout) => {
|
|
164
|
+
if (err) {
|
|
165
|
+
resolve(null);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const kib = Number.parseInt(stdout.trim().split(/\s+/)[0] ?? '', 10);
|
|
169
|
+
resolve(Number.isFinite(kib) ? kib * 1024 : null);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** Remove a task worktree and its branch. Best effort — never throws. */
|
|
174
|
+
export async function removeWorktree(repoRoot, worktreePath, branch) {
|
|
175
|
+
await git(repoRoot, ['worktree', 'remove', '--force', worktreePath]);
|
|
176
|
+
await rm(worktreePath, { recursive: true, force: true }).catch(() => undefined);
|
|
177
|
+
await git(repoRoot, ['worktree', 'prune']);
|
|
178
|
+
if (branch)
|
|
179
|
+
await git(repoRoot, ['branch', '-D', branch]);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Stage and commit everything in the worktree as a "cezar autosave" commit
|
|
183
|
+
* (janitor pattern) — the agent's progress is always recoverable from the
|
|
184
|
+
* `cez/<id8>` branch history. Quietly a no-op when nothing changed.
|
|
185
|
+
*/
|
|
186
|
+
export async function autosaveCommit(dir) {
|
|
187
|
+
const status = await git(dir, ['status', '--porcelain']);
|
|
188
|
+
if (!status.ok || !status.stdout.trim())
|
|
189
|
+
return false;
|
|
190
|
+
await git(dir, ['add', '-A']);
|
|
191
|
+
// Commit as the CURRENT git user, so the branch's commits (and any PR opened from it) are
|
|
192
|
+
// attributed to the real author and pass CLA / attribution checks. The old hardcoded
|
|
193
|
+
// `cezar <cezar@local>` identity made every autosave look like a non-GitHub user. Fall back to
|
|
194
|
+
// that identity ONLY when the machine has no git identity configured — otherwise `git commit`
|
|
195
|
+
// would fail and the autosave (the run's recovery point) would be lost.
|
|
196
|
+
const identityArgs = (await gitHasIdentity(dir))
|
|
197
|
+
? []
|
|
198
|
+
: ['-c', 'user.name=cezar', '-c', 'user.email=cezar@local'];
|
|
199
|
+
const commit = await git(dir, [
|
|
200
|
+
...identityArgs,
|
|
201
|
+
'commit',
|
|
202
|
+
'--no-verify',
|
|
203
|
+
'-m',
|
|
204
|
+
'cezar autosave',
|
|
205
|
+
]);
|
|
206
|
+
return commit.ok;
|
|
207
|
+
}
|
|
208
|
+
/** Does this repo/worktree resolve a git author identity (name + email)? Ambient config wins so
|
|
209
|
+
* autosave commits carry the user's own identity — see autosaveCommit. */
|
|
210
|
+
async function gitHasIdentity(dir) {
|
|
211
|
+
const [name, email] = await Promise.all([
|
|
212
|
+
git(dir, ['config', 'user.name']),
|
|
213
|
+
git(dir, ['config', 'user.email']),
|
|
214
|
+
]);
|
|
215
|
+
return name.ok && name.stdout.trim() !== '' && email.ok && email.stdout.trim() !== '';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* "What did this task change": diff of the worktree (committed + uncommitted
|
|
219
|
+
* + untracked, via `add -N`) against the merge-base with its base branch —
|
|
220
|
+
* so the diff stays *this task's* changes even after the base moves on.
|
|
221
|
+
*/
|
|
222
|
+
export async function worktreeDiff(worktreePath, baseBranch, cap = DIFF_CAP) {
|
|
223
|
+
await git(worktreePath, ['add', '-N', '.']); // intent-to-add: untracked files show up
|
|
224
|
+
const mergeBase = await git(worktreePath, ['merge-base', baseBranch, 'HEAD']);
|
|
225
|
+
const base = mergeBase.ok && mergeBase.stdout.trim() ? mergeBase.stdout.trim() : baseBranch;
|
|
226
|
+
const res = await git(worktreePath, ['diff', base]);
|
|
227
|
+
if (!res.ok)
|
|
228
|
+
return `(diff failed: ${res.stderr.trim() || 'unknown git error'})`;
|
|
229
|
+
if (res.stdout.length > cap)
|
|
230
|
+
return `${res.stdout.slice(0, cap)}\n… (diff truncated)`;
|
|
231
|
+
return res.stdout;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* `git diff --stat` version of `worktreeDiff` (spec 010 — the variant
|
|
235
|
+
* comparison columns). Same merge-base anchoring; returns '' on any failure.
|
|
236
|
+
*/
|
|
237
|
+
export async function worktreeDiffStat(worktreePath, baseBranch) {
|
|
238
|
+
await git(worktreePath, ['add', '-N', '.']); // intent-to-add: untracked files show up
|
|
239
|
+
const mergeBase = await git(worktreePath, ['merge-base', baseBranch, 'HEAD']);
|
|
240
|
+
const base = mergeBase.ok && mergeBase.stdout.trim() ? mergeBase.stdout.trim() : baseBranch;
|
|
241
|
+
const res = await git(worktreePath, ['diff', '--stat', base]);
|
|
242
|
+
return res.ok ? res.stdout.trim() : '';
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Parse `git diff --shortstat` output — " 3 files changed, 10 insertions(+),
|
|
246
|
+
* 2 deletions(-)". Every part is optional: insertions-only and deletions-only
|
|
247
|
+
* diffs omit the other counter, and an empty diff prints nothing at all
|
|
248
|
+
* (→ all zeros). The wording is stable porcelain English — git does not
|
|
249
|
+
* localize `--shortstat` — so matching the words is safe.
|
|
250
|
+
*/
|
|
251
|
+
export function parseShortstat(s) {
|
|
252
|
+
const files = /(\d+) files? changed/.exec(s);
|
|
253
|
+
const adds = /(\d+) insertions?\(\+\)/.exec(s);
|
|
254
|
+
const dels = /(\d+) deletions?\(-\)/.exec(s);
|
|
255
|
+
return {
|
|
256
|
+
files: files ? Number(files[1]) : 0,
|
|
257
|
+
adds: adds ? Number(adds[1]) : 0,
|
|
258
|
+
dels: dels ? Number(dels[1]) : 0,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* `git diff --shortstat` of the worktree vs its base (#389) — same
|
|
263
|
+
* merge-base anchoring and intent-to-add as `worktreeDiff`, parsed into
|
|
264
|
+
* numbers. Null on git failure (the caller notes it, never fails the run);
|
|
265
|
+
* an empty diff is a valid all-zero stat.
|
|
266
|
+
*/
|
|
267
|
+
export async function worktreeShortstat(worktreePath, baseBranch) {
|
|
268
|
+
await git(worktreePath, ['add', '-N', '.']); // intent-to-add: untracked files show up
|
|
269
|
+
const mergeBase = await git(worktreePath, ['merge-base', baseBranch, 'HEAD']);
|
|
270
|
+
const base = mergeBase.ok && mergeBase.stdout.trim() ? mergeBase.stdout.trim() : baseBranch;
|
|
271
|
+
const res = await git(worktreePath, ['diff', '--shortstat', base]);
|
|
272
|
+
return res.ok ? parseShortstat(res.stdout) : null;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Startup reconcile: `git worktree prune` + remove every directory under
|
|
276
|
+
* `.ai/cezar/worktrees/` whose run id is no longer in the store (and its
|
|
277
|
+
* branch). Returns the removed run ids for the boot log. Never throws.
|
|
278
|
+
*/
|
|
279
|
+
export async function pruneOrphans(repoRoot, validIds) {
|
|
280
|
+
await git(repoRoot, ['worktree', 'prune']);
|
|
281
|
+
let entries;
|
|
282
|
+
try {
|
|
283
|
+
entries = await readdir(join(repoRoot, WORKTREES_DIR), { withFileTypes: true });
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return []; // no worktrees dir yet
|
|
287
|
+
}
|
|
288
|
+
const removed = [];
|
|
289
|
+
for (const entry of entries) {
|
|
290
|
+
if (!entry.isDirectory() || validIds.has(entry.name))
|
|
291
|
+
continue;
|
|
292
|
+
await removeWorktree(repoRoot, worktreePathFor(repoRoot, entry.name), branchFor(entry.name));
|
|
293
|
+
removed.push(entry.name);
|
|
294
|
+
}
|
|
295
|
+
return removed;
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=git-worktree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-worktree.js","sourceRoot":"","sources":["../src/git-worktree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task handoff journal (spec 007): `.ai/cezar/runs/<runId>.handoff.md`,
|
|
3
|
+
* next to the run's NDJSON events and outside the task worktree — it survives
|
|
4
|
+
* worktree removal. Cez seeds the skeleton and appends heartbeats; the agent
|
|
5
|
+
* (told via CEZ_HANDOFF_FILE + the system-prompt fragment below) keeps the
|
|
6
|
+
* "Progress log" and "Resume notes" sections up to date. Everything here is
|
|
7
|
+
* best-effort: the handoff is a journal, never a reason to fail a run.
|
|
8
|
+
*/
|
|
9
|
+
export declare function handoffPath(dataDir: string, runId: string): string;
|
|
10
|
+
export interface HandoffSeed {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
workflow: string;
|
|
14
|
+
task: string;
|
|
15
|
+
branch?: string;
|
|
16
|
+
worktreePath?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Create the handoff skeleton. Idempotent — an existing file (resume,
|
|
19
|
+
* continuation) is never overwritten. Returns the file path. */
|
|
20
|
+
export declare function seedHandoffFile(dataDir: string, run: HandoffSeed): string;
|
|
21
|
+
/**
|
|
22
|
+
* Cez's own heartbeat (the janitor pattern): insert `- <ISO ts> — <note>`
|
|
23
|
+
* right under the `## Progress log` header (newest at the top), so the file
|
|
24
|
+
* stays current even when the agent forgets to write. Missing header →
|
|
25
|
+
* append at the end of the file; missing file → no-op.
|
|
26
|
+
*/
|
|
27
|
+
export declare function appendHandoffHeartbeat(dataDir: string, runId: string, note: string): void;
|
|
28
|
+
/** Full handoff markdown, or '' when the file doesn't exist (yet). */
|
|
29
|
+
export declare function readHandoff(dataDir: string, runId: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* First few non-empty lines under "## Progress log" (spec 010 — the variant
|
|
32
|
+
* comparison columns). Stops at the next `## ` header; '' when there's no
|
|
33
|
+
* Progress log section or it's empty.
|
|
34
|
+
*/
|
|
35
|
+
export declare function handoffProgressExcerpt(text: string, maxLines?: number): string;
|
|
36
|
+
export declare function deleteHandoff(dataDir: string, runId: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Is the global follow-up inbox on? (#471)
|
|
39
|
+
*
|
|
40
|
+
* Opt-in, off by default: agents kept hanging on stale, pre-saved follow-ups,
|
|
41
|
+
* which made skill behavior unpredictable. This is the single source of truth —
|
|
42
|
+
* `resolveCapabilities` reports it to the UI and `RunManager` enforces it on
|
|
43
|
+
* every run, so the HTTP route, the `cezar run` CLI and the inbox's own "▶ Run"
|
|
44
|
+
* cannot drift apart. The per-task handoff journal is a separate feature and is
|
|
45
|
+
* never gated by this.
|
|
46
|
+
*
|
|
47
|
+
* An exact `'1'` opts in — the house spelling (`AGENTS.md`: "opt-in behind a
|
|
48
|
+
* `CEZ_*` flag, off by default").
|
|
49
|
+
*/
|
|
50
|
+
export declare function followupsEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Appended to every agent step's `--append-system-prompt` (spec 007). The
|
|
53
|
+
* matching handoff/task env vars are set on every agent process;
|
|
54
|
+
* CEZ_TODOS_FILE carries a usable path only when follow-up generation is
|
|
55
|
+
* enabled (#444, #471) — opted-out runs get it empty, never absent, so an
|
|
56
|
+
* inherited value from a parent cezar cannot shine through
|
|
57
|
+
* (`RunManager.agentEnv`).
|
|
58
|
+
*/
|
|
59
|
+
export declare const HANDOFF_ONLY_INSTRUCTIONS = "## Handoff (cezar)\n\nCEZ_HANDOFF_FILE (env) is the absolute path to this task's rolling handoff file. Treat it like a HANDOFF.md:\n1. At the start of work, read it \u2014 \"Resume notes\" left by a previous session is your starting context.\n2. After every meaningful milestone (passing tests, a commit, a PR, a scope decision), append one terse timestamped line under \"## Progress log\", newest at the top.\n3. Before finishing or pausing, update \"## Resume notes\" with what's done, what's next and any blockers. Leave it empty only when the task is truly complete.\n\nTask completion marker: when the task's goal is fully achieved and you have no question for the user, end your final message with a line containing exactly CEZ:DONE \u2014 cez then closes the session and marks the task finished. If you are waiting on the user (a question, a decision, missing input), just end your message normally; the session stays open for their reply. Never emit CEZ:DONE while anything is unfinished or unverified.\n\nStill-working marker: if you end a turn while still working on your OWN downstream work \u2014 a sub-agent you dispatched, or a long-running command you're monitoring \u2014 and are NOT waiting on the user for anything, end your final message with a line containing exactly CEZ:MONITORING. cez then shows the task as \"monitoring\" (still working) instead of asking for your attention. Use CEZ:MONITORING only for that in-progress case; use CEZ:DONE when the goal is done; end plainly (no marker) only when you are genuinely waiting on the user. Never combine CEZ:MONITORING with CEZ:DONE.";
|
|
60
|
+
export declare const FOLLOWUP_INSTRUCTIONS = "## Follow-ups (cezar)\n\nCEZ_TODOS_FILE (env) is the absolute path to the user's follow-up inbox \u2014 a JSON array. When you finish the task and a concrete follow-up remains for the user or a next agent, read the file (treat a missing file as []), append ONE object and write the whole array back:\n{ \"ts\": \"<ISO 8601>\", \"taskId\": \"<value of CEZ_TASK_ID>\", \"summary\": \"<one sentence: what was done / what's next>\", \"action\": \"<imperative user action, optional>\", \"prUrl\": \"<optional>\", \"suggestedSkill\": \"<optional skill name for the follow-up>\", \"suggestedArgs\": \"<optional>\", \"suggestedPrompt\": \"<optional freeform prompt for the follow-up task>\", \"runnable\": <true when an agent can execute this follow-up, false when it is a note> }\nSet \"runnable\": false for anything a human must do or merely read \u2014 manual QA, \"remember to\u2026\", informational notes \u2014 and leave out suggestedSkill/suggestedPrompt for those; the inbox then offers \"Acknowledge\" instead of \"Run\". Set \"runnable\": true only when suggestedSkill or suggestedPrompt says what to actually execute.\nNever modify or remove existing entries \u2014 append only.";
|
|
61
|
+
/** Default-on combined contract retained for old callers and runs. */
|
|
62
|
+
export declare const HANDOFF_INSTRUCTIONS = "## Handoff (cezar)\n\nCEZ_HANDOFF_FILE (env) is the absolute path to this task's rolling handoff file. Treat it like a HANDOFF.md:\n1. At the start of work, read it \u2014 \"Resume notes\" left by a previous session is your starting context.\n2. After every meaningful milestone (passing tests, a commit, a PR, a scope decision), append one terse timestamped line under \"## Progress log\", newest at the top.\n3. Before finishing or pausing, update \"## Resume notes\" with what's done, what's next and any blockers. Leave it empty only when the task is truly complete.\n\nTask completion marker: when the task's goal is fully achieved and you have no question for the user, end your final message with a line containing exactly CEZ:DONE \u2014 cez then closes the session and marks the task finished. If you are waiting on the user (a question, a decision, missing input), just end your message normally; the session stays open for their reply. Never emit CEZ:DONE while anything is unfinished or unverified.\n\nStill-working marker: if you end a turn while still working on your OWN downstream work \u2014 a sub-agent you dispatched, or a long-running command you're monitoring \u2014 and are NOT waiting on the user for anything, end your final message with a line containing exactly CEZ:MONITORING. cez then shows the task as \"monitoring\" (still working) instead of asking for your attention. Use CEZ:MONITORING only for that in-progress case; use CEZ:DONE when the goal is done; end plainly (no marker) only when you are genuinely waiting on the user. Never combine CEZ:MONITORING with CEZ:DONE.\n\n## Follow-ups (cezar)\n\nCEZ_TODOS_FILE (env) is the absolute path to the user's follow-up inbox \u2014 a JSON array. When you finish the task and a concrete follow-up remains for the user or a next agent, read the file (treat a missing file as []), append ONE object and write the whole array back:\n{ \"ts\": \"<ISO 8601>\", \"taskId\": \"<value of CEZ_TASK_ID>\", \"summary\": \"<one sentence: what was done / what's next>\", \"action\": \"<imperative user action, optional>\", \"prUrl\": \"<optional>\", \"suggestedSkill\": \"<optional skill name for the follow-up>\", \"suggestedArgs\": \"<optional>\", \"suggestedPrompt\": \"<optional freeform prompt for the follow-up task>\", \"runnable\": <true when an agent can execute this follow-up, false when it is a note> }\nSet \"runnable\": false for anything a human must do or merely read \u2014 manual QA, \"remember to\u2026\", informational notes \u2014 and leave out suggestedSkill/suggestedPrompt for those; the inbox then offers \"Acknowledge\" instead of \"Run\". Set \"runnable\": true only when suggestedSkill or suggestedPrompt says what to actually execute.\nNever modify or remove existing entries \u2014 append only.";
|
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Per-task handoff journal (spec 007): `.ai/cezar/runs/<runId>.handoff.md`,
|
|
5
|
+
* next to the run's NDJSON events and outside the task worktree — it survives
|
|
6
|
+
* worktree removal. Cez seeds the skeleton and appends heartbeats; the agent
|
|
7
|
+
* (told via CEZ_HANDOFF_FILE + the system-prompt fragment below) keeps the
|
|
8
|
+
* "Progress log" and "Resume notes" sections up to date. Everything here is
|
|
9
|
+
* best-effort: the handoff is a journal, never a reason to fail a run.
|
|
10
|
+
*/
|
|
11
|
+
export function handoffPath(dataDir, runId) {
|
|
12
|
+
return join(dataDir, 'runs', `${runId}.handoff.md`);
|
|
13
|
+
}
|
|
14
|
+
/** Create the handoff skeleton. Idempotent — an existing file (resume,
|
|
15
|
+
* continuation) is never overwritten. Returns the file path. */
|
|
16
|
+
export function seedHandoffFile(dataDir, run) {
|
|
17
|
+
const file = handoffPath(dataDir, run.id);
|
|
18
|
+
if (existsSync(file))
|
|
19
|
+
return file;
|
|
20
|
+
const header = `# Handoff — ${run.title}\n\n` +
|
|
21
|
+
`**Task id:** ${run.id}\n` +
|
|
22
|
+
`**Workflow:** ${run.workflow}\n` +
|
|
23
|
+
(run.branch ? `**Branch:** ${run.branch}\n` : '') +
|
|
24
|
+
(run.worktreePath ? `**Worktree:** ${run.worktreePath}\n` : '') +
|
|
25
|
+
`\n## Goal\n\n${run.task.trim()}\n\n` +
|
|
26
|
+
`## Progress log\n\n` +
|
|
27
|
+
`## Resume notes\n`;
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
30
|
+
writeFileSync(file, header, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// best effort — a read-only data dir must not break the run
|
|
34
|
+
}
|
|
35
|
+
return file;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Cez's own heartbeat (the janitor pattern): insert `- <ISO ts> — <note>`
|
|
39
|
+
* right under the `## Progress log` header (newest at the top), so the file
|
|
40
|
+
* stays current even when the agent forgets to write. Missing header →
|
|
41
|
+
* append at the end of the file; missing file → no-op.
|
|
42
|
+
*/
|
|
43
|
+
export function appendHandoffHeartbeat(dataDir, runId, note) {
|
|
44
|
+
const file = handoffPath(dataDir, runId);
|
|
45
|
+
let text;
|
|
46
|
+
try {
|
|
47
|
+
text = readFileSync(file, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return; // not seeded — nothing to heartbeat
|
|
51
|
+
}
|
|
52
|
+
const line = `- ${new Date().toISOString()} — ${note}\n`;
|
|
53
|
+
const marker = '## Progress log\n';
|
|
54
|
+
const idx = text.indexOf(marker);
|
|
55
|
+
const next = idx >= 0
|
|
56
|
+
? `${text.slice(0, idx + marker.length)}\n${line}${text.slice(idx + marker.length).replace(/^\n+/, '')}`
|
|
57
|
+
: `${text}${text.endsWith('\n') || text === '' ? '' : '\n'}${line}`;
|
|
58
|
+
try {
|
|
59
|
+
writeFileSync(file, next, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// best effort
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Full handoff markdown, or '' when the file doesn't exist (yet). */
|
|
66
|
+
export function readHandoff(dataDir, runId) {
|
|
67
|
+
try {
|
|
68
|
+
return readFileSync(handoffPath(dataDir, runId), 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* First few non-empty lines under "## Progress log" (spec 010 — the variant
|
|
76
|
+
* comparison columns). Stops at the next `## ` header; '' when there's no
|
|
77
|
+
* Progress log section or it's empty.
|
|
78
|
+
*/
|
|
79
|
+
export function handoffProgressExcerpt(text, maxLines = 3) {
|
|
80
|
+
const marker = '## Progress log';
|
|
81
|
+
const idx = text.indexOf(marker);
|
|
82
|
+
if (idx < 0)
|
|
83
|
+
return '';
|
|
84
|
+
const lines = [];
|
|
85
|
+
for (const line of text.slice(idx + marker.length).split('\n')) {
|
|
86
|
+
if (line.startsWith('## '))
|
|
87
|
+
break;
|
|
88
|
+
const trimmed = line.trim();
|
|
89
|
+
if (!trimmed)
|
|
90
|
+
continue;
|
|
91
|
+
lines.push(trimmed);
|
|
92
|
+
if (lines.length >= maxLines)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
export function deleteHandoff(dataDir, runId) {
|
|
98
|
+
try {
|
|
99
|
+
rmSync(handoffPath(dataDir, runId), { force: true });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// best effort
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Is the global follow-up inbox on? (#471)
|
|
107
|
+
*
|
|
108
|
+
* Opt-in, off by default: agents kept hanging on stale, pre-saved follow-ups,
|
|
109
|
+
* which made skill behavior unpredictable. This is the single source of truth —
|
|
110
|
+
* `resolveCapabilities` reports it to the UI and `RunManager` enforces it on
|
|
111
|
+
* every run, so the HTTP route, the `cezar run` CLI and the inbox's own "▶ Run"
|
|
112
|
+
* cannot drift apart. The per-task handoff journal is a separate feature and is
|
|
113
|
+
* never gated by this.
|
|
114
|
+
*
|
|
115
|
+
* An exact `'1'` opts in — the house spelling (`AGENTS.md`: "opt-in behind a
|
|
116
|
+
* `CEZ_*` flag, off by default").
|
|
117
|
+
*/
|
|
118
|
+
export function followupsEnabled(env = process.env) {
|
|
119
|
+
return env.CEZ_FOLLOWUPS === '1';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Appended to every agent step's `--append-system-prompt` (spec 007). The
|
|
123
|
+
* matching handoff/task env vars are set on every agent process;
|
|
124
|
+
* CEZ_TODOS_FILE carries a usable path only when follow-up generation is
|
|
125
|
+
* enabled (#444, #471) — opted-out runs get it empty, never absent, so an
|
|
126
|
+
* inherited value from a parent cezar cannot shine through
|
|
127
|
+
* (`RunManager.agentEnv`).
|
|
128
|
+
*/
|
|
129
|
+
export const HANDOFF_ONLY_INSTRUCTIONS = `## Handoff (cezar)
|
|
130
|
+
|
|
131
|
+
CEZ_HANDOFF_FILE (env) is the absolute path to this task's rolling handoff file. Treat it like a HANDOFF.md:
|
|
132
|
+
1. At the start of work, read it — "Resume notes" left by a previous session is your starting context.
|
|
133
|
+
2. After every meaningful milestone (passing tests, a commit, a PR, a scope decision), append one terse timestamped line under "## Progress log", newest at the top.
|
|
134
|
+
3. Before finishing or pausing, update "## Resume notes" with what's done, what's next and any blockers. Leave it empty only when the task is truly complete.
|
|
135
|
+
|
|
136
|
+
Task completion marker: when the task's goal is fully achieved and you have no question for the user, end your final message with a line containing exactly CEZ:DONE — cez then closes the session and marks the task finished. If you are waiting on the user (a question, a decision, missing input), just end your message normally; the session stays open for their reply. Never emit CEZ:DONE while anything is unfinished or unverified.
|
|
137
|
+
|
|
138
|
+
Still-working marker: if you end a turn while still working on your OWN downstream work — a sub-agent you dispatched, or a long-running command you're monitoring — and are NOT waiting on the user for anything, end your final message with a line containing exactly CEZ:MONITORING. cez then shows the task as "monitoring" (still working) instead of asking for your attention. Use CEZ:MONITORING only for that in-progress case; use CEZ:DONE when the goal is done; end plainly (no marker) only when you are genuinely waiting on the user. Never combine CEZ:MONITORING with CEZ:DONE.`;
|
|
139
|
+
export const FOLLOWUP_INSTRUCTIONS = `## Follow-ups (cezar)
|
|
140
|
+
|
|
141
|
+
CEZ_TODOS_FILE (env) is the absolute path to the user's follow-up inbox — a JSON array. When you finish the task and a concrete follow-up remains for the user or a next agent, read the file (treat a missing file as []), append ONE object and write the whole array back:
|
|
142
|
+
{ "ts": "<ISO 8601>", "taskId": "<value of CEZ_TASK_ID>", "summary": "<one sentence: what was done / what's next>", "action": "<imperative user action, optional>", "prUrl": "<optional>", "suggestedSkill": "<optional skill name for the follow-up>", "suggestedArgs": "<optional>", "suggestedPrompt": "<optional freeform prompt for the follow-up task>", "runnable": <true when an agent can execute this follow-up, false when it is a note> }
|
|
143
|
+
Set "runnable": false for anything a human must do or merely read — manual QA, "remember to…", informational notes — and leave out suggestedSkill/suggestedPrompt for those; the inbox then offers "Acknowledge" instead of "Run". Set "runnable": true only when suggestedSkill or suggestedPrompt says what to actually execute.
|
|
144
|
+
Never modify or remove existing entries — append only.`;
|
|
145
|
+
/** Default-on combined contract retained for old callers and runs. */
|
|
146
|
+
export const HANDOFF_INSTRUCTIONS = `${HANDOFF_ONLY_INSTRUCTIONS}\n\n${FOLLOWUP_INSTRUCTIONS}`;
|
|
147
|
+
//# sourceMappingURL=handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.js","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;;;GAOG;AAEH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAa;IACxD,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,aAAa,CAAC,CAAC;AACtD,CAAC;AAWD;iEACiE;AACjE,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,GAAgB;IAC/D,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1C,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,MAAM,GACV,eAAe,GAAG,CAAC,KAAK,MAAM;QAC9B,gBAAgB,GAAG,CAAC,EAAE,IAAI;QAC1B,iBAAiB,GAAG,CAAC,QAAQ,IAAI;QACjC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,iBAAiB,GAAG,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,gBAAgB,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM;QACrC,qBAAqB;QACrB,mBAAmB,CAAC;IACtB,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,KAAa,EAAE,IAAY;IACjF,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACzC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,oCAAoC;IAC9C,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,IAAI,IAAI,CAAC;IACzD,MAAM,MAAM,GAAG,mBAAmB,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,IAAI,GACR,GAAG,IAAI,CAAC;QACN,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE;QACxG,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;IACxE,IAAI,CAAC;QACH,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAa;IACxD,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY,EAAE,QAAQ,GAAG,CAAC;IAC/D,MAAM,MAAM,GAAG,iBAAiB,CAAC;IACjC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,MAAM;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;YAAE,MAAM;IACtC,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,KAAa;IAC1D,IAAI,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,OAAO,GAAG,CAAC,aAAa,KAAK,GAAG,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;kkBASyhB,CAAC;AAEnkB,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;uDAKkB,CAAC;AAExD,sEAAsE;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,yBAAyB,OAAO,qBAAqB,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED