@schoolai/shipyard 3.19.0 → 3.20.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/capability-detector-worker.js +4 -4
- package/dist/{chunk-KMMF5R3M.js → chunk-FMHVDAJK.js} +3 -3
- package/dist/{chunk-FASB7KFF.js → chunk-LYUR7G6B.js} +2 -2
- package/dist/{chunk-NRXFTDBH.js → chunk-MD32Y6U4.js} +2 -2
- package/dist/{chunk-LVQLRQKF.js → chunk-OBYRPZRA.js} +40 -10
- package/dist/chunk-OBYRPZRA.js.map +1 -0
- package/dist/{chunk-6TK5KO76.js → chunk-OC75AUKJ.js} +33 -5
- package/dist/chunk-OC75AUKJ.js.map +1 -0
- package/dist/{chunk-4XU7QHYE.js → chunk-QNEFPC4Y.js} +2 -2
- package/dist/{chunk-JEIJ4LIM.js → chunk-SECZGRK2.js} +3 -3
- package/dist/{chunk-JEIJ4LIM.js.map → chunk-SECZGRK2.js.map} +1 -1
- package/dist/{chunk-HD66ZG4O.js → chunk-TJ4TMMMB.js} +3 -3
- package/dist/chunk-TJ4TMMMB.js.map +1 -0
- package/dist/cursor-runner.js +3 -3
- package/dist/electron-utility.js +2 -2
- package/dist/index.js +4 -4
- package/dist/{login-RU3BXPEI.js → login-VNZ6AQDV.js} +3 -3
- package/dist/{plan-backfill-CKSC7OK3.js → plan-backfill-ZNL2YQP6.js} +4 -4
- package/dist/{serve-AA4FCUY6.js → serve-WG7HQAYG.js} +653 -202
- package/dist/{serve-AA4FCUY6.js.map → serve-WG7HQAYG.js.map} +1 -1
- package/dist/{start-RNGMBSQN.js → start-5VG7QWUR.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-6TK5KO76.js.map +0 -1
- package/dist/chunk-HD66ZG4O.js.map +0 -1
- package/dist/chunk-LVQLRQKF.js.map +0 -1
- /package/dist/{chunk-KMMF5R3M.js.map → chunk-FMHVDAJK.js.map} +0 -0
- /package/dist/{chunk-FASB7KFF.js.map → chunk-LYUR7G6B.js.map} +0 -0
- /package/dist/{chunk-NRXFTDBH.js.map → chunk-MD32Y6U4.js.map} +0 -0
- /package/dist/{chunk-4XU7QHYE.js.map → chunk-QNEFPC4Y.js.map} +0 -0
- /package/dist/{login-RU3BXPEI.js.map → login-VNZ6AQDV.js.map} +0 -0
- /package/dist/{plan-backfill-CKSC7OK3.js.map → plan-backfill-ZNL2YQP6.js.map} +0 -0
- /package/dist/{start-RNGMBSQN.js.map → start-5VG7QWUR.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
guardedSubscribe,
|
|
13
13
|
makeInitialAttemptState,
|
|
14
14
|
shutdownFileWatcherGuard
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-QNEFPC4Y.js";
|
|
16
16
|
import {
|
|
17
17
|
TRAILER_SCHEMA_VERSION,
|
|
18
18
|
appendTrailerToMessage,
|
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
startPlanBackfillForDaemon,
|
|
40
40
|
sync,
|
|
41
41
|
validatePeerId
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-SECZGRK2.js";
|
|
43
43
|
import {
|
|
44
44
|
loadAuthToken
|
|
45
45
|
} from "./chunk-TFWDJCUJ.js";
|
|
46
46
|
import {
|
|
47
47
|
getDaemonVersion
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-FMHVDAJK.js";
|
|
49
49
|
import {
|
|
50
50
|
collectPriorCrashDetail,
|
|
51
51
|
getRecentWasmPanicMessages
|
|
@@ -174,7 +174,7 @@ import {
|
|
|
174
174
|
unresolveReviewThread,
|
|
175
175
|
withLabelAsync,
|
|
176
176
|
writeCursorApiKeyToVault
|
|
177
|
-
} from "./chunk-
|
|
177
|
+
} from "./chunk-OBYRPZRA.js";
|
|
178
178
|
import {
|
|
179
179
|
buildNodeSpawnEnv,
|
|
180
180
|
nodeSpawnEnvOverlay,
|
|
@@ -235,7 +235,7 @@ import {
|
|
|
235
235
|
createAssetResolver,
|
|
236
236
|
toSdkContent,
|
|
237
237
|
toSdkPermissionMode
|
|
238
|
-
} from "./chunk-
|
|
238
|
+
} from "./chunk-LYUR7G6B.js";
|
|
239
239
|
import {
|
|
240
240
|
AGENT_IDS,
|
|
241
241
|
AGENT_SYSTEM_CLAUDE_CODE,
|
|
@@ -306,6 +306,7 @@ import {
|
|
|
306
306
|
MERMAID_SECURITY_LEVEL,
|
|
307
307
|
MessageSchema,
|
|
308
308
|
OBSOLETE_PREFIXES,
|
|
309
|
+
OPEN_FILE_TOOL,
|
|
309
310
|
PLAN_EPOCH,
|
|
310
311
|
PLUGIN_READ_ME_TOOL,
|
|
311
312
|
PRESENT_TOOL,
|
|
@@ -420,7 +421,7 @@ import {
|
|
|
420
421
|
unsafeAssertSourceId,
|
|
421
422
|
vizFileExtension,
|
|
422
423
|
withPreferredRuntimeAuthMethod
|
|
423
|
-
} from "./chunk-
|
|
424
|
+
} from "./chunk-OC75AUKJ.js";
|
|
424
425
|
import {
|
|
425
426
|
AuthGitHubCallbackRequestSchema,
|
|
426
427
|
AuthGitHubCallbackResponseSchema,
|
|
@@ -468,7 +469,7 @@ import {
|
|
|
468
469
|
VaultKeyPutResponseSchema,
|
|
469
470
|
classifyClaudeCodeCompatibility,
|
|
470
471
|
classifyCodexCompatibility
|
|
471
|
-
} from "./chunk-
|
|
472
|
+
} from "./chunk-TJ4TMMMB.js";
|
|
472
473
|
import {
|
|
473
474
|
external_exports as external_exports2
|
|
474
475
|
} from "./chunk-EHQITHQX.js";
|
|
@@ -4857,28 +4858,133 @@ function getErrorCode(err3) {
|
|
|
4857
4858
|
|
|
4858
4859
|
// src/services/bootstrap/heartbeat.ts
|
|
4859
4860
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4861
|
+
|
|
4862
|
+
// src/services/bootstrap/pid-identity.ts
|
|
4863
|
+
import { execFileSync } from "child_process";
|
|
4864
|
+
function decideProcessIdentity(recorded, live) {
|
|
4865
|
+
if (recorded === null) return "unrecorded";
|
|
4866
|
+
if (live === null) return "unverifiable";
|
|
4867
|
+
return recorded === live ? "ours" : "not-ours";
|
|
4868
|
+
}
|
|
4869
|
+
function readProcessStartTime(pid, exec = defaultExec) {
|
|
4870
|
+
try {
|
|
4871
|
+
const out = exec("ps", ["-p", String(pid), "-o", "lstart="]).trim();
|
|
4872
|
+
return out.length > 0 ? out : null;
|
|
4873
|
+
} catch {
|
|
4874
|
+
return null;
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
function defaultExec(command, args) {
|
|
4878
|
+
return execFileSync(command, args, { encoding: "utf8" });
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
// src/services/bootstrap/pid-liveness.ts
|
|
4882
|
+
function probePid(pid, kill = process.kill.bind(process)) {
|
|
4883
|
+
try {
|
|
4884
|
+
kill(pid, 0);
|
|
4885
|
+
return { outcome: "signaled" };
|
|
4886
|
+
} catch (err3) {
|
|
4887
|
+
const code = errorCode(err3);
|
|
4888
|
+
if (code === "ESRCH") return { outcome: "no-process" };
|
|
4889
|
+
if (code === "EPERM") return { outcome: "foreign-owner" };
|
|
4890
|
+
return { outcome: "probe-error", code };
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
function errorCode(err3) {
|
|
4894
|
+
if (err3 instanceof Error && "code" in err3) {
|
|
4895
|
+
const { code } = err3;
|
|
4896
|
+
return typeof code === "string" ? code : void 0;
|
|
4897
|
+
}
|
|
4898
|
+
return void 0;
|
|
4899
|
+
}
|
|
4900
|
+
function classifyPidLiveness(probe) {
|
|
4901
|
+
switch (probe.outcome) {
|
|
4902
|
+
case "signaled":
|
|
4903
|
+
return "alive-same-user";
|
|
4904
|
+
case "no-process":
|
|
4905
|
+
return "stale";
|
|
4906
|
+
case "foreign-owner":
|
|
4907
|
+
return "foreign";
|
|
4908
|
+
case "probe-error":
|
|
4909
|
+
return "indeterminate";
|
|
4910
|
+
default:
|
|
4911
|
+
return assertNever(probe);
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
function decidePidAcquisition(reading, ctx) {
|
|
4915
|
+
switch (reading.kind) {
|
|
4916
|
+
case "absent":
|
|
4917
|
+
return { action: "reclaim", reason: "no-file" };
|
|
4918
|
+
case "corrupt":
|
|
4919
|
+
return { action: "reclaim", reason: "corrupt" };
|
|
4920
|
+
case "present":
|
|
4921
|
+
return decideForLiveness(reading.liveness, reading.identity, ctx);
|
|
4922
|
+
default:
|
|
4923
|
+
return assertNever(reading);
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
function decideForLiveness(liveness, identity, ctx) {
|
|
4927
|
+
switch (liveness) {
|
|
4928
|
+
case "stale":
|
|
4929
|
+
return { action: "reclaim", reason: "stale" };
|
|
4930
|
+
case "foreign":
|
|
4931
|
+
return { action: "reclaim", reason: "foreign" };
|
|
4932
|
+
case "alive-same-user":
|
|
4933
|
+
return decideForSameUser(identity, ctx);
|
|
4934
|
+
case "indeterminate":
|
|
4935
|
+
return { action: "refuse", reason: "indeterminate" };
|
|
4936
|
+
default:
|
|
4937
|
+
return assertNever(liveness);
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
function decideForSameUser(identity, ctx) {
|
|
4941
|
+
switch (identity) {
|
|
4942
|
+
case "ours":
|
|
4943
|
+
return { action: "refuse", reason: "live-same-user" };
|
|
4944
|
+
case "not-ours":
|
|
4945
|
+
return { action: "reclaim", reason: "recycled-pid" };
|
|
4946
|
+
case "unverifiable":
|
|
4947
|
+
return { action: "refuse", reason: "identity-unverifiable" };
|
|
4948
|
+
case "unrecorded":
|
|
4949
|
+
return ctx.selfTokenAvailable ? { action: "reclaim", reason: "unrecorded-stale" } : { action: "refuse", reason: "live-same-user" };
|
|
4950
|
+
default:
|
|
4951
|
+
return assertNever(identity);
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
// src/services/bootstrap/heartbeat.ts
|
|
4860
4956
|
var DEFAULT_INTERVAL_MS2 = 15e3;
|
|
4861
4957
|
var DEFAULT_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
4862
4958
|
function isHeartbeatPayload(value) {
|
|
4863
4959
|
if (typeof value !== "object" || value === null) return false;
|
|
4864
4960
|
if (!("pid" in value) || !("timestamp" in value)) return false;
|
|
4865
4961
|
const { pid, timestamp } = value;
|
|
4866
|
-
|
|
4962
|
+
if (typeof pid !== "number" || typeof timestamp !== "number") return false;
|
|
4963
|
+
if ("startTime" in value) {
|
|
4964
|
+
const { startTime } = value;
|
|
4965
|
+
if (typeof startTime !== "string") return false;
|
|
4966
|
+
}
|
|
4967
|
+
return true;
|
|
4867
4968
|
}
|
|
4868
4969
|
function getErrorCode2(err3) {
|
|
4869
4970
|
if (!(err3 instanceof Error) || !("code" in err3)) return void 0;
|
|
4870
4971
|
const { code } = err3;
|
|
4871
4972
|
return typeof code === "string" ? code : void 0;
|
|
4872
4973
|
}
|
|
4873
|
-
function
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
} catch (err3) {
|
|
4878
|
-
if (getErrorCode2(err3) === "ESRCH") {
|
|
4974
|
+
function isPriorDaemonRunning(payload, readStartTime) {
|
|
4975
|
+
const liveness = classifyPidLiveness(probePid(payload.pid));
|
|
4976
|
+
switch (liveness) {
|
|
4977
|
+
case "stale":
|
|
4879
4978
|
return false;
|
|
4979
|
+
case "foreign":
|
|
4980
|
+
case "indeterminate":
|
|
4981
|
+
return true;
|
|
4982
|
+
case "alive-same-user": {
|
|
4983
|
+
const identity = decideProcessIdentity(payload.startTime ?? null, readStartTime(payload.pid));
|
|
4984
|
+
return identity !== "not-ours";
|
|
4880
4985
|
}
|
|
4881
|
-
|
|
4986
|
+
default:
|
|
4987
|
+
return assertNever(liveness);
|
|
4882
4988
|
}
|
|
4883
4989
|
}
|
|
4884
4990
|
async function writeHeartbeatAtomic(fs3, pidFilePath, payload) {
|
|
@@ -4888,11 +4994,14 @@ async function writeHeartbeatAtomic(fs3, pidFilePath, payload) {
|
|
|
4888
4994
|
}
|
|
4889
4995
|
function startHeartbeat(deps) {
|
|
4890
4996
|
const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
4997
|
+
const readStartTime = deps.readProcessStartTime ?? readProcessStartTime;
|
|
4998
|
+
const ownStartTime = readStartTime(process.pid);
|
|
4891
4999
|
const writeOnce = async () => {
|
|
4892
5000
|
try {
|
|
4893
5001
|
await writeHeartbeatAtomic(deps.fs, deps.pidFilePath, {
|
|
4894
5002
|
pid: process.pid,
|
|
4895
|
-
timestamp: deps.now()
|
|
5003
|
+
timestamp: deps.now(),
|
|
5004
|
+
...ownStartTime !== null ? { startTime: ownStartTime } : {}
|
|
4896
5005
|
});
|
|
4897
5006
|
} catch (err3) {
|
|
4898
5007
|
deps.log({ event: "daemon_heartbeat_write_failed", err: String(err3) });
|
|
@@ -4923,6 +5032,7 @@ function startHeartbeat(deps) {
|
|
|
4923
5032
|
}
|
|
4924
5033
|
async function detectPriorAbnormalExit(deps) {
|
|
4925
5034
|
const staleThresholdMs = deps.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS;
|
|
5035
|
+
const readStartTime = deps.readProcessStartTime ?? readProcessStartTime;
|
|
4926
5036
|
let raw;
|
|
4927
5037
|
try {
|
|
4928
5038
|
raw = await deps.fs.readFile(deps.pidFilePath, "utf-8");
|
|
@@ -4950,7 +5060,7 @@ async function detectPriorAbnormalExit(deps) {
|
|
|
4950
5060
|
if (ageMs > staleThresholdMs) {
|
|
4951
5061
|
return { prior: "unknown" };
|
|
4952
5062
|
}
|
|
4953
|
-
if (
|
|
5063
|
+
if (isPriorDaemonRunning(parsed, readStartTime)) {
|
|
4954
5064
|
return { prior: "clean" };
|
|
4955
5065
|
}
|
|
4956
5066
|
return {
|
|
@@ -5200,7 +5310,7 @@ function forwardAbnormalExitMetric(report, restartDetail, metrics) {
|
|
|
5200
5310
|
}
|
|
5201
5311
|
|
|
5202
5312
|
// src/services/bootstrap/lifecycle.ts
|
|
5203
|
-
import { unlinkSync } from "fs";
|
|
5313
|
+
import { readFileSync, unlinkSync } from "fs";
|
|
5204
5314
|
import { readFile as readFile3, unlink as unlink2, writeFile as writeFile4 } from "fs/promises";
|
|
5205
5315
|
import { join as join9 } from "path";
|
|
5206
5316
|
|
|
@@ -5252,67 +5362,14 @@ function getLoroCrdtVersion() {
|
|
|
5252
5362
|
return cachedVersion;
|
|
5253
5363
|
}
|
|
5254
5364
|
|
|
5255
|
-
// src/services/bootstrap/pid-liveness.ts
|
|
5256
|
-
function probePid(pid, kill = process.kill.bind(process)) {
|
|
5257
|
-
try {
|
|
5258
|
-
kill(pid, 0);
|
|
5259
|
-
return { outcome: "signaled" };
|
|
5260
|
-
} catch (err3) {
|
|
5261
|
-
const code = errorCode(err3);
|
|
5262
|
-
if (code === "ESRCH") return { outcome: "no-process" };
|
|
5263
|
-
if (code === "EPERM") return { outcome: "foreign-owner" };
|
|
5264
|
-
return { outcome: "probe-error", code };
|
|
5265
|
-
}
|
|
5266
|
-
}
|
|
5267
|
-
function errorCode(err3) {
|
|
5268
|
-
if (err3 instanceof Error && "code" in err3) {
|
|
5269
|
-
const { code } = err3;
|
|
5270
|
-
return typeof code === "string" ? code : void 0;
|
|
5271
|
-
}
|
|
5272
|
-
return void 0;
|
|
5273
|
-
}
|
|
5274
|
-
function classifyPidLiveness(probe) {
|
|
5275
|
-
switch (probe.outcome) {
|
|
5276
|
-
case "signaled":
|
|
5277
|
-
return "alive-same-user";
|
|
5278
|
-
case "no-process":
|
|
5279
|
-
return "stale";
|
|
5280
|
-
case "foreign-owner":
|
|
5281
|
-
return "foreign";
|
|
5282
|
-
case "probe-error":
|
|
5283
|
-
return "indeterminate";
|
|
5284
|
-
default:
|
|
5285
|
-
return assertNever(probe);
|
|
5286
|
-
}
|
|
5287
|
-
}
|
|
5288
|
-
function decidePidAcquisition(reading) {
|
|
5289
|
-
switch (reading.kind) {
|
|
5290
|
-
case "absent":
|
|
5291
|
-
return { action: "reclaim", reason: "no-file" };
|
|
5292
|
-
case "corrupt":
|
|
5293
|
-
return { action: "reclaim", reason: "corrupt" };
|
|
5294
|
-
case "present":
|
|
5295
|
-
return decideForLiveness(reading.liveness);
|
|
5296
|
-
default:
|
|
5297
|
-
return assertNever(reading);
|
|
5298
|
-
}
|
|
5299
|
-
}
|
|
5300
|
-
function decideForLiveness(liveness) {
|
|
5301
|
-
switch (liveness) {
|
|
5302
|
-
case "stale":
|
|
5303
|
-
return { action: "reclaim", reason: "stale" };
|
|
5304
|
-
case "foreign":
|
|
5305
|
-
return { action: "reclaim", reason: "foreign" };
|
|
5306
|
-
case "alive-same-user":
|
|
5307
|
-
return { action: "refuse", reason: "live-same-user" };
|
|
5308
|
-
case "indeterminate":
|
|
5309
|
-
return { action: "refuse", reason: "indeterminate" };
|
|
5310
|
-
default:
|
|
5311
|
-
return assertNever(liveness);
|
|
5312
|
-
}
|
|
5313
|
-
}
|
|
5314
|
-
|
|
5315
5365
|
// src/services/bootstrap/lifecycle.ts
|
|
5366
|
+
var PID_META_SUFFIX = ".meta";
|
|
5367
|
+
function isPidMetaPayload(value) {
|
|
5368
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5369
|
+
if (!("pid" in value) || !("startTime" in value)) return false;
|
|
5370
|
+
const { pid, startTime } = value;
|
|
5371
|
+
return typeof pid === "number" && typeof startTime === "string";
|
|
5372
|
+
}
|
|
5316
5373
|
function extractWasmPanicContext(error) {
|
|
5317
5374
|
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
5318
5375
|
const frames = stack.split("\n").map((f2) => f2.trim());
|
|
@@ -5328,16 +5385,19 @@ var LifecycleManager = class {
|
|
|
5328
5385
|
#isShuttingDown = false;
|
|
5329
5386
|
#cleanupFns = [];
|
|
5330
5387
|
#pidFilePath = null;
|
|
5388
|
+
#sidecarFilePath = null;
|
|
5331
5389
|
#log;
|
|
5332
5390
|
#flushLogger;
|
|
5333
5391
|
#deps;
|
|
5334
5392
|
#probePid;
|
|
5393
|
+
#readProcessStartTime;
|
|
5335
5394
|
#started = false;
|
|
5336
5395
|
constructor(log, deps, flushLogger2) {
|
|
5337
5396
|
this.#log = log;
|
|
5338
5397
|
this.#deps = deps;
|
|
5339
5398
|
this.#flushLogger = flushLogger2 ?? (() => Promise.resolve());
|
|
5340
5399
|
this.#probePid = deps.probePid ?? ((pid) => probePid(pid));
|
|
5400
|
+
this.#readProcessStartTime = deps.readProcessStartTime ?? ((pid) => readProcessStartTime(pid));
|
|
5341
5401
|
}
|
|
5342
5402
|
start() {
|
|
5343
5403
|
if (this.#started) return;
|
|
@@ -5414,15 +5474,19 @@ var LifecycleManager = class {
|
|
|
5414
5474
|
}
|
|
5415
5475
|
async acquirePidFile(shipyardHome) {
|
|
5416
5476
|
const pidFilePath = join9(shipyardHome, "daemon.pid");
|
|
5417
|
-
const
|
|
5418
|
-
const
|
|
5477
|
+
const sidecarPath = pidFilePath + PID_META_SUFFIX;
|
|
5478
|
+
const ownStartTime = this.#readProcessStartTime(process.pid);
|
|
5479
|
+
const reading = await this.#readPidFile(pidFilePath, sidecarPath);
|
|
5480
|
+
const decision = decidePidAcquisition(reading, { selfTokenAvailable: ownStartTime !== null });
|
|
5419
5481
|
this.#applyPidDecision(decision, reading, pidFilePath);
|
|
5482
|
+
await this.#writeSidecar(sidecarPath, ownStartTime);
|
|
5483
|
+
this.#sidecarFilePath = sidecarPath;
|
|
5420
5484
|
await writeFile4(pidFilePath, String(process.pid), { mode: 420 });
|
|
5421
5485
|
this.#pidFilePath = pidFilePath;
|
|
5422
5486
|
this.#log.info({ pid: process.pid, pidFile: pidFilePath }, "PID file acquired");
|
|
5423
5487
|
}
|
|
5424
5488
|
/** GATHER: read + normalize the existing PID file into a `PidFileReading`. */
|
|
5425
|
-
async #readPidFile(pidFilePath) {
|
|
5489
|
+
async #readPidFile(pidFilePath, sidecarPath) {
|
|
5426
5490
|
let existing;
|
|
5427
5491
|
try {
|
|
5428
5492
|
existing = await readFile3(pidFilePath, "utf-8");
|
|
@@ -5433,27 +5497,63 @@ var LifecycleManager = class {
|
|
|
5433
5497
|
const raw = existing.trim();
|
|
5434
5498
|
const pid = Number.parseInt(raw, 10);
|
|
5435
5499
|
if (!Number.isInteger(pid) || pid <= 0) return { kind: "corrupt", raw };
|
|
5436
|
-
|
|
5500
|
+
const liveness = classifyPidLiveness(this.#probePid(pid));
|
|
5501
|
+
const identity = liveness === "alive-same-user" ? this.#readIdentity(pid, sidecarPath) : "unrecorded";
|
|
5502
|
+
return { kind: "present", pid, liveness, identity };
|
|
5503
|
+
}
|
|
5504
|
+
/** GATHER + DECIDE: compare the recorded token against the live start time. */
|
|
5505
|
+
#readIdentity(pid, sidecarPath) {
|
|
5506
|
+
const recorded = this.#readRecordedStartTime(pid, sidecarPath);
|
|
5507
|
+
const live = this.#readProcessStartTime(pid);
|
|
5508
|
+
return decideProcessIdentity(recorded, live);
|
|
5509
|
+
}
|
|
5510
|
+
/**
|
|
5511
|
+
* Read the start-time token recorded for `pid` from the sidecar. Returns
|
|
5512
|
+
* `null` when the sidecar is absent, unparseable, or was written for a
|
|
5513
|
+
* different PID (a stale sidecar must not be trusted as this PID's identity).
|
|
5514
|
+
*/
|
|
5515
|
+
#readRecordedStartTime(pid, sidecarPath) {
|
|
5516
|
+
let raw;
|
|
5517
|
+
try {
|
|
5518
|
+
raw = readFileSync(sidecarPath, "utf-8");
|
|
5519
|
+
} catch {
|
|
5520
|
+
return null;
|
|
5521
|
+
}
|
|
5522
|
+
let parsed;
|
|
5523
|
+
try {
|
|
5524
|
+
parsed = JSON.parse(raw);
|
|
5525
|
+
} catch {
|
|
5526
|
+
return null;
|
|
5527
|
+
}
|
|
5528
|
+
if (!isPidMetaPayload(parsed) || parsed.pid !== pid) return null;
|
|
5529
|
+
return parsed.startTime;
|
|
5530
|
+
}
|
|
5531
|
+
/**
|
|
5532
|
+
* Persist the identity token alongside `daemon.pid`. When we could not read
|
|
5533
|
+
* our own start time (`null`), remove any stale sidecar rather than writing a
|
|
5534
|
+
* bogus one — a future reader then sees `unrecorded` and picks a safe default.
|
|
5535
|
+
*/
|
|
5536
|
+
async #writeSidecar(sidecarPath, startTime) {
|
|
5537
|
+
if (startTime === null) {
|
|
5538
|
+
try {
|
|
5539
|
+
await unlink2(sidecarPath);
|
|
5540
|
+
} catch {
|
|
5541
|
+
}
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
const payload = { pid: process.pid, startTime };
|
|
5545
|
+
await writeFile4(sidecarPath, JSON.stringify(payload), { mode: 420 });
|
|
5437
5546
|
}
|
|
5438
5547
|
/** EXECUTE: log the decision and refuse (exit) when mutual exclusion applies. */
|
|
5439
5548
|
#applyPidDecision(decision, reading, pidFilePath) {
|
|
5440
5549
|
if (decision.action === "refuse") {
|
|
5441
5550
|
const pid = reading.kind === "present" ? reading.pid : null;
|
|
5442
|
-
|
|
5443
|
-
this.#log.error(
|
|
5444
|
-
{ event: "pid_live_daemon_refused", pid, pidFile: pidFilePath },
|
|
5445
|
-
"Another daemon is already running. Stop it first or remove the stale PID file."
|
|
5446
|
-
);
|
|
5447
|
-
} else {
|
|
5448
|
-
this.#log.error(
|
|
5449
|
-
{ event: "pid_probe_indeterminate_refused", pid, pidFile: pidFilePath },
|
|
5450
|
-
"Cannot determine if existing daemon is alive \u2014 refusing to overwrite PID file."
|
|
5451
|
-
);
|
|
5452
|
-
}
|
|
5551
|
+
this.#logRefusal(decision.reason, pid, pidFilePath);
|
|
5453
5552
|
process.exit(1);
|
|
5454
5553
|
}
|
|
5455
5554
|
const reclaimedPid = reading.kind === "present" ? reading.pid : null;
|
|
5456
|
-
|
|
5555
|
+
const reason = decision.reason;
|
|
5556
|
+
switch (reason) {
|
|
5457
5557
|
case "no-file":
|
|
5458
5558
|
return;
|
|
5459
5559
|
case "corrupt":
|
|
@@ -5478,23 +5578,80 @@ var LifecycleManager = class {
|
|
|
5478
5578
|
"PID file points at a process owned by another user \u2014 not our daemon, reclaiming"
|
|
5479
5579
|
);
|
|
5480
5580
|
return;
|
|
5581
|
+
case "recycled-pid":
|
|
5582
|
+
this.#log.info(
|
|
5583
|
+
{ event: "pid_reclaimed_recycled", stalePid: reclaimedPid, pidFile: pidFilePath },
|
|
5584
|
+
"PID file points at a recycled PID (start time differs) \u2014 not our daemon, reclaiming"
|
|
5585
|
+
);
|
|
5586
|
+
return;
|
|
5587
|
+
case "unrecorded-stale":
|
|
5588
|
+
this.#log.info(
|
|
5589
|
+
{ event: "pid_reclaimed_unrecorded", stalePid: reclaimedPid, pidFile: pidFilePath },
|
|
5590
|
+
"PID file has no identity sidecar (pre-fix leftover) \u2014 treating as stale and reclaiming"
|
|
5591
|
+
);
|
|
5592
|
+
return;
|
|
5593
|
+
default:
|
|
5594
|
+
assertNever(reason);
|
|
5481
5595
|
}
|
|
5482
5596
|
}
|
|
5597
|
+
#logRefusal(reason, pid, pidFilePath) {
|
|
5598
|
+
switch (reason) {
|
|
5599
|
+
case "live-same-user":
|
|
5600
|
+
this.#log.error(
|
|
5601
|
+
{ event: "pid_live_daemon_refused", pid, pidFile: pidFilePath },
|
|
5602
|
+
"Another daemon is already running. Stop it first or remove the stale PID file."
|
|
5603
|
+
);
|
|
5604
|
+
return;
|
|
5605
|
+
case "identity-unverifiable":
|
|
5606
|
+
this.#log.error(
|
|
5607
|
+
{ event: "pid_identity_unverifiable_refused", pid, pidFile: pidFilePath },
|
|
5608
|
+
"Existing PID is alive but its identity could not be verified \u2014 refusing to overwrite PID file."
|
|
5609
|
+
);
|
|
5610
|
+
return;
|
|
5611
|
+
case "indeterminate":
|
|
5612
|
+
this.#log.error(
|
|
5613
|
+
{ event: "pid_probe_indeterminate_refused", pid, pidFile: pidFilePath },
|
|
5614
|
+
"Cannot determine if existing daemon is alive \u2014 refusing to overwrite PID file."
|
|
5615
|
+
);
|
|
5616
|
+
return;
|
|
5617
|
+
default:
|
|
5618
|
+
assertNever(reason);
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
/**
|
|
5622
|
+
* Remove `daemon.pid` BEFORE the sidecar (mirror of the acquire ordering):
|
|
5623
|
+
* once `daemon.pid` is gone a reader treats the slot as free regardless of
|
|
5624
|
+
* the sidecar, so we never expose "pid present, sidecar gone" during teardown.
|
|
5625
|
+
*/
|
|
5483
5626
|
#removePidFileSync() {
|
|
5484
|
-
if (
|
|
5627
|
+
if (this.#pidFilePath) {
|
|
5628
|
+
try {
|
|
5629
|
+
unlinkSync(this.#pidFilePath);
|
|
5630
|
+
} catch {
|
|
5631
|
+
}
|
|
5632
|
+
this.#pidFilePath = null;
|
|
5633
|
+
}
|
|
5634
|
+
if (!this.#sidecarFilePath) return;
|
|
5485
5635
|
try {
|
|
5486
|
-
unlinkSync(this.#
|
|
5636
|
+
unlinkSync(this.#sidecarFilePath);
|
|
5487
5637
|
} catch {
|
|
5488
5638
|
}
|
|
5489
|
-
this.#
|
|
5639
|
+
this.#sidecarFilePath = null;
|
|
5490
5640
|
}
|
|
5491
5641
|
async #removePidFile() {
|
|
5492
|
-
if (
|
|
5642
|
+
if (this.#pidFilePath) {
|
|
5643
|
+
try {
|
|
5644
|
+
await unlink2(this.#pidFilePath);
|
|
5645
|
+
} catch {
|
|
5646
|
+
}
|
|
5647
|
+
this.#pidFilePath = null;
|
|
5648
|
+
}
|
|
5649
|
+
if (!this.#sidecarFilePath) return;
|
|
5493
5650
|
try {
|
|
5494
|
-
await unlink2(this.#
|
|
5651
|
+
await unlink2(this.#sidecarFilePath);
|
|
5495
5652
|
} catch {
|
|
5496
5653
|
}
|
|
5497
|
-
this.#
|
|
5654
|
+
this.#sidecarFilePath = null;
|
|
5498
5655
|
}
|
|
5499
5656
|
/**
|
|
5500
5657
|
* Wasm-specific recovery path. Runs when a panic originates inside
|
|
@@ -5828,7 +5985,7 @@ function buildReloadDocsFromStorage(repoRef, log) {
|
|
|
5828
5985
|
}
|
|
5829
5986
|
|
|
5830
5987
|
// src/services/bootstrap/signaling.ts
|
|
5831
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5988
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5832
5989
|
import { hostname } from "os";
|
|
5833
5990
|
import { dirname as dirname5, join as join12 } from "path";
|
|
5834
5991
|
|
|
@@ -5864,7 +6021,7 @@ function nanoid(size = 21) {
|
|
|
5864
6021
|
}
|
|
5865
6022
|
|
|
5866
6023
|
// src/services/bootstrap/signaling.ts
|
|
5867
|
-
var DAEMON_NPM_VERSION = true ? "3.
|
|
6024
|
+
var DAEMON_NPM_VERSION = true ? "3.20.0" : "unknown";
|
|
5868
6025
|
function createDaemonSignaling(config) {
|
|
5869
6026
|
const agentId = config.agentId ?? nanoid();
|
|
5870
6027
|
function send(msg) {
|
|
@@ -5895,7 +6052,7 @@ function createDaemonSignaling(config) {
|
|
|
5895
6052
|
function getOrCreateAgentId(shipyardHome) {
|
|
5896
6053
|
const filePath = join12(shipyardHome, "agent-id");
|
|
5897
6054
|
try {
|
|
5898
|
-
const existing =
|
|
6055
|
+
const existing = readFileSync2(filePath, "utf-8").trim();
|
|
5899
6056
|
if (existing) return existing;
|
|
5900
6057
|
} catch {
|
|
5901
6058
|
}
|
|
@@ -7583,10 +7740,21 @@ function runOnCloseCleanup(isLive, cleanup) {
|
|
|
7583
7740
|
cleanup.liveOnly.deleteEpochEntry();
|
|
7584
7741
|
}
|
|
7585
7742
|
function runTaskSearch(args) {
|
|
7586
|
-
const {
|
|
7743
|
+
const {
|
|
7744
|
+
daemon,
|
|
7745
|
+
controlHandler,
|
|
7746
|
+
logAdapter,
|
|
7747
|
+
requestId,
|
|
7748
|
+
query: query3,
|
|
7749
|
+
limit,
|
|
7750
|
+
includeReviews,
|
|
7751
|
+
includeArchived,
|
|
7752
|
+
projectId
|
|
7753
|
+
} = args;
|
|
7587
7754
|
daemon.taskStateStore.searchTasks(query3, {
|
|
7588
7755
|
limit: limit ?? 100,
|
|
7589
7756
|
includeReviews,
|
|
7757
|
+
includeArchived,
|
|
7590
7758
|
...projectId !== void 0 && { projectId }
|
|
7591
7759
|
}).then(({ tasks, truncated }) => {
|
|
7592
7760
|
controlHandler.sendControl({
|
|
@@ -10624,7 +10792,7 @@ import { basename as basename5, join as join19 } from "path";
|
|
|
10624
10792
|
|
|
10625
10793
|
// src/services/publish/published-artifact-store.ts
|
|
10626
10794
|
import { createHash, randomUUID as randomUUID7 } from "crypto";
|
|
10627
|
-
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as
|
|
10795
|
+
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
10628
10796
|
import { mkdir as mkdir7, readFile as readFile9, rename as rename6, unlink as unlink5, writeFile as writeFile9 } from "fs/promises";
|
|
10629
10797
|
import { dirname as dirname9, join as join17, relative as relative3 } from "path";
|
|
10630
10798
|
import { isDeepStrictEqual } from "util";
|
|
@@ -10647,7 +10815,7 @@ async function atomicWrite(filePath, content) {
|
|
|
10647
10815
|
function parseRecord(filePath, log) {
|
|
10648
10816
|
let raw;
|
|
10649
10817
|
try {
|
|
10650
|
-
raw =
|
|
10818
|
+
raw = readFileSync3(filePath, "utf-8");
|
|
10651
10819
|
} catch {
|
|
10652
10820
|
return null;
|
|
10653
10821
|
}
|
|
@@ -14326,6 +14494,13 @@ function filterOutboundForCollab(msg, collabTaskId, getTaskSync) {
|
|
|
14326
14494
|
*/
|
|
14327
14495
|
case "auth_refresh_failed":
|
|
14328
14496
|
case "open_canvas":
|
|
14497
|
+
/**
|
|
14498
|
+
* open_file: owner-local UI navigation, suppressed for the same reason as
|
|
14499
|
+
* open_canvas. Auto-hijacking a viewer's code panel to a file the owner's
|
|
14500
|
+
* agent opened is intrusive, and the path is owner-cwd-relative so it may
|
|
14501
|
+
* not even resolve on a viewer's tree. Viewers navigate their own file view.
|
|
14502
|
+
*/
|
|
14503
|
+
case "open_file":
|
|
14329
14504
|
case "plugin_data_response":
|
|
14330
14505
|
case "plugin_auth_status":
|
|
14331
14506
|
case "plugin_panels_updated":
|
|
@@ -15500,6 +15675,7 @@ function routeMessage(msg, callbacks, sendControl, log) {
|
|
|
15500
15675
|
query: msg.query,
|
|
15501
15676
|
limit: msg.limit,
|
|
15502
15677
|
includeReviews: msg.includeReviews ?? false,
|
|
15678
|
+
includeArchived: msg.includeArchived ?? false,
|
|
15503
15679
|
projectId: msg.projectId
|
|
15504
15680
|
});
|
|
15505
15681
|
break;
|
|
@@ -19316,7 +19492,7 @@ function decideCodexAuthFromAccountUpdated(authMode) {
|
|
|
19316
19492
|
}
|
|
19317
19493
|
|
|
19318
19494
|
// src/services/serve-factory-helpers.ts
|
|
19319
|
-
import { existsSync as existsSync3, readFileSync as
|
|
19495
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
19320
19496
|
import { join as join28 } from "path";
|
|
19321
19497
|
|
|
19322
19498
|
// src/shared/capabilities/runtime/rate-limit-account-key.ts
|
|
@@ -19713,7 +19889,7 @@ function isSafeStorageReply(msg) {
|
|
|
19713
19889
|
}
|
|
19714
19890
|
|
|
19715
19891
|
// src/services/harness/lsp-agent-server.ts
|
|
19716
|
-
import { readFileSync as
|
|
19892
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
19717
19893
|
import { basename as basename6, relative as relative4, resolve as resolve6 } from "path";
|
|
19718
19894
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
19719
19895
|
var LspPositionSchema = external_exports.object({ line: external_exports.number(), character: external_exports.number() });
|
|
@@ -19808,7 +19984,7 @@ function isSecretFile(absolutePath) {
|
|
|
19808
19984
|
}
|
|
19809
19985
|
function readSnippet(absolutePath, line0, context = 2) {
|
|
19810
19986
|
try {
|
|
19811
|
-
const lines =
|
|
19987
|
+
const lines = readFileSync4(absolutePath, "utf8").split("\n");
|
|
19812
19988
|
const start = Math.max(0, line0 - context);
|
|
19813
19989
|
const end = Math.min(lines.length - 1, line0 + context);
|
|
19814
19990
|
return lines.slice(start, end + 1).map((l, i) => ` ${start + i + 1} ${l}`).join("\n");
|
|
@@ -20203,6 +20379,9 @@ var REGISTER_DELIVERABLE_PROMPT_DOC = [
|
|
|
20203
20379
|
"",
|
|
20204
20380
|
"**Required fields:** `kind` (one of `screenshot`, `video`, `log`, `markdown`, `html`, `json`, `ax-tree`, `dom-snapshot`, `other`), `title`, and exactly one of `assetId` or `filePath`."
|
|
20205
20381
|
].join("\n");
|
|
20382
|
+
var OPEN_FILE_PROMPT_DOC = [
|
|
20383
|
+
"`open_file` opens a file in the code-editor side panel \u2014 pass `path` (absolute or workspace-relative) and an optional 1-based `line` to scroll to. Reach for it to surface a file the reviewer should see: right after you edit it, when your reply references specific code, or when pointing them at what to review next. Not a substitute for a diff comment \u2014 use `add_comment` to explain a change; use `open_file` to put the file in front of them."
|
|
20384
|
+
].join("\n");
|
|
20206
20385
|
var SKILLS_PROMPT_DOC = [
|
|
20207
20386
|
"Shipyard curates a set of skills for this task \u2014 reusable, expert instructions for specific workflows. **Before starting domain-specific work, check skills first:** call `list_skills` to see what's available, then `load_skill({ name })` to pull a skill's full instructions (and a directory path for any sibling files it references) before you dive in. A relevant skill saves you re-deriving conventions the team already wrote down. Some skills are also reachable through your own `/name` mentions; for anything in `list_skills`, `load_skill` always works."
|
|
20208
20387
|
].join("\n");
|
|
@@ -20317,7 +20496,9 @@ var HARNESS_TOOL_REGISTRY = [
|
|
|
20317
20496
|
appliesTo: "all",
|
|
20318
20497
|
modeGate: "task",
|
|
20319
20498
|
promptDoc: `\`${LSP_RENAME_SYMBOL_TOOL}\` \u2014 ${LSP_RENAME_SYMBOL_DESCRIPTION}`
|
|
20320
|
-
}
|
|
20499
|
+
},
|
|
20500
|
+
/** File viewer — gate: task. */
|
|
20501
|
+
{ name: OPEN_FILE_TOOL, appliesTo: "all", modeGate: "task", promptDoc: OPEN_FILE_PROMPT_DOC }
|
|
20321
20502
|
];
|
|
20322
20503
|
|
|
20323
20504
|
// src/services/harness/system-prompt.ts
|
|
@@ -20348,7 +20529,8 @@ var TOOL_SECTIONS = [
|
|
|
20348
20529
|
{
|
|
20349
20530
|
heading: "### Code Navigation (LSP)",
|
|
20350
20531
|
tools: ["lsp_go_to_definition", "lsp_find_references", "lsp_rename_symbol"]
|
|
20351
|
-
}
|
|
20532
|
+
},
|
|
20533
|
+
{ heading: "### File Viewer", tools: ["open_file"] }
|
|
20352
20534
|
];
|
|
20353
20535
|
var TOOL_SECTION_HEADINGS = TOOL_SECTIONS.map((s2) => s2.heading);
|
|
20354
20536
|
function buildToolsBlock(agent, intro) {
|
|
@@ -20368,14 +20550,10 @@ function buildToolsBlock(agent, intro) {
|
|
|
20368
20550
|
return parts.join("\n\n");
|
|
20369
20551
|
}
|
|
20370
20552
|
var CLAUDE_TOOLS = buildToolsBlock("claude-code", null);
|
|
20371
|
-
var
|
|
20372
|
-
|
|
20373
|
-
|
|
20374
|
-
);
|
|
20375
|
-
var CURSOR_TOOLS = buildToolsBlock(
|
|
20376
|
-
"cursor",
|
|
20377
|
-
"You have access to a Shipyard MCP server. The tools below are MCP tools registered on that server \u2014 call them by their unqualified names (e.g. `add_comment`, `visualize`, `present`). For shell access use the native `Shell` tool; for file edits use `Write` / `Edit`; for the live task list use the `shipyard_update_plan` MCP tool."
|
|
20378
|
-
);
|
|
20553
|
+
var CODEX_TOOLS_PREAMBLE = "You have access to a Shipyard MCP server. The tools below are MCP tools registered on that server \u2014 call them by their unqualified names (e.g. `add_comment`, `visualize`, `present`). For shell access use the native `shell` tool; for file edits use `apply_patch`; for the live task list use the native `update_plan` tool; for spawning subagents use the native `spawn_agent` tool. Those native tools are always available even though they are NOT in the MCP tool list below \u2014 do not go looking for them there.";
|
|
20554
|
+
var CURSOR_TOOLS_PREAMBLE = "You have access to a Shipyard MCP server. The tools below are MCP tools registered on that server \u2014 call them by their unqualified names (e.g. `add_comment`, `visualize`, `present`). For shell access use the native `Shell` tool; for file edits use `Write` / `Edit`; for spawning subagents use the native `task` tool; for the live task list use the `shipyard_update_plan` MCP tool. Those native tools are always available even though they are NOT in the MCP tool list below. Careful: the list below also registers a Shipyard MCP tool named `task` \u2014 that one manages sibling tasks (send/cancel/snapshot) and does NOT spawn subagents; only your native `task` tool does.";
|
|
20555
|
+
var CODEX_TOOLS = buildToolsBlock("codex", CODEX_TOOLS_PREAMBLE);
|
|
20556
|
+
var CURSOR_TOOLS = buildToolsBlock("cursor", CURSOR_TOOLS_PREAMBLE);
|
|
20379
20557
|
function sharedIntro(shellWord) {
|
|
20380
20558
|
return `# Shipyard
|
|
20381
20559
|
|
|
@@ -20615,7 +20793,7 @@ var CODEX_NOTES = `## Codex-specific Notes
|
|
|
20615
20793
|
- **Plan mode** \u2014 Publish plans by wrapping them in \`<proposed_plan>...</proposed_plan>\` blocks (Codex's native publish marker, parsed out of your streamed text and rendered as a structured plan on the Shipyard canvas). There is no \`ExitPlanMode\` tool. To revise, emit a fresh \`<proposed_plan>\` block in the next turn \u2014 each block is a complete replacement of the prior one. See the \`<plan>\` resource section above for full semantics (sandbox shape, reviewer feedback flow, approval signal).
|
|
20616
20794
|
- **\`request_user_input\`** \u2014 Codex's tool for asking the user multiple-choice questions during plan refinement. Routes to the same UX as Claude's \`AskUserQuestion\`. Use it whenever you need a decision from the user that materially changes the plan. Options-only \u2014 freeform input (\`options: null\`) and secret input (\`isSecret: true\`) are not yet supported by Shipyard and will return an error suggesting you re-ask with multiple-choice options.
|
|
20617
20795
|
- **Todo list** \u2014 Use the \`update_plan\` tool to keep the user-visible task list in sync **in non-plan modes only**. Codex blocks \`update_plan\` while you are in plan-collaboration mode and will return an error \u2014 \`update_plan\` is a checklist/progress tool, distinct from the \`<proposed_plan>\` publishing flow. In normal mode: when starting, include any items already visible in \`<task-list>\` using their exact subjects, then add your own steps below them. Don't paraphrase template subjects or you'll create duplicate sidebar entries. Update statuses (pending \u2192 in_progress \u2192 completed) as you go; call \`update_plan\` whenever you start, finish, or reorder a step.
|
|
20618
|
-
- **Subagents** \u2014 Use
|
|
20796
|
+
- **Subagents** \u2014 Use your native \`spawn_agent\` tool to launch a sub-thread for a self-contained task. It is a built-in Codex tool, NOT a Shipyard MCP tool, so it will not appear in the MCP tool list \u2014 call it directly rather than searching the MCP surface for it. Codex runs sub-agents in separate threads; results land on the parent turn via \`wait\`. \`spawn_agent\` has no \`run_in_background\` field \u2014 every call is foreground from your perspective.
|
|
20619
20797
|
- **Edits** \u2014 Use the \`apply_patch\` tool for all file edits. Codex does not have separate Read/Edit/Write tools; \`apply_patch\` is the single edit verb.
|
|
20620
20798
|
- **Patching** \u2014 \`apply_patch\` does NOT enforce string uniqueness the way Claude's \`Edit\` tool does. When editing a repeated pattern (e.g. \`return null;\` appearing in many functions), include enough surrounding context in the hunk to make the match unambiguous, or you will silently edit the wrong occurrence. Concretely: prefer a 3+ line context window above and below the change, and include the enclosing function header in the hunk when in doubt.
|
|
20621
20799
|
- **Permission failures \u2014 do not retry escalated** \u2014 this runtime's approval policy is \`never\` and sandbox escalation requests will be rejected. If a \`shell\` command fails on permissions or sandboxing, report the failure to the user and stop; do NOT retry with elevated permissions, do NOT loop on \`with_escalated_permissions\`. The \`openai-docs\` skill's "immediately retry with escalated permissions" advice does not apply here \u2014 it would just burn turns hitting the same rejection.
|
|
@@ -20678,7 +20856,7 @@ var CURSOR_NOTES = `## Cursor-specific Notes
|
|
|
20678
20856
|
- **Ignore \`run_in_background: true\`** \u2014 that is a Claude-Code-only flag on \`Bash\`/Task. Cursor's \`Shell\` and \`task\` do not accept it. AGENTS.md says "Always use \`run_in_background: true\` on all Task tool calls" \u2014 that rule does not apply here.
|
|
20679
20857
|
- **Background execution** \u2014 Cursor does not support background execution at all. Long-running \`Shell\` commands block the current turn; keep them short or break work into smaller steps so the user sees progress. For genuinely long work, ask the user to split it into multiple turns.
|
|
20680
20858
|
- **Todo list** \u2014 Use the \`shipyard_update_plan\` MCP tool to keep the user-visible task list in sync. Pass the FULL list of steps with their statuses on every call \u2014 partial updates are not supported. When starting, include any items already visible in \`<${XML_TAGS.TASK_LIST}>\` with their visible IDs (for example, use \`id: "1"\` for \`#1\`). Keep that same ID if you rename a template item, and omit \`id\` only for brand-new steps. Do NOT use Cursor's native \`task\` subagent tool for todo tracking \u2014 \`task\` is for spawning subagents, not the user-facing plan.
|
|
20681
|
-
- **Subagents** \u2014 Use
|
|
20859
|
+
- **Subagents** \u2014 Use your native \`task\` tool to launch a sub-agent for a self-contained piece of work. It is a built-in Cursor tool, NOT the same-named Shipyard \`task\` MCP tool (that one manages sibling tasks \u2014 see below); call the native one directly rather than searching the MCP surface. The native \`task\` has no \`run_in_background\` field (every call is foreground from your perspective) and no cancel API.
|
|
20682
20860
|
- **Edits** \u2014 Use \`Write\` (new files) and \`Edit\` (modify existing files) for all file changes.
|
|
20683
20861
|
- **Permission failures \u2014 do not retry** \u2014 when a tool call is blocked (plan mode, or a denied permission prompt), it fails with a clear message. Report the failure to the user and stop; do NOT loop retrying the same blocked call.
|
|
20684
20862
|
- **Skills** \u2014 Invoke a skill by typing \`/skill-name\` in a message. If a skill fails to load, read its \`SKILL.md\` directly via \`Shell\` (e.g. \`cat ~/.claude/skills/<name>/SKILL.md\`). The Shipyard repo's domain docs (\`docs/architecture.md\`, \`docs/engineering-standards.md\`, \`docs/development.md\`, and \`AGENTS.md\`) are always available via \`Shell\` regardless of skill resolution.
|
|
@@ -21196,6 +21374,22 @@ function matchCollabQueueCorrelationIds(messages, queueEntries) {
|
|
|
21196
21374
|
return claimedIds;
|
|
21197
21375
|
}
|
|
21198
21376
|
|
|
21377
|
+
// src/services/session/claude-tool-guard.ts
|
|
21378
|
+
var SHIPYARD_DENIED_CLAUDE_TOOLS = ["Artifact"];
|
|
21379
|
+
function detectDeniedToolsPresent(initTools, denied = SHIPYARD_DENIED_CLAUDE_TOOLS) {
|
|
21380
|
+
const present = new Set(initTools);
|
|
21381
|
+
return denied.filter((name) => present.has(name));
|
|
21382
|
+
}
|
|
21383
|
+
function partitionInitTools(initTools) {
|
|
21384
|
+
const builtin = [];
|
|
21385
|
+
const mcp = [];
|
|
21386
|
+
for (const name of initTools) {
|
|
21387
|
+
if (name.startsWith("mcp__")) mcp.push(name);
|
|
21388
|
+
else builtin.push(name);
|
|
21389
|
+
}
|
|
21390
|
+
return { builtin, mcp };
|
|
21391
|
+
}
|
|
21392
|
+
|
|
21199
21393
|
// src/services/session/codex-home-isolation.ts
|
|
21200
21394
|
import { join as join26 } from "path";
|
|
21201
21395
|
var ISOLATED_CODEX_HOME_DIR = "codex-isolated";
|
|
@@ -21807,9 +22001,26 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
21807
22001
|
...options.env,
|
|
21808
22002
|
DISABLE_AUTO_COMPACT: "1",
|
|
21809
22003
|
CLAUDE_CODE_ENTRYPOINT: "shipyard",
|
|
22004
|
+
/**
|
|
22005
|
+
* The `shipyard` entrypoint above opts Shipyard OUT of the runtime's
|
|
22006
|
+
* SDK-session default that hides claude.ai Artifacts, so the built-in
|
|
22007
|
+
* `Artifact` tool would otherwise be live and compete with Shipyard's
|
|
22008
|
+
* own `visualize` / `present` / `register_deliverable` surface (#4992).
|
|
22009
|
+
* This is the runtime's dedicated kill switch: it disables the tool,
|
|
22010
|
+
* its prompt guidance, and the coupled claude.ai publish flow. It is
|
|
22011
|
+
* the belt to the `disallowedTools` suspenders below. The runtime does
|
|
22012
|
+
* a raw truthiness check — set `'1'`, never `'0'` to "disable".
|
|
22013
|
+
*/
|
|
22014
|
+
CLAUDE_CODE_DISABLE_ARTIFACT: "1",
|
|
21810
22015
|
...buildAgentSubprocessCodexHomeEnv()
|
|
21811
22016
|
},
|
|
21812
|
-
|
|
22017
|
+
/**
|
|
22018
|
+
* Deny Shipyard-suppressed built-in tools unconditionally at this
|
|
22019
|
+
* chokepoint (not via the caller's option, which `Thread.setMode` may
|
|
22020
|
+
* clear to `undefined`). Union with any caller-supplied denies so this
|
|
22021
|
+
* never clobbers them. See `claude-tool-guard.ts`.
|
|
22022
|
+
*/
|
|
22023
|
+
disallowedTools: [...options.disallowedTools ?? [], ...SHIPYARD_DENIED_CLAUDE_TOOLS],
|
|
21813
22024
|
skills: options.skills,
|
|
21814
22025
|
// eslint-disable-next-line no-restricted-syntax -- SpawnOptions.betas is string[] for loose protocol compat; SDK expects SdkBeta[] which is a branded subset
|
|
21815
22026
|
betas: options.betas,
|
|
@@ -22074,6 +22285,34 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
22074
22285
|
})
|
|
22075
22286
|
});
|
|
22076
22287
|
}
|
|
22288
|
+
/**
|
|
22289
|
+
* Record the built-in tool surface the SDK reported at init, and alarm if a
|
|
22290
|
+
* Shipyard-denied tool leaked through despite the spawn-time deny config
|
|
22291
|
+
* (`claude-tool-guard.ts`). `agent_tool_surface` is emitted once per spawn so
|
|
22292
|
+
* fleet telemetry can spot a silently-added opt-in tool across an SDK bump
|
|
22293
|
+
* without a hardcoded baseline; `agent_denied_tool_leaked` is the regression
|
|
22294
|
+
* canary for our own disable (e.g. the runtime renamed `Artifact`).
|
|
22295
|
+
*/
|
|
22296
|
+
#reportToolSurface(event, log) {
|
|
22297
|
+
if (!log) return;
|
|
22298
|
+
const tools = event.metadata?.tools ?? [];
|
|
22299
|
+
const { builtin, mcp } = partitionInitTools(tools);
|
|
22300
|
+
log({
|
|
22301
|
+
event: "agent_tool_surface",
|
|
22302
|
+
builtinCount: builtin.length,
|
|
22303
|
+
mcpCount: mcp.length,
|
|
22304
|
+
builtinTools: builtin,
|
|
22305
|
+
agentRuntimeVersion: event.metadata?.agentRuntimeVersion
|
|
22306
|
+
});
|
|
22307
|
+
const leaked = detectDeniedToolsPresent(tools);
|
|
22308
|
+
if (leaked.length > 0) {
|
|
22309
|
+
log({
|
|
22310
|
+
event: "agent_denied_tool_leaked",
|
|
22311
|
+
leaked,
|
|
22312
|
+
agentRuntimeVersion: event.metadata?.agentRuntimeVersion
|
|
22313
|
+
});
|
|
22314
|
+
}
|
|
22315
|
+
}
|
|
22077
22316
|
async #runMessageLoop(log) {
|
|
22078
22317
|
const taskFilter = createSubagentTaskFilter();
|
|
22079
22318
|
try {
|
|
@@ -22083,6 +22322,7 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
22083
22322
|
if (event) {
|
|
22084
22323
|
if (event.type === "init_received") {
|
|
22085
22324
|
await this.#awaitMcpSettleForInit(message, log);
|
|
22325
|
+
this.#reportToolSurface(event, log);
|
|
22086
22326
|
}
|
|
22087
22327
|
this.#onEvent(event);
|
|
22088
22328
|
}
|
|
@@ -22482,7 +22722,7 @@ async function runFastSliceAgentBroadcast(deps) {
|
|
|
22482
22722
|
if (deps.isDisposed?.()) return;
|
|
22483
22723
|
const current = deps.capabilitiesRef.current;
|
|
22484
22724
|
if (!current) return;
|
|
22485
|
-
deps.
|
|
22725
|
+
deps.commit({
|
|
22486
22726
|
...current,
|
|
22487
22727
|
installedAgents: agents,
|
|
22488
22728
|
installedRuntimes: agents,
|
|
@@ -22494,7 +22734,7 @@ async function runFastSliceAgentBroadcast(deps) {
|
|
|
22494
22734
|
* must not also assume other slices are populated until the second broadcast.
|
|
22495
22735
|
*/
|
|
22496
22736
|
detectionComplete: true
|
|
22497
|
-
};
|
|
22737
|
+
});
|
|
22498
22738
|
deps.broadcast();
|
|
22499
22739
|
}
|
|
22500
22740
|
function decidePreferredAuthBackfill(args) {
|
|
@@ -22635,7 +22875,7 @@ function rehydrateVizRegistry(registry, vizWatcher, resolvedTaskId, vizDir, log)
|
|
|
22635
22875
|
const registryPath = join28(vizDir, resolvedTaskId, "registry.json");
|
|
22636
22876
|
if (!existsSync3(registryPath)) return Promise.resolve();
|
|
22637
22877
|
try {
|
|
22638
|
-
const raw =
|
|
22878
|
+
const raw = readFileSync5(registryPath, "utf-8");
|
|
22639
22879
|
const parsed = PersistedVisualizationRegistrySchema.safeParse(JSON.parse(raw));
|
|
22640
22880
|
if (parsed.success) {
|
|
22641
22881
|
const effects = registry.rehydrate(resolvedTaskId, parsed.data);
|
|
@@ -23924,6 +24164,7 @@ import { randomUUID as randomUUID9 } from "crypto";
|
|
|
23924
24164
|
var NOTIFICATIONS_PER_TASK_CAP = 50;
|
|
23925
24165
|
var CREATE_THEN_RESOLVE_WINDOW_MS = 500;
|
|
23926
24166
|
var DEFAULT_GC_HORIZON_MS = 24 * 60 * 60 * 1e3;
|
|
24167
|
+
var EVENT_AGE_OUT_HORIZON_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
23927
24168
|
var STATEFUL_NOTIFICATION_KINDS = /* @__PURE__ */ new Set([
|
|
23928
24169
|
"permission",
|
|
23929
24170
|
"plan_review",
|
|
@@ -24128,19 +24369,28 @@ var NotificationsHandler = class {
|
|
|
24128
24369
|
* the dedup window) are NOT replayed — they aren't durable yet.
|
|
24129
24370
|
*
|
|
24130
24371
|
* `viewerUserId` is accepted for symmetry with the handler signature
|
|
24131
|
-
* documented in the plan, but per-user `targetUserIds
|
|
24132
|
-
* applied browser-side.
|
|
24133
|
-
*
|
|
24372
|
+
* documented in the plan, but per-user `targetUserIds`/dismiss filtering is
|
|
24373
|
+
* applied browser-side. Aged pure events are dropped here (viewer-independent)
|
|
24374
|
+
* so this per-task path agrees with `buildInboxSnapshot`'s read-path age
|
|
24375
|
+
* filter — otherwise opening the active task on a long-uptime daemon would
|
|
24376
|
+
* re-surface events the cross-task `inbox_snapshot` just aged out.
|
|
24134
24377
|
*/
|
|
24135
24378
|
replayNotificationsToPeer(send, _viewerUserId) {
|
|
24136
24379
|
this.#runWhenReady(() => {
|
|
24137
24380
|
const record = this.#deps.taskStateStore.getTaskSync(this.#deps.taskId);
|
|
24138
24381
|
if (record?.archivedAt != null) return;
|
|
24139
|
-
|
|
24382
|
+
const now = Date.now();
|
|
24383
|
+
const notifications = {};
|
|
24384
|
+
for (const [id, n] of Object.entries(this.#notifications)) {
|
|
24385
|
+
if (n.resolvedAt == null && isPureEventPastHorizon(n, now, EVENT_AGE_OUT_HORIZON_MS))
|
|
24386
|
+
continue;
|
|
24387
|
+
notifications[id] = n;
|
|
24388
|
+
}
|
|
24389
|
+
if (Object.keys(notifications).length === 0) return;
|
|
24140
24390
|
send({
|
|
24141
24391
|
type: "notification_snapshot",
|
|
24142
24392
|
taskId: this.#deps.taskId,
|
|
24143
|
-
notifications
|
|
24393
|
+
notifications
|
|
24144
24394
|
});
|
|
24145
24395
|
});
|
|
24146
24396
|
}
|
|
@@ -24416,6 +24666,19 @@ function passReplaceByKind(next, bootTimestamp) {
|
|
|
24416
24666
|
}
|
|
24417
24667
|
return retired;
|
|
24418
24668
|
}
|
|
24669
|
+
function passRetireAgedEvents(next, bootTimestamp, eventHorizonMs) {
|
|
24670
|
+
let retired = 0;
|
|
24671
|
+
for (const [id, n] of Object.entries(next)) {
|
|
24672
|
+
if (n.resolvedAt != null) continue;
|
|
24673
|
+
if (!isPureEventPastHorizon(n, bootTimestamp, eventHorizonMs)) continue;
|
|
24674
|
+
next[id] = { ...n, resolvedAt: bootTimestamp, resolvedBy: "aged_out" };
|
|
24675
|
+
retired++;
|
|
24676
|
+
}
|
|
24677
|
+
return retired;
|
|
24678
|
+
}
|
|
24679
|
+
function isPureEventPastHorizon(n, now, horizonMs) {
|
|
24680
|
+
return notificationDedupKey(n) === null && now - n.createdAt >= horizonMs;
|
|
24681
|
+
}
|
|
24419
24682
|
function passPruneResolved(next, bootTimestamp, pruneHorizonMs) {
|
|
24420
24683
|
const pruneCutoff = bootTimestamp - pruneHorizonMs;
|
|
24421
24684
|
const after = {};
|
|
@@ -24429,31 +24692,46 @@ function passPruneResolved(next, bootTimestamp, pruneHorizonMs) {
|
|
|
24429
24692
|
}
|
|
24430
24693
|
return { after, pruned };
|
|
24431
24694
|
}
|
|
24432
|
-
function sweepNotificationsForTask(notifications, bootTimestamp, pruneHorizonMs = DEFAULT_GC_HORIZON_MS) {
|
|
24695
|
+
function sweepNotificationsForTask(notifications, bootTimestamp, pruneHorizonMs = DEFAULT_GC_HORIZON_MS, eventHorizonMs = EVENT_AGE_OUT_HORIZON_MS) {
|
|
24433
24696
|
const working = { ...notifications };
|
|
24434
24697
|
const retired1 = passRetireStatefulOrphans(working, bootTimestamp);
|
|
24435
24698
|
const retired2 = passReplaceByKind(working, bootTimestamp);
|
|
24699
|
+
const retired3 = passRetireAgedEvents(working, bootTimestamp, eventHorizonMs);
|
|
24436
24700
|
const { after, pruned } = passPruneResolved(working, bootTimestamp, pruneHorizonMs);
|
|
24437
|
-
const retired = retired1 + retired2;
|
|
24701
|
+
const retired = retired1 + retired2 + retired3;
|
|
24438
24702
|
const changed = retired > 0 || pruned > 0;
|
|
24439
24703
|
return { next: changed ? after : null, retired, pruned };
|
|
24440
24704
|
}
|
|
24441
|
-
function
|
|
24705
|
+
function isInboxVisible(n, ownerUserId, acknowledgedAt, now) {
|
|
24706
|
+
if (n.resolvedAt != null) return false;
|
|
24707
|
+
if (n.ackByUser[ownerUserId]?.dismissedAt != null) return false;
|
|
24708
|
+
if (acknowledgedAt != null && acknowledgedAt >= n.createdAt) return false;
|
|
24709
|
+
if (isPureEventPastHorizon(n, now, EVENT_AGE_OUT_HORIZON_MS)) return false;
|
|
24710
|
+
return true;
|
|
24711
|
+
}
|
|
24712
|
+
function selectInboxNotificationsForTask(record, ownerUserId, now) {
|
|
24713
|
+
if (record.archivedAt != null) return null;
|
|
24714
|
+
const notifications = record.notifications;
|
|
24715
|
+
if (!notifications) return null;
|
|
24716
|
+
const acknowledgedAt = record.acknowledgedAt ?? null;
|
|
24717
|
+
let visible = null;
|
|
24718
|
+
for (const [id, n] of Object.entries(notifications)) {
|
|
24719
|
+
if (!isInboxVisible(n, ownerUserId, acknowledgedAt, now)) continue;
|
|
24720
|
+
if (visible === null) visible = {};
|
|
24721
|
+
visible[id] = n;
|
|
24722
|
+
}
|
|
24723
|
+
return visible;
|
|
24724
|
+
}
|
|
24725
|
+
function buildInboxSnapshot(tasks, ownerUserId) {
|
|
24726
|
+
const now = Date.now();
|
|
24442
24727
|
const out = {};
|
|
24443
24728
|
for (const [taskId, record] of Object.entries(tasks)) {
|
|
24444
|
-
const
|
|
24445
|
-
if (
|
|
24446
|
-
let unresolved = null;
|
|
24447
|
-
for (const [id, n] of Object.entries(notifications)) {
|
|
24448
|
-
if (n.resolvedAt != null) continue;
|
|
24449
|
-
if (unresolved === null) unresolved = {};
|
|
24450
|
-
unresolved[id] = n;
|
|
24451
|
-
}
|
|
24452
|
-
if (unresolved !== null) out[taskId] = unresolved;
|
|
24729
|
+
const visible = selectInboxNotificationsForTask(record, ownerUserId, now);
|
|
24730
|
+
if (visible !== null) out[taskId] = visible;
|
|
24453
24731
|
}
|
|
24454
24732
|
return out;
|
|
24455
24733
|
}
|
|
24456
|
-
async function sweepOrphanedNotifications(taskStateStore, log) {
|
|
24734
|
+
async function sweepOrphanedNotifications(taskStateStore, log, eventHorizonMs = EVENT_AGE_OUT_HORIZON_MS) {
|
|
24457
24735
|
const bootTimestamp = Date.now();
|
|
24458
24736
|
let tasks;
|
|
24459
24737
|
try {
|
|
@@ -24471,7 +24749,12 @@ async function sweepOrphanedNotifications(taskStateStore, log) {
|
|
|
24471
24749
|
for (const [taskId, record] of Object.entries(tasks)) {
|
|
24472
24750
|
const notifications = record.notifications;
|
|
24473
24751
|
if (!notifications || Object.keys(notifications).length === 0) continue;
|
|
24474
|
-
const { next, retired, pruned } = sweepNotificationsForTask(
|
|
24752
|
+
const { next, retired, pruned } = sweepNotificationsForTask(
|
|
24753
|
+
notifications,
|
|
24754
|
+
bootTimestamp,
|
|
24755
|
+
DEFAULT_GC_HORIZON_MS,
|
|
24756
|
+
eventHorizonMs
|
|
24757
|
+
);
|
|
24475
24758
|
if (!next) continue;
|
|
24476
24759
|
sweptTasks++;
|
|
24477
24760
|
sweptNotifications += retired;
|
|
@@ -25034,7 +25317,7 @@ function buildTaskCallbacks(deps) {
|
|
|
25034
25317
|
daemon.taskStateStore.listTasks().then((all) => {
|
|
25035
25318
|
getControlHandler().sendControl({
|
|
25036
25319
|
type: "inbox_snapshot",
|
|
25037
|
-
notificationsByTask: buildInboxSnapshot(all)
|
|
25320
|
+
notificationsByTask: buildInboxSnapshot(all, daemon.userId)
|
|
25038
25321
|
});
|
|
25039
25322
|
}).catch((err3) => {
|
|
25040
25323
|
logAdapter({
|
|
@@ -30383,7 +30666,7 @@ function isNodeBuildReleaseDir(dir) {
|
|
|
30383
30666
|
}
|
|
30384
30667
|
|
|
30385
30668
|
// src/services/port-detection.ts
|
|
30386
|
-
import { existsSync as existsSync6, readFileSync as
|
|
30669
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
30387
30670
|
import { dirname as dirname19, join as join35 } from "path";
|
|
30388
30671
|
var LSOF_TIMEOUT_MS = 8e3;
|
|
30389
30672
|
var LSOF_TTL_MS = 5e3;
|
|
@@ -30570,7 +30853,7 @@ function resolveProjectRoot(cwd) {
|
|
|
30570
30853
|
if (existsSync6(candidate)) {
|
|
30571
30854
|
let packageName;
|
|
30572
30855
|
try {
|
|
30573
|
-
const raw =
|
|
30856
|
+
const raw = readFileSync6(candidate, "utf8");
|
|
30574
30857
|
const parsed = JSON.parse(raw);
|
|
30575
30858
|
if (parsed !== null && typeof parsed === "object" && hasStringName(parsed)) {
|
|
30576
30859
|
packageName = parsed.name;
|
|
@@ -30685,7 +30968,7 @@ import { randomUUID as randomUUID10 } from "crypto";
|
|
|
30685
30968
|
import {
|
|
30686
30969
|
mkdirSync as mkdirSync3,
|
|
30687
30970
|
readdirSync as readdirSync4,
|
|
30688
|
-
readFileSync as
|
|
30971
|
+
readFileSync as readFileSync7,
|
|
30689
30972
|
renameSync,
|
|
30690
30973
|
unlinkSync as unlinkSync2,
|
|
30691
30974
|
writeFileSync as writeFileSync2
|
|
@@ -30705,7 +30988,7 @@ function recordFilePath2(rootDir, taskId, elementId) {
|
|
|
30705
30988
|
function parseRecord2(filePath, logger2) {
|
|
30706
30989
|
let raw;
|
|
30707
30990
|
try {
|
|
30708
|
-
raw =
|
|
30991
|
+
raw = readFileSync7(filePath, "utf-8");
|
|
30709
30992
|
} catch {
|
|
30710
30993
|
return null;
|
|
30711
30994
|
}
|
|
@@ -37899,10 +38182,10 @@ function isWakeableSession(state) {
|
|
|
37899
38182
|
}
|
|
37900
38183
|
}
|
|
37901
38184
|
function buildWakeMessage(prNumber, ciState) {
|
|
37902
|
-
|
|
37903
|
-
|
|
37904
|
-
|
|
37905
|
-
|
|
38185
|
+
const body = ciState === "failure" ? `CI checks failed on your open pull request #${prNumber}. Review the failing checks in the <branch> resource, fix them, and push to the same PR.` : `CI checks passed on your open pull request #${prNumber}. Review the <branch> resource and continue your task \u2014 merge it if it is ready, or address any remaining work.`;
|
|
38186
|
+
return `<system-reminder>
|
|
38187
|
+
${body}
|
|
38188
|
+
</system-reminder>`;
|
|
37906
38189
|
}
|
|
37907
38190
|
function maybeWakeAgentOnPrCheck(deps, taskId, prev, next) {
|
|
37908
38191
|
const decision = decidePrCheckWake({
|
|
@@ -37925,7 +38208,17 @@ function buildPrCheckWakeDeps(taskManager, log) {
|
|
|
37925
38208
|
return {
|
|
37926
38209
|
getSessionState: (taskId) => taskManager.getTaskState(taskId)?.sessionState ?? null,
|
|
37927
38210
|
wake: (taskId, message) => {
|
|
37928
|
-
void taskManager.handleUserMessage(
|
|
38211
|
+
void taskManager.handleUserMessage(
|
|
38212
|
+
taskId,
|
|
38213
|
+
[{ type: "text", text: message }],
|
|
38214
|
+
void 0,
|
|
38215
|
+
void 0,
|
|
38216
|
+
void 0,
|
|
38217
|
+
void 0,
|
|
38218
|
+
void 0,
|
|
38219
|
+
"human",
|
|
38220
|
+
true
|
|
38221
|
+
).catch((err3) => {
|
|
37929
38222
|
log({
|
|
37930
38223
|
event: "pr_check_wake_failed",
|
|
37931
38224
|
taskId,
|
|
@@ -39580,6 +39873,56 @@ function createDeliverableTools(ctx) {
|
|
|
39580
39873
|
];
|
|
39581
39874
|
}
|
|
39582
39875
|
|
|
39876
|
+
// src/services/harness/open-file-server.ts
|
|
39877
|
+
var OPEN_FILE_DESCRIPTION = [
|
|
39878
|
+
"Open a file in Shipyard's code-editor side panel.",
|
|
39879
|
+
"",
|
|
39880
|
+
"Use it to surface a file in the UI at the moment it matters \u2014 right after you edit it, when your reply references specific code, or when you point the reviewer at what to look at next. Opens the panel if closed, selects the file, and (with `line`) scrolls to that line.",
|
|
39881
|
+
"",
|
|
39882
|
+
"WHEN: after an edit the reviewer should see, when your answer references a file, or when directing attention to a specific spot.",
|
|
39883
|
+
'WHEN NOT: for a file the reviewer is already looking at, or as a substitute for explaining a change \u2014 use `add_comment` for the "why" on a diff hunk.',
|
|
39884
|
+
"",
|
|
39885
|
+
"Params:",
|
|
39886
|
+
" path \u2014 the file to open. Absolute or workspace-relative; the browser resolves it against the task working directory.",
|
|
39887
|
+
" line \u2014 optional 1-based line to scroll to and place the cursor on.",
|
|
39888
|
+
"",
|
|
39889
|
+
"EXAMPLES:",
|
|
39890
|
+
' {"path": "src/db.ts"}',
|
|
39891
|
+
' {"path": "src/index.ts", "line": 42}'
|
|
39892
|
+
].join("\n");
|
|
39893
|
+
var OpenFileInput = {
|
|
39894
|
+
path: external_exports.string().trim().min(1, "path cannot be empty").describe(
|
|
39895
|
+
"File to open, absolute or workspace-relative. Resolved against the task working directory in the browser."
|
|
39896
|
+
),
|
|
39897
|
+
line: external_exports.number().int().positive().optional().describe("Optional 1-based line number to scroll to and place the cursor on.")
|
|
39898
|
+
};
|
|
39899
|
+
var OpenFileSchema = external_exports.object(OpenFileInput);
|
|
39900
|
+
function createOpenFileTools(ctx) {
|
|
39901
|
+
return [
|
|
39902
|
+
{
|
|
39903
|
+
name: OPEN_FILE_TOOL,
|
|
39904
|
+
description: OPEN_FILE_DESCRIPTION,
|
|
39905
|
+
inputSchema: OpenFileInput,
|
|
39906
|
+
handler: async (input) => {
|
|
39907
|
+
const parsed = OpenFileSchema.safeParse(input);
|
|
39908
|
+
if (!parsed.success) {
|
|
39909
|
+
const reason = parsed.error.issues.map((i) => i.message).join("; ");
|
|
39910
|
+
return {
|
|
39911
|
+
content: [{ type: "text", text: `Invalid open_file input: ${reason}` }],
|
|
39912
|
+
isError: true
|
|
39913
|
+
};
|
|
39914
|
+
}
|
|
39915
|
+
const { path: path5, line } = parsed.data;
|
|
39916
|
+
ctx.broadcastControl({ type: "open_file", taskId: ctx.taskId.current, path: path5, line });
|
|
39917
|
+
const where = line !== void 0 ? ` at line ${line}` : "";
|
|
39918
|
+
return {
|
|
39919
|
+
content: [{ type: "text", text: `Opened ${path5}${where} in the file viewer.` }]
|
|
39920
|
+
};
|
|
39921
|
+
}
|
|
39922
|
+
}
|
|
39923
|
+
];
|
|
39924
|
+
}
|
|
39925
|
+
|
|
39583
39926
|
// src/services/harness/plugin-server.ts
|
|
39584
39927
|
import { mkdir as mkdir19, writeFile as writeFile19 } from "fs/promises";
|
|
39585
39928
|
import { join as join52 } from "path";
|
|
@@ -40433,6 +40776,12 @@ var CreateTaskInputSchema = external_exports.object({
|
|
|
40433
40776
|
"Must be a model id registered with this daemon (see the `models` capability snapshot). The daemon validates the id against the registered profile catalog at handler time and refuses spawn if the model is unknown.",
|
|
40434
40777
|
"The spawned task always runs on the agent that owns this model \u2014 pre-warm adoption respects the target agent."
|
|
40435
40778
|
].join("\n")
|
|
40779
|
+
),
|
|
40780
|
+
mcpServers: external_exports.array(external_exports.string()).optional().describe(
|
|
40781
|
+
[
|
|
40782
|
+
"ALLOWLIST of MCP server names the spawned task may use. Omit to inherit the parent task's MCP set (the default). When set, ONLY the named servers stay enabled for the spawned task \u2014 all other configured servers are filtered out, so a spawned sub-task does not inherit the full workspace roster.",
|
|
40783
|
+
"Pass [] to spawn with no MCP servers at all. Names must match the daemon's configured MCP set (see the `mcpServers` capability snapshot); unknown names are reported and the spawn is refused so you can correct."
|
|
40784
|
+
].join("\n")
|
|
40436
40785
|
)
|
|
40437
40786
|
});
|
|
40438
40787
|
var TASK_TOOL_DESCRIPTION = [
|
|
@@ -40518,6 +40867,18 @@ function isTerminalTaskStatus(status) {
|
|
|
40518
40867
|
return TERMINAL_TASK_STATUS_SET.has(status);
|
|
40519
40868
|
}
|
|
40520
40869
|
var TASK_SNAPSHOT_URI_PREFIX = "shipyard://task-snapshot/";
|
|
40870
|
+
function planMcpAllowlist(allowlist, knownNames) {
|
|
40871
|
+
if (allowlist === void 0) return { kind: "inherit" };
|
|
40872
|
+
const known = knownNames ? [...knownNames] : [];
|
|
40873
|
+
const knownSet = new Set(known);
|
|
40874
|
+
const unknown = [...new Set(allowlist.filter((name) => !knownSet.has(name)))];
|
|
40875
|
+
if (unknown.length > 0) return { kind: "unknown", unknown, known };
|
|
40876
|
+
return { kind: "filter", mcpServerSnapshot: [...new Set(allowlist)], disabledMcpServers: [] };
|
|
40877
|
+
}
|
|
40878
|
+
function unknownMcpServersMessage(unknown, known) {
|
|
40879
|
+
const suggestion = known.length === 0 ? "This daemon has no MCP servers configured \u2014 omit `mcpServers` to inherit the parent set, or pass [] to spawn with none." : `Configured MCP servers: ${[...known].sort((a, b2) => a.localeCompare(b2)).join(", ")}.`;
|
|
40880
|
+
return `Unknown MCP server(s) in the mcpServers allowlist: ${unknown.join(", ")}. ${suggestion}`;
|
|
40881
|
+
}
|
|
40521
40882
|
async function handleCreateTask(input, ctx) {
|
|
40522
40883
|
const parsed = CreateTaskInputSchema.safeParse(input);
|
|
40523
40884
|
if (!parsed.success) {
|
|
@@ -40527,9 +40888,17 @@ async function handleCreateTask(input, ctx) {
|
|
|
40527
40888
|
const callingTaskId = ctx.callingTaskId.current;
|
|
40528
40889
|
const inputError = validateCreateTaskInputs(args, ctx);
|
|
40529
40890
|
if (inputError !== null) return inputError;
|
|
40891
|
+
const mcpPlan = planMcpAllowlist(
|
|
40892
|
+
args.mcpServers,
|
|
40893
|
+
args.mcpServers === void 0 ? void 0 : ctx.getKnownMcpServerNames()
|
|
40894
|
+
);
|
|
40895
|
+
if (mcpPlan.kind === "unknown") {
|
|
40896
|
+
return errorResult(unknownMcpServersMessage(mcpPlan.unknown, mcpPlan.known));
|
|
40897
|
+
}
|
|
40530
40898
|
const inheritedDefaults = await resolveInheritedTaskDefaults(
|
|
40531
40899
|
args.permissionMode,
|
|
40532
40900
|
args.model,
|
|
40901
|
+
mcpPlan,
|
|
40533
40902
|
callingTaskId,
|
|
40534
40903
|
ctx
|
|
40535
40904
|
);
|
|
@@ -40836,7 +41205,7 @@ async function spawnWithFreshWorktree(args, callingTaskId, ctx, inheritedDefault
|
|
|
40836
41205
|
}
|
|
40837
41206
|
return successResult(taskId, args.title, branchName, "create");
|
|
40838
41207
|
}
|
|
40839
|
-
async function resolveInheritedTaskDefaults(permissionModeOverride, modelOverride, callingTaskId, ctx) {
|
|
41208
|
+
async function resolveInheritedTaskDefaults(permissionModeOverride, modelOverride, mcpPlan, callingTaskId, ctx) {
|
|
40840
41209
|
const parent = await ctx.taskStateStore.getTask(callingTaskId);
|
|
40841
41210
|
const parentSettings = parent?.composerSettings ?? {};
|
|
40842
41211
|
const merged = { ...parentSettings };
|
|
@@ -40846,6 +41215,10 @@ async function resolveInheritedTaskDefaults(permissionModeOverride, modelOverrid
|
|
|
40846
41215
|
if (modelOverride !== void 0) {
|
|
40847
41216
|
merged.model = modelOverride;
|
|
40848
41217
|
}
|
|
41218
|
+
if (mcpPlan.kind === "filter") {
|
|
41219
|
+
merged.disabledMcpServers = mcpPlan.disabledMcpServers;
|
|
41220
|
+
merged.mcpServerSnapshot = mcpPlan.mcpServerSnapshot;
|
|
41221
|
+
}
|
|
40849
41222
|
return {
|
|
40850
41223
|
initialComposerSettings: Object.keys(merged).length === 0 ? void 0 : merged,
|
|
40851
41224
|
projectId: parent?.projectId ?? null
|
|
@@ -50252,6 +50625,7 @@ var LSP_TOOL_NAMES = [
|
|
|
50252
50625
|
"lsp_find_references",
|
|
50253
50626
|
"lsp_rename_symbol"
|
|
50254
50627
|
];
|
|
50628
|
+
var OPEN_FILE_TOOL_NAMES = ["open_file"];
|
|
50255
50629
|
function buildHarnessTools(ctx) {
|
|
50256
50630
|
const taskIdRef = { current: ctx.taskId };
|
|
50257
50631
|
const epochRef = { current: null };
|
|
@@ -50305,6 +50679,7 @@ function buildHarnessTools(ctx) {
|
|
|
50305
50679
|
defaultRepoPath: ctx.projectRoot,
|
|
50306
50680
|
isKnownModel: ctx.isKnownModel,
|
|
50307
50681
|
getModels: ctx.getModels,
|
|
50682
|
+
getKnownMcpServerNames: ctx.getKnownMcpServerNames,
|
|
50308
50683
|
resolveSnapshot: ctx.resolveSnapshot
|
|
50309
50684
|
};
|
|
50310
50685
|
const lspAgentCtx = {
|
|
@@ -50312,6 +50687,10 @@ function buildHarnessTools(ctx) {
|
|
|
50312
50687
|
log: ctx.log,
|
|
50313
50688
|
getClient: ctx.getLspClient
|
|
50314
50689
|
};
|
|
50690
|
+
const openFileCtx = {
|
|
50691
|
+
taskId: taskIdRef,
|
|
50692
|
+
broadcastControl: ctx.broadcastControl
|
|
50693
|
+
};
|
|
50315
50694
|
const skillsCtx = {
|
|
50316
50695
|
taskId: taskIdRef,
|
|
50317
50696
|
agentSystem: ctx.agentSystem,
|
|
@@ -50335,6 +50714,7 @@ function buildHarnessTools(ctx) {
|
|
|
50335
50714
|
registerFactory(TASK_CONTROL_TOOL_NAMES, () => createTaskControlTools(taskControlCtx));
|
|
50336
50715
|
registerFactory(DELIVERABLE_TOOL_NAMES, () => createDeliverableTools(deliverableCtx));
|
|
50337
50716
|
registerFactory(LSP_TOOL_NAMES, () => createLspAgentTools(lspAgentCtx));
|
|
50717
|
+
registerFactory(OPEN_FILE_TOOL_NAMES, () => createOpenFileTools(openFileCtx));
|
|
50338
50718
|
const tools = [];
|
|
50339
50719
|
for (const spec of HARNESS_TOOL_REGISTRY) {
|
|
50340
50720
|
if (!passes(spec)) continue;
|
|
@@ -50398,12 +50778,12 @@ function createHarnessMcpServer(ctx) {
|
|
|
50398
50778
|
|
|
50399
50779
|
// src/services/harness/lsp-agent-client.ts
|
|
50400
50780
|
import { spawn as spawn10 } from "child_process";
|
|
50401
|
-
import { readFileSync as
|
|
50781
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
50402
50782
|
import { extname as extname3 } from "path";
|
|
50403
50783
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
50404
50784
|
|
|
50405
50785
|
// src/services/lsp/language-server-registry.ts
|
|
50406
|
-
import { existsSync as existsSync7, readFileSync as
|
|
50786
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
50407
50787
|
import { createRequire as createRequire3 } from "module";
|
|
50408
50788
|
import { dirname as dirname22, isAbsolute as isAbsolute4, join as join53 } from "path";
|
|
50409
50789
|
function decideTypescriptServer(d) {
|
|
@@ -50532,7 +50912,7 @@ function createRegistryDeps(requireFrom) {
|
|
|
50532
50912
|
fileExists: (path5) => existsSync7(path5),
|
|
50533
50913
|
readJsonFile: (path5) => {
|
|
50534
50914
|
try {
|
|
50535
|
-
return parse2(
|
|
50915
|
+
return parse2(readFileSync8(path5, "utf-8"));
|
|
50536
50916
|
} catch {
|
|
50537
50917
|
return void 0;
|
|
50538
50918
|
}
|
|
@@ -50560,7 +50940,7 @@ function resolveTsgoBinaryPath(req) {
|
|
|
50560
50940
|
}
|
|
50561
50941
|
function resolveBinViaPackageJson(pkg, binName, req) {
|
|
50562
50942
|
const pkgJsonPath = req.resolve(`${pkg}/package.json`);
|
|
50563
|
-
const manifest = parse2(
|
|
50943
|
+
const manifest = parse2(readFileSync8(pkgJsonPath, "utf-8"));
|
|
50564
50944
|
const binField = isRecord7(manifest) ? manifest.bin : void 0;
|
|
50565
50945
|
const named = isRecord7(binField) ? binField[binName] : void 0;
|
|
50566
50946
|
const bin = typeof binField === "string" ? binField : typeof named === "string" ? named : void 0;
|
|
@@ -50716,7 +51096,7 @@ var LspAgentClient = class {
|
|
|
50716
51096
|
if (this.#disposed) return;
|
|
50717
51097
|
this.#armIdleTimer();
|
|
50718
51098
|
const uri = pathToFileURL3(absolutePath).href;
|
|
50719
|
-
const text =
|
|
51099
|
+
const text = readFileSync9(absolutePath, "utf8");
|
|
50720
51100
|
const prevVersion = this.#openedVersions.get(uri);
|
|
50721
51101
|
if (prevVersion === void 0) {
|
|
50722
51102
|
this.#notify("textDocument/didOpen", {
|
|
@@ -59983,6 +60363,13 @@ var ScheduleEvaluator = class {
|
|
|
59983
60363
|
}
|
|
59984
60364
|
};
|
|
59985
60365
|
|
|
60366
|
+
// src/services/serve-factory/capability-writer.ts
|
|
60367
|
+
function createCapabilityWriter(capabilitiesRef) {
|
|
60368
|
+
return (next) => {
|
|
60369
|
+
capabilitiesRef.current = next;
|
|
60370
|
+
};
|
|
60371
|
+
}
|
|
60372
|
+
|
|
59986
60373
|
// ../../packages/session/src/codex-app-server/shared-protocol-types.ts
|
|
59987
60374
|
var ReasoningEffortSchema = external_exports2.string();
|
|
59988
60375
|
var TurnStatusSchema = external_exports2.enum(["completed", "interrupted", "failed", "inProgress"]);
|
|
@@ -63107,6 +63494,29 @@ function buildCodexPlanUpdateHandler(deps) {
|
|
|
63107
63494
|
};
|
|
63108
63495
|
}
|
|
63109
63496
|
|
|
63497
|
+
// src/services/session/codex-resume-classifier.ts
|
|
63498
|
+
function isCodexArchiveThreadNotFound(err3) {
|
|
63499
|
+
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
63500
|
+
return err3 instanceof JsonRpcRequestError && err3.method === "thread/archive" && /(?:no rollout found for thread id|ThreadNotFound|thread not found|no rollout|rollout.*not.*found)/i.test(
|
|
63501
|
+
message
|
|
63502
|
+
);
|
|
63503
|
+
}
|
|
63504
|
+
function isCodexResumeThreadNotFound(err3) {
|
|
63505
|
+
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
63506
|
+
return err3 instanceof JsonRpcRequestError && err3.method === "thread/resume" && /(?:no rollout found for thread id|ThreadNotFound|thread not found|no rollout|rollout.*not.*found)/i.test(
|
|
63507
|
+
message
|
|
63508
|
+
);
|
|
63509
|
+
}
|
|
63510
|
+
function isCodexResumeThreadArchived(err3) {
|
|
63511
|
+
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
63512
|
+
return err3 instanceof JsonRpcRequestError && err3.method === "thread/resume" && /archived/i.test(message);
|
|
63513
|
+
}
|
|
63514
|
+
function classifyResumeFallback(err3) {
|
|
63515
|
+
if (isCodexResumeThreadArchived(err3)) return { reason: "archived" };
|
|
63516
|
+
if (isCodexResumeThreadNotFound(err3)) return { reason: "no_rollout" };
|
|
63517
|
+
return null;
|
|
63518
|
+
}
|
|
63519
|
+
|
|
63110
63520
|
// src/services/session/codex-turn-event-dispatch.ts
|
|
63111
63521
|
function maybeEmitBlockBoundary(nextType, cell, onEvent) {
|
|
63112
63522
|
if (!cell) return;
|
|
@@ -63123,6 +63533,7 @@ function maybeEmitBlockBoundary(nextType, cell, onEvent) {
|
|
|
63123
63533
|
cell.current = nextType;
|
|
63124
63534
|
}
|
|
63125
63535
|
function dispatchCodexTurnEvent(frame, deps) {
|
|
63536
|
+
deps.onLiveness();
|
|
63126
63537
|
switch (frame.method) {
|
|
63127
63538
|
case CodexNotifications.itemStarted:
|
|
63128
63539
|
case CodexNotifications.itemCompleted:
|
|
@@ -64259,8 +64670,13 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
64259
64670
|
try {
|
|
64260
64671
|
return await client.resumeThread(opts.resume, threadParams);
|
|
64261
64672
|
} catch (err3) {
|
|
64262
|
-
|
|
64263
|
-
|
|
64673
|
+
const fallback = classifyResumeFallback(err3);
|
|
64674
|
+
if (fallback === null) throw err3;
|
|
64675
|
+
opts.log({
|
|
64676
|
+
event: "codex_resume_no_rollout_fallback",
|
|
64677
|
+
threadId: opts.resume,
|
|
64678
|
+
reason: fallback.reason
|
|
64679
|
+
});
|
|
64264
64680
|
if (opts.onResumeNoRollout !== void 0) {
|
|
64265
64681
|
try {
|
|
64266
64682
|
await opts.onResumeNoRollout(opts.resume);
|
|
@@ -64429,8 +64845,13 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
64429
64845
|
try {
|
|
64430
64846
|
startResponse = await client.resumeThread(priorThreadId, startParams);
|
|
64431
64847
|
} catch (err3) {
|
|
64432
|
-
|
|
64433
|
-
|
|
64848
|
+
const fallback = classifyResumeFallback(err3);
|
|
64849
|
+
if (fallback === null) throw err3;
|
|
64850
|
+
this.#log({
|
|
64851
|
+
event: "codex_engine_resume_no_rollout_fallback",
|
|
64852
|
+
threadId: priorThreadId,
|
|
64853
|
+
reason: fallback.reason
|
|
64854
|
+
});
|
|
64434
64855
|
startResponse = await client.startThread(startParams);
|
|
64435
64856
|
}
|
|
64436
64857
|
} else {
|
|
@@ -64628,6 +65049,13 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
64628
65049
|
dispatchCodexTurnEvent(event, {
|
|
64629
65050
|
translatorCtx: this.#translatorCtx,
|
|
64630
65051
|
onEvent: this.#onEvent,
|
|
65052
|
+
/**
|
|
65053
|
+
* Stall-watchdog liveness (#4921): every turn frame is proof of life.
|
|
65054
|
+
* `stream_activity` resets the (agent-neutral) session-manager watchdog;
|
|
65055
|
+
* the manager throttles high-rate deltas and no-ops when the watchdog is
|
|
65056
|
+
* disabled, so this is safe to fire per frame.
|
|
65057
|
+
*/
|
|
65058
|
+
onLiveness: () => this.#onEvent({ type: "stream_activity" }),
|
|
64631
65059
|
onTokenUsage: (payload) => {
|
|
64632
65060
|
this.#lastTurnTokenUsage = payload.tokenUsage;
|
|
64633
65061
|
},
|
|
@@ -64919,7 +65347,7 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
64919
65347
|
*/
|
|
64920
65348
|
async rollback(numTurns) {
|
|
64921
65349
|
const threadId = this.#threadId;
|
|
64922
|
-
if (!threadId || this.#closed) return;
|
|
65350
|
+
if (!threadId || this.#closed || numTurns < 1) return;
|
|
64923
65351
|
await this.#client.rollback(threadId, { numTurns });
|
|
64924
65352
|
}
|
|
64925
65353
|
/**
|
|
@@ -65211,18 +65639,6 @@ var CodexAgentSubprocess = class _CodexAgentSubprocess {
|
|
|
65211
65639
|
function toCodexWireEffort(effort) {
|
|
65212
65640
|
return effort;
|
|
65213
65641
|
}
|
|
65214
|
-
function isCodexArchiveThreadNotFound(err3) {
|
|
65215
|
-
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
65216
|
-
return err3 instanceof JsonRpcRequestError && err3.method === "thread/archive" && /(?:no rollout found for thread id|ThreadNotFound|thread not found|no rollout|rollout.*not.*found)/i.test(
|
|
65217
|
-
message
|
|
65218
|
-
);
|
|
65219
|
-
}
|
|
65220
|
-
function isCodexResumeThreadNotFound(err3) {
|
|
65221
|
-
const message = err3 instanceof Error ? err3.message : String(err3);
|
|
65222
|
-
return err3 instanceof JsonRpcRequestError && err3.method === "thread/resume" && /(?:no rollout found for thread id|ThreadNotFound|thread not found|no rollout|rollout.*not.*found)/i.test(
|
|
65223
|
-
message
|
|
65224
|
-
);
|
|
65225
|
-
}
|
|
65226
65642
|
|
|
65227
65643
|
// src/services/session/codex-subprocess-facade.ts
|
|
65228
65644
|
function detectedCodexVersionForLog(input) {
|
|
@@ -72353,13 +72769,13 @@ async function defaultFileExists2(path5) {
|
|
|
72353
72769
|
return false;
|
|
72354
72770
|
}
|
|
72355
72771
|
}
|
|
72356
|
-
function
|
|
72772
|
+
function defaultExec2(command, args, cwd) {
|
|
72357
72773
|
return runWithTimeout(command, args, cwd, STACK_TIMEOUT_MS);
|
|
72358
72774
|
}
|
|
72359
72775
|
async function detectStack(cwd, currentBranch, deps = {}) {
|
|
72360
72776
|
const isCommandAvailable = deps.isCommandAvailable ?? defaultIsCommandAvailable;
|
|
72361
72777
|
const fileExists2 = deps.fileExists ?? defaultFileExists2;
|
|
72362
|
-
const exec = deps.exec ??
|
|
72778
|
+
const exec = deps.exec ?? defaultExec2;
|
|
72363
72779
|
const getDefaultBranch2 = deps.getDefaultBranch ?? getRepoDefaultBranch;
|
|
72364
72780
|
const graphite = await detectGraphite(
|
|
72365
72781
|
cwd,
|
|
@@ -77233,7 +77649,7 @@ function handleSpawning(snapshot, event) {
|
|
|
77233
77649
|
b2.log("spawning", "cold_idle", event.type);
|
|
77234
77650
|
b2.taskStatus("input_required");
|
|
77235
77651
|
b2.emitError(
|
|
77236
|
-
"
|
|
77652
|
+
"The agent timed out waiting for the first response. Send a message to try again.",
|
|
77237
77653
|
"sdk_error",
|
|
77238
77654
|
"run_stall_timeout"
|
|
77239
77655
|
);
|
|
@@ -77256,7 +77672,7 @@ function runningStallTimeout(sessionId, event) {
|
|
|
77256
77672
|
if (!event.recoveryArmed) {
|
|
77257
77673
|
b2.taskStatus("input_required");
|
|
77258
77674
|
b2.emitError(
|
|
77259
|
-
"
|
|
77675
|
+
"The agent stopped responding mid-run. Send a message to try again.",
|
|
77260
77676
|
"sdk_error",
|
|
77261
77677
|
"run_stall_timeout"
|
|
77262
77678
|
);
|
|
@@ -81163,6 +81579,15 @@ function createPlanFileWatcher(deps) {
|
|
|
81163
81579
|
}
|
|
81164
81580
|
|
|
81165
81581
|
// src/services/plan/plan-handler-helpers.ts
|
|
81582
|
+
function narrowPrePlanMode(mode) {
|
|
81583
|
+
if (mode === void 0 || mode === "plan") return void 0;
|
|
81584
|
+
return mode;
|
|
81585
|
+
}
|
|
81586
|
+
function decidePostApprovalMode(incoming, prePlanMode) {
|
|
81587
|
+
if (incoming !== void 0 && incoming !== "default") return incoming;
|
|
81588
|
+
if (prePlanMode !== void 0) return prePlanMode;
|
|
81589
|
+
return incoming;
|
|
81590
|
+
}
|
|
81166
81591
|
function parseAllowedPrompts(input) {
|
|
81167
81592
|
if (input === null || input === void 0 || typeof input !== "object" || Array.isArray(input))
|
|
81168
81593
|
return [];
|
|
@@ -82311,7 +82736,14 @@ var PlanHandler = class {
|
|
|
82311
82736
|
}
|
|
82312
82737
|
}
|
|
82313
82738
|
async #tryPersistPreAttach(toolUseId, filePath, planDocId, allowedPrompts) {
|
|
82314
|
-
const
|
|
82739
|
+
const prePlanPermissionMode = narrowPrePlanMode(this.#deps.getLatestSettings().permissionMode);
|
|
82740
|
+
const pendingDetection = {
|
|
82741
|
+
toolUseId,
|
|
82742
|
+
filePath,
|
|
82743
|
+
planDocId,
|
|
82744
|
+
allowedPrompts,
|
|
82745
|
+
prePlanPermissionMode
|
|
82746
|
+
};
|
|
82315
82747
|
try {
|
|
82316
82748
|
await this.#persistDetection(pendingDetection);
|
|
82317
82749
|
return true;
|
|
@@ -82409,8 +82841,12 @@ var PlanHandler = class {
|
|
|
82409
82841
|
});
|
|
82410
82842
|
}
|
|
82411
82843
|
#applyPermissionMode(permissionMode) {
|
|
82412
|
-
|
|
82413
|
-
|
|
82844
|
+
const target = decidePostApprovalMode(
|
|
82845
|
+
permissionMode,
|
|
82846
|
+
this.#lastPlanDetection?.prePlanPermissionMode
|
|
82847
|
+
);
|
|
82848
|
+
if (!target) return;
|
|
82849
|
+
this.#deps.applyTaskSettings({ permissionMode: target });
|
|
82414
82850
|
}
|
|
82415
82851
|
/**
|
|
82416
82852
|
* Build the narrow Context for the review-resolution free functions. State
|
|
@@ -83849,18 +84285,27 @@ var RewindCheckpointHandler = class {
|
|
|
83849
84285
|
return { kind: "fresh" };
|
|
83850
84286
|
}
|
|
83851
84287
|
/**
|
|
83852
|
-
* Count the
|
|
83853
|
-
* that
|
|
83854
|
-
* Codex's count-based `client.rollback` call
|
|
84288
|
+
* Count the completed agent turns dropped by rewinding to `targetSeqNo`,
|
|
84289
|
+
* inclusive of the turn that STARTS at the target. Used to compute
|
|
84290
|
+
* `numTurns` for Codex's count-based `client.rollback` call and Cursor's
|
|
84291
|
+
* store truncation.
|
|
83855
84292
|
*
|
|
83856
|
-
*
|
|
83857
|
-
* `#allCheckpointTurnNos
|
|
83858
|
-
* is
|
|
84293
|
+
* A user-facing turn = 1 spawn + 1 end = 2 consecutive entries in
|
|
84294
|
+
* `#allCheckpointTurnNos` (`[spawn0, end0, spawn1, end1, ...]`). A rewind
|
|
84295
|
+
* target seqNo is a user message, which maps to that turn's SPAWN
|
|
84296
|
+
* checkpoint (even index) — and `rewind_files` restores the tree to that
|
|
84297
|
+
* spawn checkpoint, i.e. to BEFORE the turn ran. So the target's own turn
|
|
84298
|
+
* is being dropped and must be counted: the answer is
|
|
84299
|
+
* floor(count of entries `>= targetTurnNo` / 2), not `>`. Using `>` here
|
|
84300
|
+
* undercounts by one whenever the target lands on a spawn checkpoint,
|
|
84301
|
+
* yielding `numTurns: 0` for a rewind to the initial or most-recent turn —
|
|
84302
|
+
* an invalid Codex `thread/rollback` (#5041). (For an odd/end target the
|
|
84303
|
+
* two forms agree, so this only corrects the spawn-checkpoint case.)
|
|
83859
84304
|
*/
|
|
83860
84305
|
#countTurnsAfter(targetSeqNo) {
|
|
83861
84306
|
const targetTurnNo = this.#findCheckpointForSeqNo(targetSeqNo);
|
|
83862
84307
|
if (targetTurnNo === null) return 0;
|
|
83863
|
-
return Math.floor(this.#allCheckpointTurnNos.filter((t) => t
|
|
84308
|
+
return Math.floor(this.#allCheckpointTurnNos.filter((t) => t >= targetTurnNo).length / 2);
|
|
83864
84309
|
}
|
|
83865
84310
|
async rewind(targetSeqNo, mode) {
|
|
83866
84311
|
if (this.#rewindInProgress) {
|
|
@@ -92960,6 +93405,7 @@ async function persistUserMessageAtChokepoint(args) {
|
|
|
92960
93405
|
senderKind: args.senderKind ?? "human",
|
|
92961
93406
|
content: stripBlobResourcesForStorage(args.content),
|
|
92962
93407
|
timestamp: Date.now(),
|
|
93408
|
+
...args.isSynthetic === true && { isSynthetic: true },
|
|
92963
93409
|
...persistedCorrelationId !== void 0 && { correlationId: persistedCorrelationId },
|
|
92964
93410
|
model: effectiveSettings.model ?? null,
|
|
92965
93411
|
reasoningEffort: effectiveSettings.reasoningEffort ?? null,
|
|
@@ -94449,7 +94895,7 @@ var TaskManager = class {
|
|
|
94449
94895
|
});
|
|
94450
94896
|
return task;
|
|
94451
94897
|
}
|
|
94452
|
-
async handleUserMessage(taskId, content, settings, cwd, participantId, senderDisplayName, correlationId, senderKind = "human") {
|
|
94898
|
+
async handleUserMessage(taskId, content, settings, cwd, participantId, senderDisplayName, correlationId, senderKind = "human", isSynthetic = false) {
|
|
94453
94899
|
const task = await this.#ensureTaskForUserMessage(taskId);
|
|
94454
94900
|
if (cwd && cwd !== task.cwd) {
|
|
94455
94901
|
if (task.mode === "review" && !task.cwd) {
|
|
@@ -94480,7 +94926,8 @@ var TaskManager = class {
|
|
|
94480
94926
|
latestSettings: task.orchestrator.latestSettings,
|
|
94481
94927
|
participantId,
|
|
94482
94928
|
senderKind,
|
|
94483
|
-
correlationId
|
|
94929
|
+
correlationId,
|
|
94930
|
+
isSynthetic
|
|
94484
94931
|
});
|
|
94485
94932
|
task.orchestrator.handleUserMessage(
|
|
94486
94933
|
content,
|
|
@@ -95761,6 +96208,7 @@ async function createDaemon(deps) {
|
|
|
95761
96208
|
shipyardResolver.addSubResolver("plugin", pluginResourceResolver);
|
|
95762
96209
|
resourceRegistry.register(shipyardResolver);
|
|
95763
96210
|
const capabilitiesRef = { current: null };
|
|
96211
|
+
const commitCapabilities = createCapabilityWriter(capabilitiesRef);
|
|
95764
96212
|
const rateLimitStoreRef = { current: null };
|
|
95765
96213
|
const gitCheckpoint = createGitCheckpointService();
|
|
95766
96214
|
const skillResolution = buildSkillResolutionService({
|
|
@@ -96266,6 +96714,7 @@ async function createDaemon(deps) {
|
|
|
96266
96714
|
* fallback to Claude.
|
|
96267
96715
|
*/
|
|
96268
96716
|
getModels: () => profileRegistry.list().flatMap((p2) => p2.models),
|
|
96717
|
+
getKnownMcpServerNames: () => mcpCoordinatorRef.current?.getKnownServerNames(),
|
|
96269
96718
|
/**
|
|
96270
96719
|
* Lazy: taskSnapshotResolver is assigned after this function is defined
|
|
96271
96720
|
* (same pattern as `getTaskManager: () => taskManager`). The callback is
|
|
@@ -96403,6 +96852,7 @@ async function createDaemon(deps) {
|
|
|
96403
96852
|
* fallback to Claude.
|
|
96404
96853
|
*/
|
|
96405
96854
|
getModels: () => profileRegistry.list().flatMap((p2) => p2.models),
|
|
96855
|
+
getKnownMcpServerNames: () => mcpCoordinatorRef.current?.getKnownServerNames(),
|
|
96406
96856
|
/**
|
|
96407
96857
|
* Same lazy-resolver pattern as the Codex path above — taskSnapshotResolver
|
|
96408
96858
|
* is assigned later in this function but always before any handler fires.
|
|
@@ -97424,14 +97874,14 @@ async function createDaemon(deps) {
|
|
|
97424
97874
|
if (stallProfilerConfig.enabled) {
|
|
97425
97875
|
stallProfiler.start();
|
|
97426
97876
|
}
|
|
97427
|
-
|
|
97877
|
+
commitCapabilities(await buildCapabilitiesStubWithDiskSeed());
|
|
97428
97878
|
function markDetectionFailed(err3) {
|
|
97429
97879
|
deps.log({
|
|
97430
97880
|
event: "background_capability_init_failed",
|
|
97431
97881
|
error: err3 instanceof Error ? err3.message : String(err3)
|
|
97432
97882
|
});
|
|
97433
97883
|
if (disposed || !capabilitiesRef.current) return;
|
|
97434
|
-
|
|
97884
|
+
commitCapabilities({ ...capabilitiesRef.current, detectionComplete: true });
|
|
97435
97885
|
broadcastRealCapabilities();
|
|
97436
97886
|
}
|
|
97437
97887
|
async function detectAndCommitCapabilities() {
|
|
@@ -97444,7 +97894,7 @@ async function createDaemon(deps) {
|
|
|
97444
97894
|
const cursorDetect = profileRegistry.get("cursor")?.authDetector;
|
|
97445
97895
|
next = await mergeCursorAuthFromVault(next, cursorDetect, deps.log);
|
|
97446
97896
|
if (disposed) return null;
|
|
97447
|
-
|
|
97897
|
+
commitCapabilities(next);
|
|
97448
97898
|
void saveCapabilitiesCache({
|
|
97449
97899
|
environments: next.environments,
|
|
97450
97900
|
mcpServers: next.mcpServers
|
|
@@ -97455,6 +97905,7 @@ async function createDaemon(deps) {
|
|
|
97455
97905
|
await runFastSliceAgentBroadcast({
|
|
97456
97906
|
detectAgents: (lastKnown) => detectAgentProviders(lastKnown),
|
|
97457
97907
|
capabilitiesRef,
|
|
97908
|
+
commit: commitCapabilities,
|
|
97458
97909
|
broadcast: broadcastRealCapabilities,
|
|
97459
97910
|
log: deps.log,
|
|
97460
97911
|
isDisposed: () => disposed
|
|
@@ -97603,12 +98054,12 @@ async function createDaemon(deps) {
|
|
|
97603
98054
|
function wireProxyAuthCallbacks(proxyServer) {
|
|
97604
98055
|
proxyServer.setOnAuthRequired((serverName) => {
|
|
97605
98056
|
if (capabilitiesRef.current?.mcpServers) {
|
|
97606
|
-
|
|
98057
|
+
commitCapabilities({
|
|
97607
98058
|
...capabilitiesRef.current,
|
|
97608
98059
|
mcpServers: capabilitiesRef.current.mcpServers.map(
|
|
97609
98060
|
(s2) => s2.name === serverName ? { ...s2, authStatus: "unauthenticated" } : s2
|
|
97610
98061
|
)
|
|
97611
|
-
};
|
|
98062
|
+
});
|
|
97612
98063
|
}
|
|
97613
98064
|
refreshCoordinatorFromCaps();
|
|
97614
98065
|
});
|
|
@@ -97616,12 +98067,12 @@ async function createDaemon(deps) {
|
|
|
97616
98067
|
function markConnectedProxyServersAuthed(proxyServer) {
|
|
97617
98068
|
if (!capabilitiesRef.current?.mcpServers) return;
|
|
97618
98069
|
const connectedNames = new Set(proxyServer.getServerNames());
|
|
97619
|
-
|
|
98070
|
+
commitCapabilities({
|
|
97620
98071
|
...capabilitiesRef.current,
|
|
97621
98072
|
mcpServers: capabilitiesRef.current.mcpServers.map(
|
|
97622
98073
|
(s2) => connectedNames.has(s2.name) ? { ...s2, authStatus: "authenticated" } : s2
|
|
97623
98074
|
)
|
|
97624
|
-
};
|
|
98075
|
+
});
|
|
97625
98076
|
}
|
|
97626
98077
|
function buildEnrichedSkillRows(skills) {
|
|
97627
98078
|
try {
|
|
@@ -97913,7 +98364,7 @@ async function createDaemon(deps) {
|
|
|
97913
98364
|
return capabilitiesRef.current;
|
|
97914
98365
|
},
|
|
97915
98366
|
set capabilities(c) {
|
|
97916
|
-
|
|
98367
|
+
commitCapabilities(applyPluginSupersession(c));
|
|
97917
98368
|
refreshCoordinatorFromCaps();
|
|
97918
98369
|
taskManager.refreshAllTaskSkillsSnapshots();
|
|
97919
98370
|
reBroadcastEnrichedSkillsAfterWarm();
|
|
@@ -101194,4 +101645,4 @@ export {
|
|
|
101194
101645
|
decideWorkspaceScope,
|
|
101195
101646
|
serve
|
|
101196
101647
|
};
|
|
101197
|
-
//# sourceMappingURL=serve-
|
|
101648
|
+
//# sourceMappingURL=serve-WG7HQAYG.js.map
|