@rallycry/conveyor-agent 10.2.2 → 10.2.5
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/chunk-7TQO4ZF4.js +60 -0
- package/dist/chunk-7TQO4ZF4.js.map +1 -0
- package/dist/{chunk-53ZLFIAI.js → chunk-CFELRV35.js} +1405 -1233
- package/dist/chunk-CFELRV35.js.map +1 -0
- package/dist/cli.js +209 -10
- package/dist/cli.js.map +1 -1
- package/dist/heartbeat-worker.d.ts +2 -0
- package/dist/heartbeat-worker.js +47 -0
- package/dist/heartbeat-worker.js.map +1 -0
- package/dist/index.d.ts +50 -42
- package/dist/index.js +19 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/entrypoint.sh +93 -4
- package/dist/chunk-53ZLFIAI.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _project_shared from '@project/shared';
|
|
2
|
-
import { RunnerMode, AgentSessionServiceMethods, AgentMode, WorkspaceDiscoveredPort, AgentQuestion, AgentRunnerStatus, BootstrapBundle } from '@project/shared';
|
|
2
|
+
import { RunnerMode, AgentSessionServiceMethods, AgentMode, PtyChatEventPayload, WorkspaceDiscoveredPort, AgentQuestion, AgentRunnerStatus, BootstrapBundle } from '@project/shared';
|
|
3
3
|
export * from '@project/shared';
|
|
4
4
|
import { ChildProcess } from 'node:child_process';
|
|
5
5
|
|
|
@@ -143,6 +143,12 @@ declare class AgentConnection {
|
|
|
143
143
|
cols: number;
|
|
144
144
|
rows: number;
|
|
145
145
|
}): void;
|
|
146
|
+
/**
|
|
147
|
+
* Forward one compact chat-proxy event derived from the transcript JSONL to
|
|
148
|
+
* the relay (fire-and-forget). Feeds the experimental chat PTY proxy ring.
|
|
149
|
+
* Old servers that don't know the method reject harmlessly.
|
|
150
|
+
*/
|
|
151
|
+
sendPtyChatEvent(event: PtyChatEventPayload): void;
|
|
146
152
|
/**
|
|
147
153
|
* Report that the interactive CLI process for this session has died and no
|
|
148
154
|
* respawn is imminent (fire-and-forget). The server clears the scrollback
|
|
@@ -164,7 +170,10 @@ declare class AgentConnection {
|
|
|
164
170
|
duplicate: true;
|
|
165
171
|
matchedMessagePreview: string;
|
|
166
172
|
};
|
|
167
|
-
sendHeartbeat(): void;
|
|
173
|
+
sendHeartbeat(loopLagMs?: number): void;
|
|
174
|
+
private heartbeatWorker;
|
|
175
|
+
startHeartbeatWorker(sharedBuffer: SharedArrayBuffer, intervalMs?: number): void;
|
|
176
|
+
stopHeartbeatWorker(): void;
|
|
168
177
|
emitModeChanged(agentMode?: AgentMode | null): void;
|
|
169
178
|
updateTaskFields(fields: {
|
|
170
179
|
plan?: string;
|
|
@@ -409,8 +418,14 @@ declare class SessionRunner {
|
|
|
409
418
|
private agentSpokeThisTurn;
|
|
410
419
|
/** Guards overlapping runs of the periodic git flush. */
|
|
411
420
|
private periodicFlushInFlight;
|
|
421
|
+
/** Consecutive flush ticks skipped because a heavy gate was running. */
|
|
422
|
+
private gateSkipCount;
|
|
423
|
+
/** Max consecutive gate-deferred ticks (~2 min each) before flushing anyway. */
|
|
424
|
+
private static readonly MAX_GATE_SKIPS;
|
|
412
425
|
/** Runtime preview-port poller (v3 dynamic port discovery). */
|
|
413
426
|
private portDiscovery;
|
|
427
|
+
/** Main event-loop lag measurement, shared with the heartbeat worker. */
|
|
428
|
+
private readonly loopLag;
|
|
414
429
|
constructor(config: SessionRunnerConfig, callbacks: SessionRunnerCallbacks);
|
|
415
430
|
get state(): AgentRunnerStatus;
|
|
416
431
|
get sessionId(): string;
|
|
@@ -508,16 +523,27 @@ declare class SessionRunner {
|
|
|
508
523
|
stop(): void;
|
|
509
524
|
softStop(): void;
|
|
510
525
|
private static readonly MAX_STUCK_NUDGES;
|
|
511
|
-
/** The
|
|
512
|
-
*
|
|
526
|
+
/** The build-phase nudge (In Progress, no PR). Offers BOTH escape routes so
|
|
527
|
+
* the agent never just goes idle. */
|
|
513
528
|
private static readonly STUCK_PROMPT;
|
|
529
|
+
/** The planning-phase nudge (still Planning, identification/plan unfinished).
|
|
530
|
+
* Same two-escape-route shape, aimed at finishing discovery instead of a PR. */
|
|
531
|
+
private static readonly STUCK_PROMPT_PLANNING;
|
|
514
532
|
/**
|
|
515
|
-
*
|
|
516
|
-
* post-turn, so the TUI is no longer
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
533
|
+
* Classify how an auto task is "stuck" after its turn ended (call sites run
|
|
534
|
+
* post-turn, so the TUI is no longer working), or null if it isn't. Shared
|
|
535
|
+
* guards: only auto, not rate-limited, under the nudge cap, and the agent
|
|
536
|
+
* stayed silent this turn — if it posted to chat (explained itself / asked for
|
|
537
|
+
* help) it's waiting on a human, not silently stuck.
|
|
538
|
+
*
|
|
539
|
+
* - "pr": In Progress with no open PR (build finished without shipping).
|
|
540
|
+
* - "planning": still Planning without (identified + saved plan). A fresh auto
|
|
541
|
+
* agent that finishes its plan turn WITHOUT ExitPlanMode would otherwise idle
|
|
542
|
+
* → clean-exit → its session Ends → the workspace is reaped "agent_gone"
|
|
543
|
+
* before a plan ever landed. Nudging (and, on exhaustion, going dormant)
|
|
544
|
+
* keeps the session Active so the pod is never prematurely slept.
|
|
520
545
|
*/
|
|
546
|
+
private autoStuckKind;
|
|
521
547
|
private isAutoStuck;
|
|
522
548
|
/**
|
|
523
549
|
* Stop driving the agent and route the core loop into dormant idle —
|
|
@@ -528,15 +554,18 @@ declare class SessionRunner {
|
|
|
528
554
|
*/
|
|
529
555
|
private enterDormantForHumanTakeover;
|
|
530
556
|
private refreshTaskContext;
|
|
557
|
+
/** Prompt + chat-message label for a stuck-nudge of the given kind. */
|
|
558
|
+
private stuckNudgeContent;
|
|
531
559
|
private maybeHandleStuckAuto;
|
|
532
560
|
private buildFullContext;
|
|
533
561
|
private createQueryBridge;
|
|
534
562
|
private wireConnectionCallbacks;
|
|
535
563
|
/** Eager finalize snapshot triggered by the reconciler's sleep signal
|
|
536
|
-
* (session:finalizeSnapshot). Runs
|
|
564
|
+
* (session:finalizeSnapshot). Runs a full workspace capture and uploads it
|
|
537
565
|
* NOW so the sleep confirms on this snapshot instead of waiting for the
|
|
538
|
-
* ~2min periodic flush
|
|
539
|
-
*
|
|
566
|
+
* ~2min periodic flush. Sidecar DB state is intentionally NOT captured — it
|
|
567
|
+
* is no longer durable across sleep/wake (#2623); only the agent's
|
|
568
|
+
* /workspace rides the snapshot. Best-effort: on failure the
|
|
540
569
|
* periodic/shutdown path stays the fallback. No-op on non-v3 pods. */
|
|
541
570
|
private finalizeSnapshotNow;
|
|
542
571
|
/** Proactively refresh the GitHub token before the 1-hour expiry. */
|
|
@@ -569,29 +598,16 @@ interface AgentRunnerCallbacks {
|
|
|
569
598
|
onStatusChange: (status: string) => void | Promise<void>;
|
|
570
599
|
}
|
|
571
600
|
|
|
572
|
-
declare class PlanSync {
|
|
573
|
-
private planFileSnapshot;
|
|
574
|
-
private lockedPlanFile;
|
|
575
|
-
private workspaceDir;
|
|
576
|
-
private connection;
|
|
577
|
-
constructor(workspaceDir: string, connection: AgentConnection);
|
|
578
|
-
updateWorkspaceDir(workspaceDir: string): void;
|
|
579
|
-
private getPlanDirs;
|
|
580
|
-
snapshotPlanFiles(): void;
|
|
581
|
-
private findNewestPlanFile;
|
|
582
|
-
syncPlanFile(): Promise<void>;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
601
|
/** Returns true if the working tree has uncommitted changes (staged or unstaged). */
|
|
586
|
-
declare function hasUncommittedChanges(cwd: string): boolean
|
|
602
|
+
declare function hasUncommittedChanges(cwd: string): Promise<boolean>;
|
|
587
603
|
/** Returns the current branch name, or null if on detached HEAD. */
|
|
588
|
-
declare function getCurrentBranch(cwd: string): string | null
|
|
604
|
+
declare function getCurrentBranch(cwd: string): Promise<string | null>;
|
|
589
605
|
/** Returns true if there are committed changes that haven't been pushed to origin. */
|
|
590
|
-
declare function hasUnpushedCommits(cwd: string): boolean
|
|
606
|
+
declare function hasUnpushedCommits(cwd: string): Promise<boolean>;
|
|
591
607
|
/** Stage all changes and create a commit. Returns the commit hash if successful. */
|
|
592
|
-
declare function stageAndCommit(cwd: string, message: string): string | null
|
|
608
|
+
declare function stageAndCommit(cwd: string, message: string): Promise<string | null>;
|
|
593
609
|
/** Update the git remote URL with a fresh token. */
|
|
594
|
-
declare function updateRemoteToken(cwd: string, token: string): void
|
|
610
|
+
declare function updateRemoteToken(cwd: string, token: string): Promise<void>;
|
|
595
611
|
/** Best-effort flush of pending work: pushes any real (intentional) commits to
|
|
596
612
|
* the task branch, and captures uncommitted changes as a WIP snapshot on the
|
|
597
613
|
* `conveyor-wip/<branch>` ref — never as commits on the branch itself. When
|
|
@@ -608,18 +624,18 @@ declare function flushPendingChanges(cwd: string, opts?: {
|
|
|
608
624
|
* attempting the push. On auth failure, refreshes again and retries. */
|
|
609
625
|
declare function pushToOrigin(cwd: string, refreshToken?: () => Promise<string | undefined>, skipVerify?: boolean): Promise<boolean>;
|
|
610
626
|
|
|
611
|
-
declare function ensureWorktree(projectDir: string, taskId: string, branch?: string): string
|
|
627
|
+
declare function ensureWorktree(projectDir: string, taskId: string, branch?: string): Promise<string>;
|
|
612
628
|
/**
|
|
613
629
|
* Detach any task worktree that has `branch` checked out, releasing the branch lock
|
|
614
630
|
* so the main project directory can check it out. Only touches worktrees inside
|
|
615
631
|
* `.worktrees/` — never the main project directory.
|
|
616
632
|
*/
|
|
617
|
-
declare function detachWorktreeBranch(projectDir: string, branch: string): void
|
|
633
|
+
declare function detachWorktreeBranch(projectDir: string, branch: string): Promise<void>;
|
|
618
634
|
/**
|
|
619
635
|
* Force-remove is intentional: this runs after task completion or explicit stop.
|
|
620
636
|
* Any uncommitted changes at this point are scratch artifacts, not valuable work.
|
|
621
637
|
*/
|
|
622
|
-
declare function removeWorktree(projectDir: string, taskId: string): void
|
|
638
|
+
declare function removeWorktree(projectDir: string, taskId: string): Promise<void>;
|
|
623
639
|
|
|
624
640
|
interface CaptureContext {
|
|
625
641
|
cwd: string;
|
|
@@ -627,9 +643,6 @@ interface CaptureContext {
|
|
|
627
643
|
snapshotUploadUrl?: string;
|
|
628
644
|
refreshToken?: () => Promise<string | undefined>;
|
|
629
645
|
wipMessage?: string;
|
|
630
|
-
/** Path to a pre-built postgres dump OUTSIDE cwd (see pg-dump.ts) that the
|
|
631
|
-
* GCS tar should carry under its fixed in-archive name. */
|
|
632
|
-
pgDumpPath?: string;
|
|
633
646
|
/** Force the GCS PUT even when the tar is empty (sizeBytes === 0). Used by the
|
|
634
647
|
* sleep FINALIZE path so `snapshotAt` is stamped and the reconciler's
|
|
635
648
|
* teardown confirm lands immediately — an empty workspace is a valid (empty)
|
|
@@ -644,8 +657,6 @@ interface SnapshotArtifact {
|
|
|
644
657
|
sizeBytes: number;
|
|
645
658
|
gcsUploaded: boolean;
|
|
646
659
|
wipPushed: boolean;
|
|
647
|
-
/** Whether this capture bundled a postgres dump into the GCS artifact. */
|
|
648
|
-
pgDumped: boolean;
|
|
649
660
|
}
|
|
650
661
|
interface RestoreResult {
|
|
651
662
|
source: "gcs" | "wip" | "clean";
|
|
@@ -656,7 +667,6 @@ interface GcsUploadResult {
|
|
|
656
667
|
fileCount: number;
|
|
657
668
|
deletionCount: number;
|
|
658
669
|
sizeBytes: number;
|
|
659
|
-
pgDumped: boolean;
|
|
660
670
|
}
|
|
661
671
|
/**
|
|
662
672
|
* The GCS sink on its own: assemble the snapshot tar (the single capture
|
|
@@ -667,7 +677,6 @@ interface GcsUploadResult {
|
|
|
667
677
|
* after any earlier in-flight one has settled.
|
|
668
678
|
*/
|
|
669
679
|
declare function uploadSnapshotToGcs(cwd: string, uploadUrl: string | undefined, opts?: {
|
|
670
|
-
pgDumpPath?: string;
|
|
671
680
|
forceUpload?: boolean;
|
|
672
681
|
}): Promise<GcsUploadResult>;
|
|
673
682
|
declare function captureSnapshot(ctx: CaptureContext): Promise<SnapshotArtifact>;
|
|
@@ -692,7 +701,6 @@ declare function restoreOnBoot(bundle: Pick<BootstrapBundle, "snapshotUrl"> & {
|
|
|
692
701
|
interface ConveyorConfig {
|
|
693
702
|
setupCommand?: string;
|
|
694
703
|
startCommand?: string;
|
|
695
|
-
previewPort?: number;
|
|
696
704
|
}
|
|
697
705
|
interface ForwardPortsResult {
|
|
698
706
|
ports: number[];
|
|
@@ -712,4 +720,4 @@ declare function runStartCommand(cmd: string, cwd: string, onOutput: (stream: "s
|
|
|
712
720
|
/** Fetch full git history if the repo was cloned with --depth=1. */
|
|
713
721
|
declare function unshallowRepo(workspaceDir: string): void;
|
|
714
722
|
|
|
715
|
-
export { AgentConnection, type AgentConnectionConfig, type AgentRunnerCallbacks, type AgentRunnerConfig, type ApiKeyUpdateData, type CaptureContext, type ConveyorConfig, type GcsUploadResult, type IncomingMessage, type LifecycleCallbacks, type LifecycleConfig, type ModeAction, type ModeTaskContext,
|
|
723
|
+
export { AgentConnection, type AgentConnectionConfig, type AgentRunnerCallbacks, type AgentRunnerConfig, type ApiKeyUpdateData, type CaptureContext, type ConveyorConfig, type GcsUploadResult, type IncomingMessage, type LifecycleCallbacks, type LifecycleConfig, type ModeAction, type ModeTaskContext, type RestoreResult, SessionRunner, type SessionRunnerCallbacks, type SessionRunnerConfig, type SetModeData, type SnapshotArtifact, captureSnapshot, detachWorktreeBranch, ensureWorktree, finalizeForSleep, flushPendingChanges, getCurrentBranch, hasUncommittedChanges, hasUnpushedCommits, loadConveyorConfig, loadForwardPorts, pushToOrigin, removeWorktree, restoreOnBoot, runAuthTokenCommand, runSetupCommand, runStartCommand, stageAndCommit, startPeriodic as startWorkPreservation, stop as stopWorkPreservation, unshallowRepo, updateRemoteToken, uploadSnapshotToGcs };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentConnection,
|
|
3
|
-
PlanSync,
|
|
4
3
|
SessionRunner,
|
|
5
4
|
captureSnapshot,
|
|
6
5
|
finalizeForSleep,
|
|
@@ -21,46 +20,45 @@ import {
|
|
|
21
20
|
unshallowRepo,
|
|
22
21
|
updateRemoteToken,
|
|
23
22
|
uploadSnapshotToGcs
|
|
24
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-CFELRV35.js";
|
|
24
|
+
import "./chunk-7TQO4ZF4.js";
|
|
25
25
|
|
|
26
26
|
// src/runner/worktree.ts
|
|
27
|
-
import {
|
|
27
|
+
import { execFile } from "child_process";
|
|
28
28
|
import { existsSync } from "fs";
|
|
29
29
|
import { join } from "path";
|
|
30
|
+
import { promisify } from "util";
|
|
31
|
+
var execFileAsync = promisify(execFile);
|
|
32
|
+
var GIT_TIMEOUT_MS = 6e4;
|
|
33
|
+
async function git(cwd, args) {
|
|
34
|
+
const { stdout } = await execFileAsync("git", args, { cwd, timeout: GIT_TIMEOUT_MS });
|
|
35
|
+
return stdout.toString();
|
|
36
|
+
}
|
|
30
37
|
var WORKTREE_DIR = ".worktrees";
|
|
31
|
-
function ensureWorktree(projectDir, taskId, branch) {
|
|
38
|
+
async function ensureWorktree(projectDir, taskId, branch) {
|
|
32
39
|
if (projectDir.includes(`/${WORKTREE_DIR}/`)) {
|
|
33
40
|
return projectDir;
|
|
34
41
|
}
|
|
35
42
|
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
36
43
|
if (existsSync(worktreePath)) {
|
|
37
44
|
if (branch) {
|
|
38
|
-
if (hasUncommittedChanges(worktreePath)) {
|
|
45
|
+
if (await hasUncommittedChanges(worktreePath)) {
|
|
39
46
|
return worktreePath;
|
|
40
47
|
}
|
|
41
48
|
try {
|
|
42
|
-
|
|
43
|
-
cwd: worktreePath,
|
|
44
|
-
stdio: "ignore"
|
|
45
|
-
});
|
|
49
|
+
await git(worktreePath, ["checkout", "--detach", `origin/${branch}`]);
|
|
46
50
|
} catch {
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
return worktreePath;
|
|
50
54
|
}
|
|
51
55
|
const ref = branch ? `origin/${branch}` : "HEAD";
|
|
52
|
-
|
|
53
|
-
cwd: projectDir,
|
|
54
|
-
stdio: "ignore"
|
|
55
|
-
});
|
|
56
|
+
await git(projectDir, ["worktree", "add", "--detach", worktreePath, ref]);
|
|
56
57
|
return worktreePath;
|
|
57
58
|
}
|
|
58
|
-
function detachWorktreeBranch(projectDir, branch) {
|
|
59
|
+
async function detachWorktreeBranch(projectDir, branch) {
|
|
59
60
|
try {
|
|
60
|
-
const output =
|
|
61
|
-
cwd: projectDir,
|
|
62
|
-
encoding: "utf-8"
|
|
63
|
-
});
|
|
61
|
+
const output = await git(projectDir, ["worktree", "list", "--porcelain"]);
|
|
64
62
|
const entries = output.split("\n\n");
|
|
65
63
|
for (const entry of entries) {
|
|
66
64
|
const lines = entry.trim().split("\n");
|
|
@@ -70,27 +68,23 @@ function detachWorktreeBranch(projectDir, branch) {
|
|
|
70
68
|
const worktreePath = worktreeLine.replace("worktree ", "");
|
|
71
69
|
if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;
|
|
72
70
|
try {
|
|
73
|
-
|
|
71
|
+
await git(worktreePath, ["checkout", "--detach"]);
|
|
74
72
|
} catch {
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
} catch {
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
|
-
function removeWorktree(projectDir, taskId) {
|
|
78
|
+
async function removeWorktree(projectDir, taskId) {
|
|
81
79
|
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
82
80
|
if (!existsSync(worktreePath)) return;
|
|
83
81
|
try {
|
|
84
|
-
|
|
85
|
-
cwd: projectDir,
|
|
86
|
-
stdio: "ignore"
|
|
87
|
-
});
|
|
82
|
+
await git(projectDir, ["worktree", "remove", worktreePath, "--force"]);
|
|
88
83
|
} catch {
|
|
89
84
|
}
|
|
90
85
|
}
|
|
91
86
|
export {
|
|
92
87
|
AgentConnection,
|
|
93
|
-
PlanSync,
|
|
94
88
|
SessionRunner,
|
|
95
89
|
captureSnapshot,
|
|
96
90
|
detachWorktreeBranch,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runner/worktree.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/runner/worktree.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { hasUncommittedChanges } from \"./git-utils.js\";\n\n// Async git (execFile, no shell) — synchronous child processes freeze the\n// agent's event loop and starve the socket heartbeat; see git-utils.ts.\nconst execFileAsync = promisify(execFile);\nconst GIT_TIMEOUT_MS = 60_000;\n\nasync function git(cwd: string, args: string[]): Promise<string> {\n const { stdout } = await execFileAsync(\"git\", args, { cwd, timeout: GIT_TIMEOUT_MS });\n return stdout.toString();\n}\n\nconst WORKTREE_DIR = \".worktrees\";\n\nexport async function ensureWorktree(\n projectDir: string,\n taskId: string,\n branch?: string,\n): Promise<string> {\n if (projectDir.includes(`/${WORKTREE_DIR}/`)) {\n return projectDir;\n }\n\n const worktreePath = join(projectDir, WORKTREE_DIR, taskId);\n\n if (existsSync(worktreePath)) {\n if (branch) {\n if (await hasUncommittedChanges(worktreePath)) {\n return worktreePath;\n }\n try {\n await git(worktreePath, [\"checkout\", \"--detach\", `origin/${branch}`]);\n } catch {\n /* branch doesn't exist on remote yet */\n }\n }\n return worktreePath;\n }\n\n const ref = branch ? `origin/${branch}` : \"HEAD\";\n await git(projectDir, [\"worktree\", \"add\", \"--detach\", worktreePath, ref]);\n\n return worktreePath;\n}\n\n/**\n * Detach any task worktree that has `branch` checked out, releasing the branch lock\n * so the main project directory can check it out. Only touches worktrees inside\n * `.worktrees/` — never the main project directory.\n */\nexport async function detachWorktreeBranch(projectDir: string, branch: string): Promise<void> {\n try {\n const output = await git(projectDir, [\"worktree\", \"list\", \"--porcelain\"]);\n const entries = output.split(\"\\n\\n\");\n for (const entry of entries) {\n const lines = entry.trim().split(\"\\n\");\n const worktreeLine = lines.find((l) => l.startsWith(\"worktree \"));\n const branchLine = lines.find((l) => l.startsWith(\"branch \"));\n if (!worktreeLine || branchLine !== `branch refs/heads/${branch}`) continue;\n\n const worktreePath = worktreeLine.replace(\"worktree \", \"\");\n if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;\n\n try {\n await git(worktreePath, [\"checkout\", \"--detach\"]);\n } catch {\n /* best effort */\n }\n }\n } catch {\n /* best effort */\n }\n}\n\n/**\n * Force-remove is intentional: this runs after task completion or explicit stop.\n * Any uncommitted changes at this point are scratch artifacts, not valuable work.\n */\nexport async function removeWorktree(projectDir: string, taskId: string): Promise<void> {\n const worktreePath = join(projectDir, WORKTREE_DIR, taskId);\n if (!existsSync(worktreePath)) return;\n try {\n await git(projectDir, [\"worktree\", \"remove\", worktreePath, \"--force\"]);\n } catch {\n /* best effort */\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAK1B,IAAM,gBAAgB,UAAU,QAAQ;AACxC,IAAM,iBAAiB;AAEvB,eAAe,IAAI,KAAa,MAAiC;AAC/D,QAAM,EAAE,OAAO,IAAI,MAAM,cAAc,OAAO,MAAM,EAAE,KAAK,SAAS,eAAe,CAAC;AACpF,SAAO,OAAO,SAAS;AACzB;AAEA,IAAM,eAAe;AAErB,eAAsB,eACpB,YACA,QACA,QACiB;AACjB,MAAI,WAAW,SAAS,IAAI,YAAY,GAAG,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,KAAK,YAAY,cAAc,MAAM;AAE1D,MAAI,WAAW,YAAY,GAAG;AAC5B,QAAI,QAAQ;AACV,UAAI,MAAM,sBAAsB,YAAY,GAAG;AAC7C,eAAO;AAAA,MACT;AACA,UAAI;AACF,cAAM,IAAI,cAAc,CAAC,YAAY,YAAY,UAAU,MAAM,EAAE,CAAC;AAAA,MACtE,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,SAAS,UAAU,MAAM,KAAK;AAC1C,QAAM,IAAI,YAAY,CAAC,YAAY,OAAO,YAAY,cAAc,GAAG,CAAC;AAExE,SAAO;AACT;AAOA,eAAsB,qBAAqB,YAAoB,QAA+B;AAC5F,MAAI;AACF,UAAM,SAAS,MAAM,IAAI,YAAY,CAAC,YAAY,QAAQ,aAAa,CAAC;AACxE,UAAM,UAAU,OAAO,MAAM,MAAM;AACnC,eAAW,SAAS,SAAS;AAC3B,YAAM,QAAQ,MAAM,KAAK,EAAE,MAAM,IAAI;AACrC,YAAM,eAAe,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,WAAW,CAAC;AAChE,YAAM,aAAa,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,CAAC;AAC5D,UAAI,CAAC,gBAAgB,eAAe,qBAAqB,MAAM,GAAI;AAEnE,YAAM,eAAe,aAAa,QAAQ,aAAa,EAAE;AACzD,UAAI,CAAC,aAAa,SAAS,IAAI,YAAY,GAAG,EAAG;AAEjD,UAAI;AACF,cAAM,IAAI,cAAc,CAAC,YAAY,UAAU,CAAC;AAAA,MAClD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAMA,eAAsB,eAAe,YAAoB,QAA+B;AACtF,QAAM,eAAe,KAAK,YAAY,cAAc,MAAM;AAC1D,MAAI,CAAC,WAAW,YAAY,EAAG;AAC/B,MAAI;AACF,UAAM,IAAI,YAAY,CAAC,YAAY,UAAU,cAAc,SAAS,CAAC;AAAA,EACvE,QAAQ;AAAA,EAER;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "10.2.
|
|
3
|
+
"version": "10.2.5",
|
|
4
4
|
"description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
package/runtime/entrypoint.sh
CHANGED
|
@@ -324,6 +324,13 @@ if [ -n "${CONVEYOR_USER_ID}" ] && [ -n "${USER_HOME_PROJECT_ID}" ] && [ -d "${U
|
|
|
324
324
|
ln -sfn "${USER_HOME_ROOT}/.claude" /home/conveyor/.claude
|
|
325
325
|
ln -sfn "${USER_HOME_ROOT}/.claude.json" /home/conveyor/.claude.json
|
|
326
326
|
ln -sfn "${USER_HOME_ROOT}/.config/claude" /home/conveyor/.config/claude
|
|
327
|
+
|
|
328
|
+
# OpenCode state (sessions + config) persists the same way as ~/.claude.
|
|
329
|
+
mkdir -p "${USER_HOME_ROOT}/.local/share/opencode" "${USER_HOME_ROOT}/.config/opencode" 2>/dev/null || true
|
|
330
|
+
rm -rf /home/conveyor/.local/share/opencode /home/conveyor/.config/opencode 2>/dev/null || true
|
|
331
|
+
mkdir -p /home/conveyor/.local/share 2>/dev/null || true
|
|
332
|
+
ln -sfn "${USER_HOME_ROOT}/.local/share/opencode" /home/conveyor/.local/share/opencode
|
|
333
|
+
ln -sfn "${USER_HOME_ROOT}/.config/opencode" /home/conveyor/.config/opencode
|
|
327
334
|
else
|
|
328
335
|
echo "[pool] Skipping user-home symlink (userId='${CONVEYOR_USER_ID}', projectId='${USER_HOME_PROJECT_ID}', mount present: $([ -d "${USER_HOME_MOUNT}" ] && echo yes || echo no))"
|
|
329
336
|
# No persistent home — the CLI reads the pod-local config; seed it there so
|
|
@@ -331,6 +338,88 @@ else
|
|
|
331
338
|
seed_claude_json /home/conveyor/.claude.json
|
|
332
339
|
fi
|
|
333
340
|
|
|
341
|
+
# ── Wire published graphify bundles from the shared user-home mount ──
|
|
342
|
+
# A locally published graph lives in the same GCS-FUSE user-home bucket as
|
|
343
|
+
# Claude state, under users/_shared/graphify/<repo>/latest. Export the path
|
|
344
|
+
# contract before the agent starts, then bind the files into graphify-out after
|
|
345
|
+
# git has prepared the workspace but before the ready marker is released.
|
|
346
|
+
configure_graphify_env() {
|
|
347
|
+
if [ "${CONVEYOR_GRAPHIFY_DISABLE:-}" = "1" ]; then
|
|
348
|
+
return 0
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
local slug="${CONVEYOR_GRAPHIFY_SLUG:-${REPO_NAME:-}}"
|
|
352
|
+
if [ -z "${slug}" ]; then
|
|
353
|
+
return 0
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
local primary_root="${USER_HOME_MOUNT}/users/_shared/graphify"
|
|
357
|
+
local legacy_root="${USER_HOME_MOUNT}/_shared/graphify"
|
|
358
|
+
local shared_root="${CONVEYOR_GRAPHIFY_SHARED_ROOT:-}"
|
|
359
|
+
if [ -z "${shared_root}" ]; then
|
|
360
|
+
if [ -d "${primary_root}" ] || [ ! -d "${legacy_root}" ]; then
|
|
361
|
+
shared_root="${primary_root}"
|
|
362
|
+
else
|
|
363
|
+
shared_root="${legacy_root}"
|
|
364
|
+
fi
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
export CONVEYOR_GRAPHIFY_SLUG="${slug}"
|
|
368
|
+
export CONVEYOR_GRAPHIFY_SHARED_ROOT="${shared_root}"
|
|
369
|
+
export CONVEYOR_GRAPHIFY_DIR="${CONVEYOR_GRAPHIFY_DIR:-${shared_root}/${slug}/latest}"
|
|
370
|
+
export CONVEYOR_GRAPHIFY_GRAPH="${CONVEYOR_GRAPHIFY_GRAPH:-${CONVEYOR_GRAPHIFY_DIR}/graph.json}"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
bind_graphify_bundle() {
|
|
374
|
+
if [ "${CONVEYOR_GRAPHIFY_DISABLE:-}" = "1" ]; then
|
|
375
|
+
echo "[pool] Graphify bind disabled."
|
|
376
|
+
return 0
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
if [ -z "${CONVEYOR_GRAPHIFY_SLUG:-}" ]; then
|
|
380
|
+
return 0
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
local workspace="${CONVEYOR_WORKSPACE:-/workspaces/repo}"
|
|
384
|
+
local source_dir="${CONVEYOR_GRAPHIFY_DIR:-}"
|
|
385
|
+
local graph_file="${CONVEYOR_GRAPHIFY_GRAPH:-}"
|
|
386
|
+
|
|
387
|
+
if [ -z "${source_dir}" ] || [ -z "${graph_file}" ]; then
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
if [ ! -f "${graph_file}" ]; then
|
|
392
|
+
echo "[pool] Graphify bundle not found for '${CONVEYOR_GRAPHIFY_SLUG}' at ${source_dir}"
|
|
393
|
+
return 0
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
local target_dir="${workspace}/graphify-out"
|
|
397
|
+
if ! mkdir -p "${target_dir}" 2>/dev/null; then
|
|
398
|
+
echo "[pool] WARN: unable to create graphify-out at ${target_dir}"
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
local rel
|
|
403
|
+
for rel in graph.json GRAPH_REPORT.md manifest.json .graphify_analysis.json .graphify_labels.json publish-manifest.json cost.json; do
|
|
404
|
+
if [ ! -e "${source_dir}/${rel}" ]; then
|
|
405
|
+
continue
|
|
406
|
+
fi
|
|
407
|
+
if [ -e "${target_dir}/${rel}" ] || [ -L "${target_dir}/${rel}" ]; then
|
|
408
|
+
continue
|
|
409
|
+
fi
|
|
410
|
+
ln -s "${source_dir}/${rel}" "${target_dir}/${rel}" 2>/dev/null || true
|
|
411
|
+
done
|
|
412
|
+
|
|
413
|
+
echo "[pool] Bound graphify bundle '${CONVEYOR_GRAPHIFY_SLUG}' into ${target_dir}"
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
mark_git_ready() {
|
|
417
|
+
bind_graphify_bundle
|
|
418
|
+
: > "$GIT_READY_MARKER"
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
configure_graphify_env
|
|
422
|
+
|
|
334
423
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
335
424
|
# WORKSPACE GIT — moved OFF the pre-launch critical path (Claudespace v3).
|
|
336
425
|
#
|
|
@@ -489,7 +578,7 @@ prepare_workspace_git() {
|
|
|
489
578
|
# Task repo is ready HERE — release the gate. The agent (already launched)
|
|
490
579
|
# is polling for this marker before it spawns Claude / runs setup. Reference
|
|
491
580
|
# repos are cloned AFTER this so they never block Claude.
|
|
492
|
-
|
|
581
|
+
mark_git_ready
|
|
493
582
|
elif [ -d "repo/.git" ] && [ -n "${CONVEYOR_GITHUB_TOKEN}" ]; then
|
|
494
583
|
# Non-image but repo already present (e.g. a baked image running without
|
|
495
584
|
# CONVEYOR_POD_IMAGE set). Must NOT settle for a bare remote-URL refresh —
|
|
@@ -501,7 +590,7 @@ prepare_workspace_git() {
|
|
|
501
590
|
if ! sync_task_branch_to_repo; then
|
|
502
591
|
return 1
|
|
503
592
|
fi
|
|
504
|
-
|
|
593
|
+
mark_git_ready
|
|
505
594
|
elif [ -n "${CONVEYOR_GITHUB_TOKEN}" ] && [ -n "${REPO_OWNER}" ] && [ -n "${REPO_NAME}" ] && [ -n "${BRANCH}" ]; then
|
|
506
595
|
echo "[pool] Cloning repo post-assignment (pre-clone was missing)..."
|
|
507
596
|
# Guard each clone/fetch/checkout: under `set -e` a bare failing clone would
|
|
@@ -543,14 +632,14 @@ prepare_workspace_git() {
|
|
|
543
632
|
return 1
|
|
544
633
|
fi
|
|
545
634
|
fi
|
|
546
|
-
|
|
635
|
+
mark_git_ready
|
|
547
636
|
else
|
|
548
637
|
# No git plan (e.g. task-less pod, or no token/repo). Nothing to prepare —
|
|
549
638
|
# the repo dir already exists (created before launch) and the agent's own
|
|
550
639
|
# git-sync fallback (guarded by CONVEYOR_GIT_READY) never runs on this pod
|
|
551
640
|
# path anyway. Signal ready so the agent doesn't wait out the timeout.
|
|
552
641
|
echo "[pool] No git plan to prepare — marking git ready."
|
|
553
|
-
|
|
642
|
+
mark_git_ready
|
|
554
643
|
fi
|
|
555
644
|
|
|
556
645
|
# ── Clone reference repos into /workspaces/references (best-effort) ──
|