@amaster.ai/employee-runtime-connector 0.1.0-beta.46 → 0.1.0-beta.48
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/amaster-runtime-daemon.mjs +2196 -196
- package/dist/amaster-runtime.mjs +1 -1
- package/package.json +4 -1
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// MirrorX runtime connector daemon bundle.
|
|
3
3
|
|
|
4
4
|
// src/amaster-runtime-daemon.mjs
|
|
5
|
-
import { createHash as
|
|
6
|
-
import { chmodSync as chmodSync6, copyFileSync as copyFileSync3, existsSync as
|
|
5
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
6
|
+
import { chmodSync as chmodSync6, copyFileSync as copyFileSync3, existsSync as existsSync13, lstatSync as lstatSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync10, readdirSync as readdirSync8, realpathSync as realpathSync4, renameSync as renameSync5, rmSync as rmSync7, statSync as statSync7, symlinkSync as symlinkSync4, unlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
7
7
|
import { arch as arch3, homedir as homedir3, hostname as hostname2, platform as platform3 } from "node:os";
|
|
8
|
-
import { basename as basename6, delimiter as delimiter2, dirname as dirname9, extname as extname2, isAbsolute as isAbsolute8, join as
|
|
9
|
-
import { spawn, spawnSync as
|
|
8
|
+
import { basename as basename6, delimiter as delimiter2, dirname as dirname9, extname as extname2, isAbsolute as isAbsolute8, join as join14, relative as relative9, resolve as resolve12 } from "node:path";
|
|
9
|
+
import { spawn, spawnSync as spawnSync6 } from "node:child_process";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
10
11
|
|
|
11
12
|
// src/amaster-runtime-daemon/common.mjs
|
|
12
13
|
import { homedir } from "node:os";
|
|
@@ -388,7 +389,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
|
|
|
388
389
|
}
|
|
389
390
|
return ptr;
|
|
390
391
|
}
|
|
391
|
-
function skipUntil(str, ptr,
|
|
392
|
+
function skipUntil(str, ptr, sep3, end, banNewLines = false) {
|
|
392
393
|
if (!end) {
|
|
393
394
|
ptr = indexOfNewline(str, ptr);
|
|
394
395
|
return ptr < 0 ? str.length : ptr;
|
|
@@ -397,7 +398,7 @@ function skipUntil(str, ptr, sep2, end, banNewLines = false) {
|
|
|
397
398
|
let c = str[i];
|
|
398
399
|
if (c === "#") {
|
|
399
400
|
i = indexOfNewline(str, i);
|
|
400
|
-
} else if (c ===
|
|
401
|
+
} else if (c === sep3) {
|
|
401
402
|
return i + 1;
|
|
402
403
|
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
403
404
|
return i;
|
|
@@ -1558,6 +1559,7 @@ import {
|
|
|
1558
1559
|
lstatSync as lstatSync2,
|
|
1559
1560
|
mkdirSync as mkdirSync3,
|
|
1560
1561
|
readFileSync as readFileSync3,
|
|
1562
|
+
realpathSync,
|
|
1561
1563
|
readdirSync as readdirSync2,
|
|
1562
1564
|
rmSync as rmSync2,
|
|
1563
1565
|
statSync as statSync2,
|
|
@@ -1778,7 +1780,10 @@ function syncAmasterProviderFiles(agentDir, executorEnv) {
|
|
|
1778
1780
|
}
|
|
1779
1781
|
|
|
1780
1782
|
// src/amaster-runtime-daemon/pi-managed-mcp-profile.mjs
|
|
1781
|
-
var
|
|
1783
|
+
var MANAGED_PI_MCP_TOOL_MODE = "proxy_only";
|
|
1784
|
+
function createManagedPiMcpProfileApi(options = {}) {
|
|
1785
|
+
const spawnSyncImpl = typeof options.spawnSync === "function" ? options.spawnSync : spawnSync2;
|
|
1786
|
+
const nowImpl = typeof options.now === "function" ? options.now : Date.now;
|
|
1782
1787
|
const SUPPORTED_SCHEMA_VERSION2 = "amaster.governed-mcp.v1";
|
|
1783
1788
|
const SUPPORTED_SERVER_NAME2 = "amaster";
|
|
1784
1789
|
const MINIMUM_PI_VERSION = [0, 73, 1];
|
|
@@ -1878,6 +1883,8 @@ var piManagedMcpProfileApi = (() => {
|
|
|
1878
1883
|
const DEFAULT_SESSION_ROLLOUT_TTL_MS2 = 24 * 60 * 60 * 1e3;
|
|
1879
1884
|
const PI_ATTESTATION_TIMEOUT_MS = 1e4;
|
|
1880
1885
|
const PI_ATTESTATION_MAX_ATTEMPTS = 2;
|
|
1886
|
+
const PI_ATTESTATION_RECENT_LIVE_TTL_MS = Number.isFinite(options.recentLiveTtlMs) && options.recentLiveTtlMs > 0 ? options.recentLiveTtlMs : 30 * 60 * 1e3;
|
|
1887
|
+
const recentLivePiVersionProbes = /* @__PURE__ */ new Map();
|
|
1881
1888
|
function record4(value) {
|
|
1882
1889
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1883
1890
|
}
|
|
@@ -1983,6 +1990,33 @@ var piManagedMcpProfileApi = (() => {
|
|
|
1983
1990
|
}
|
|
1984
1991
|
return tuple.join(".");
|
|
1985
1992
|
}
|
|
1993
|
+
function currentTimeMs() {
|
|
1994
|
+
const value = Number(nowImpl());
|
|
1995
|
+
if (!Number.isFinite(value)) throw new Error("pi_managed_mcp_attestation_failed: invalid attestation clock");
|
|
1996
|
+
return value;
|
|
1997
|
+
}
|
|
1998
|
+
function piExecutableIdentity(executorCommand) {
|
|
1999
|
+
try {
|
|
2000
|
+
const realPath = realpathSync(executorCommand);
|
|
2001
|
+
const executableStat = lstatSync2(realPath);
|
|
2002
|
+
if (!executableStat.isFile() || executableStat.isSymbolicLink()) {
|
|
2003
|
+
throw Object.assign(new Error("Pi executable is not a regular file"), { code: "EUNSAFE" });
|
|
2004
|
+
}
|
|
2005
|
+
const contentSha256 = createHash2("sha256").update(readFileSync3(realPath)).digest("hex");
|
|
2006
|
+
return `sha256:${createHash2("sha256").update(JSON.stringify({ realPath, contentSha256 })).digest("hex")}`;
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
const errorCode = typeof error?.code === "string" ? error.code : "UNKNOWN";
|
|
2009
|
+
throw new Error(`pi_managed_mcp_attestation_failed: --version error=${errorCode}`);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
function pruneRecentLivePiVersionProbes(nowMs) {
|
|
2013
|
+
for (const [identity2, entry] of recentLivePiVersionProbes) {
|
|
2014
|
+
const ageMs = nowMs - entry.attestedAtMs;
|
|
2015
|
+
if (ageMs < 0 || ageMs > PI_ATTESTATION_RECENT_LIVE_TTL_MS) {
|
|
2016
|
+
recentLivePiVersionProbes.delete(identity2);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1986
2020
|
function validateAuthority2(input) {
|
|
1987
2021
|
const runtimeAuth = record4(input.runtimeAuth);
|
|
1988
2022
|
const gateway = record4(runtimeAuth.governedMcp);
|
|
@@ -2212,9 +2246,10 @@ var piManagedMcpProfileApi = (() => {
|
|
|
2212
2246
|
return { adapterVersion, protectedValues };
|
|
2213
2247
|
}
|
|
2214
2248
|
function attestPi(executorCommand, env, configPath, expectedConfig) {
|
|
2215
|
-
|
|
2249
|
+
const executorIdentity = piExecutableIdentity(executorCommand);
|
|
2250
|
+
let result3;
|
|
2216
2251
|
for (let attempt = 1; attempt <= PI_ATTESTATION_MAX_ATTEMPTS; attempt += 1) {
|
|
2217
|
-
|
|
2252
|
+
result3 = spawnSyncImpl(executorCommand, ["--version"], {
|
|
2218
2253
|
cwd: env.AMASTER_RUNTIME_EXECUTION_WORKDIR,
|
|
2219
2254
|
env,
|
|
2220
2255
|
encoding: "utf8",
|
|
@@ -2222,18 +2257,41 @@ var piManagedMcpProfileApi = (() => {
|
|
|
2222
2257
|
killSignal: "SIGKILL",
|
|
2223
2258
|
maxBuffer: 1024 * 1024
|
|
2224
2259
|
});
|
|
2225
|
-
if (
|
|
2260
|
+
if (result3.error?.code === "ETIMEDOUT" && attempt < PI_ATTESTATION_MAX_ATTEMPTS) continue;
|
|
2226
2261
|
break;
|
|
2227
2262
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2263
|
+
let executorVersion;
|
|
2264
|
+
let executorVersionSource;
|
|
2265
|
+
let liveProbeAttestedAtMs;
|
|
2266
|
+
let liveProbeAgeMs;
|
|
2267
|
+
if (result3.error?.code === "ETIMEDOUT") {
|
|
2268
|
+
const identityAfterTimeout = piExecutableIdentity(executorCommand);
|
|
2269
|
+
const nowMs = currentTimeMs();
|
|
2270
|
+
pruneRecentLivePiVersionProbes(nowMs);
|
|
2271
|
+
const recentLiveProbe = identityAfterTimeout === executorIdentity ? recentLivePiVersionProbes.get(executorIdentity) : null;
|
|
2272
|
+
const ageMs = recentLiveProbe ? nowMs - recentLiveProbe.attestedAtMs : Number.POSITIVE_INFINITY;
|
|
2273
|
+
if (!recentLiveProbe || ageMs < 0 || ageMs > PI_ATTESTATION_RECENT_LIVE_TTL_MS) {
|
|
2274
|
+
throw new Error("pi_managed_mcp_attestation_failed: --version error=ETIMEDOUT");
|
|
2275
|
+
}
|
|
2276
|
+
executorVersion = recentLiveProbe.executorVersion;
|
|
2277
|
+
executorVersionSource = "recent_live_probe_reuse";
|
|
2278
|
+
liveProbeAttestedAtMs = recentLiveProbe.attestedAtMs;
|
|
2279
|
+
liveProbeAgeMs = ageMs;
|
|
2280
|
+
} else if (result3.error) {
|
|
2281
|
+
const errorCode = typeof result3.error.code === "string" ? result3.error.code : "UNKNOWN";
|
|
2230
2282
|
throw new Error(`pi_managed_mcp_attestation_failed: --version error=${errorCode}`);
|
|
2283
|
+
} else if (result3.status !== 0) {
|
|
2284
|
+
throw new Error(`pi_managed_mcp_attestation_failed: --version exit=${result3.status ?? "unknown"}`);
|
|
2285
|
+
} else {
|
|
2286
|
+
executorVersion = parseVersion(`${result3.stdout ?? ""}
|
|
2287
|
+
${result3.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
2288
|
+
if (piExecutableIdentity(executorCommand) !== executorIdentity) {
|
|
2289
|
+
throw new Error("pi_managed_mcp_attestation_failed: Pi executable changed during --version probe");
|
|
2290
|
+
}
|
|
2291
|
+
executorVersionSource = "live_probe";
|
|
2292
|
+
liveProbeAttestedAtMs = currentTimeMs();
|
|
2293
|
+
liveProbeAgeMs = 0;
|
|
2231
2294
|
}
|
|
2232
|
-
if (result2.status !== 0) {
|
|
2233
|
-
throw new Error(`pi_managed_mcp_attestation_failed: --version exit=${result2.status ?? "unknown"}`);
|
|
2234
|
-
}
|
|
2235
|
-
const executorVersion = parseVersion(`${result2.stdout ?? ""}
|
|
2236
|
-
${result2.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
2237
2295
|
let effectiveConfig;
|
|
2238
2296
|
try {
|
|
2239
2297
|
effectiveConfig = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
@@ -2243,7 +2301,20 @@ ${result2.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
|
2243
2301
|
if (JSON.stringify(effectiveConfig) !== JSON.stringify(expectedConfig) || env.PI_AGENT_MCP_SERVERS_FILE !== configPath) {
|
|
2244
2302
|
throw new Error("pi_managed_mcp_attestation_failed: effective config does not match managed amaster Gateway");
|
|
2245
2303
|
}
|
|
2246
|
-
|
|
2304
|
+
if (executorVersionSource === "live_probe") {
|
|
2305
|
+
pruneRecentLivePiVersionProbes(liveProbeAttestedAtMs);
|
|
2306
|
+
recentLivePiVersionProbes.set(executorIdentity, {
|
|
2307
|
+
executorVersion,
|
|
2308
|
+
attestedAtMs: liveProbeAttestedAtMs
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
return {
|
|
2312
|
+
executorVersion,
|
|
2313
|
+
executorIdentity,
|
|
2314
|
+
executorVersionSource,
|
|
2315
|
+
liveProbeAttestedAt: new Date(liveProbeAttestedAtMs).toISOString(),
|
|
2316
|
+
liveProbeAgeMs
|
|
2317
|
+
};
|
|
2247
2318
|
}
|
|
2248
2319
|
function prepareManagedPiMcpProfile2(input) {
|
|
2249
2320
|
assertInvocationIsolation2(input);
|
|
@@ -2300,12 +2371,13 @@ ${result2.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
|
2300
2371
|
PI_AGENT_MCP_SERVERS_FILE: configPath,
|
|
2301
2372
|
TMPDIR: tmp
|
|
2302
2373
|
};
|
|
2303
|
-
const
|
|
2374
|
+
const executorAttestation = attestPi(nonEmpty2(input.executorCommand, "executorCommand"), env, configPath, config);
|
|
2304
2375
|
const attestationFacts = {
|
|
2305
2376
|
connectorVersion: nonEmpty2(input.connectorVersion, "connectorVersion"),
|
|
2306
2377
|
executorKind: "pi",
|
|
2307
|
-
|
|
2378
|
+
...executorAttestation,
|
|
2308
2379
|
mcpAdapterVersion: seededRuntime.adapterVersion,
|
|
2380
|
+
mcpToolMode: MANAGED_PI_MCP_TOOL_MODE,
|
|
2309
2381
|
configMode: "isolated_home_run_scoped_mcp_file",
|
|
2310
2382
|
namespace: SUPPORTED_SERVER_NAME2,
|
|
2311
2383
|
schemaVersion: SUPPORTED_SCHEMA_VERSION2,
|
|
@@ -2396,7 +2468,7 @@ ${result2.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
|
2396
2468
|
if (existsSync3(profileRoot)) throw new Error("pi_managed_mcp_cleanup_failed: profile root still exists");
|
|
2397
2469
|
return { status: "removed", profileRoot };
|
|
2398
2470
|
}
|
|
2399
|
-
function reconcileManagedPiMcpProfiles2(rootPath,
|
|
2471
|
+
function reconcileManagedPiMcpProfiles2(rootPath, options2 = {}) {
|
|
2400
2472
|
const root = resolve2(rootPath);
|
|
2401
2473
|
if (!existsSync3(root)) return { scanned: 0, removed: 0, removedProfiles: 0, removedRollouts: 0, failed: 0, failures: [] };
|
|
2402
2474
|
const profileMarkers = [];
|
|
@@ -2427,8 +2499,8 @@ ${result2.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
|
2427
2499
|
failures.push({ profileRoot, error: error instanceof Error ? error.message : String(error) });
|
|
2428
2500
|
}
|
|
2429
2501
|
}
|
|
2430
|
-
const nowMs = Number.isFinite(
|
|
2431
|
-
const rolloutTtlMs = Number.isFinite(
|
|
2502
|
+
const nowMs = Number.isFinite(options2.nowMs) ? options2.nowMs : Date.now();
|
|
2503
|
+
const rolloutTtlMs = Number.isFinite(options2.sessionRolloutTtlMs) && options2.sessionRolloutTtlMs > 0 ? options2.sessionRolloutTtlMs : DEFAULT_SESSION_ROLLOUT_TTL_MS2;
|
|
2432
2504
|
for (const markerPath of rolloutMarkers) {
|
|
2433
2505
|
const cacheRoot = dirname3(markerPath);
|
|
2434
2506
|
try {
|
|
@@ -2456,7 +2528,8 @@ ${result2.stderr ?? ""}`, "Pi", MINIMUM_PI_VERSION);
|
|
|
2456
2528
|
};
|
|
2457
2529
|
}
|
|
2458
2530
|
return { prepareManagedPiMcpProfile: prepareManagedPiMcpProfile2, preserveManagedPiSessionRollout: preserveManagedPiSessionRollout2, cleanupManagedPiMcpProfile: cleanupManagedPiMcpProfile2, reconcileManagedPiMcpProfiles: reconcileManagedPiMcpProfiles2 };
|
|
2459
|
-
}
|
|
2531
|
+
}
|
|
2532
|
+
var piManagedMcpProfileApi = createManagedPiMcpProfileApi();
|
|
2460
2533
|
var prepareManagedPiMcpProfile = piManagedMcpProfileApi.prepareManagedPiMcpProfile;
|
|
2461
2534
|
var preserveManagedPiSessionRollout = piManagedMcpProfileApi.preserveManagedPiSessionRollout;
|
|
2462
2535
|
var cleanupManagedPiMcpProfile = piManagedMcpProfileApi.cleanupManagedPiMcpProfile;
|
|
@@ -2528,12 +2601,12 @@ function record2(value) {
|
|
|
2528
2601
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2529
2602
|
}
|
|
2530
2603
|
function managedChildSpawnInvocation(command, args, requestedIdentity) {
|
|
2531
|
-
const
|
|
2532
|
-
const childUmask =
|
|
2604
|
+
const identity2 = record2(requestedIdentity);
|
|
2605
|
+
const childUmask = identity2.umask;
|
|
2533
2606
|
if (childUmask !== void 0 && (!Number.isInteger(childUmask) || childUmask < 0 || childUmask > 511)) {
|
|
2534
2607
|
throw new Error("executor_spawn_umask_invalid");
|
|
2535
2608
|
}
|
|
2536
|
-
const spawnIdentity = { ...
|
|
2609
|
+
const spawnIdentity = { ...identity2 };
|
|
2537
2610
|
delete spawnIdentity.umask;
|
|
2538
2611
|
if (childUmask === void 0) {
|
|
2539
2612
|
return { command, args, spawnIdentity };
|
|
@@ -2697,7 +2770,10 @@ function fixedRules(input, includeIssueLine) {
|
|
|
2697
2770
|
input.managed ? `- source workspace: ${input.sourceWorkspacePath}` : "",
|
|
2698
2771
|
input.managed ? "Use the execution workspace as cwd. Never recursively scan the source workspace, the user's home directory, or any path outside the execution workspace. Only inspect a specific external source path when the task explicitly requires that exact path." : "",
|
|
2699
2772
|
input.wakeReason ? `- wake reason: ${input.wakeReason}` : "",
|
|
2700
|
-
input.hasGovernedMcp ? "- AMaster mutations are available only through the managed amaster MCP server. Use discovered tools and immediate typed results; never call AMaster mutation REST endpoints directly." : "- No mutation channel is available for this command. Keep the run read-only and report the missing Runtime V2 capability as a blocker."
|
|
2773
|
+
input.hasGovernedMcp ? "- AMaster mutations are available only through the managed amaster MCP server. Use discovered tools and immediate typed results; never call AMaster mutation REST endpoints directly." : "- No mutation channel is available for this command. Keep the run read-only and report the missing Runtime V2 capability as a blocker.",
|
|
2774
|
+
"- Runtime Artifact lineage fields sourceWorkProductId and sourceWorkProductIds accept only current artifact work product ids returned as result.effectResult.workProductId by a succeeded upload_artifact action. Never pass a documentId or revisionId as artifact lineage.",
|
|
2775
|
+
"- When all inputs are issue documents and there is no source artifact work product, persist the derived result with upsert_document and do not call upload_artifact for a derived or review artifact.",
|
|
2776
|
+
input.hasGovernedMcp && input.executorKind === "pi" && input.managedMcpToolMode === "proxy_only" ? "- For proxy-only Pi MCP writes, put the complete target argument object directly in the actual `mcp` call's string `args` field, even for long document bodies. Do not create shell, script, or file intermediates to stage or quote a would-be tool call, and do not narrate or print it as prose. After the needed `runtime_action.describe` result, emit the write call before further planning." : ""
|
|
2701
2777
|
].filter(Boolean).join("\n");
|
|
2702
2778
|
}
|
|
2703
2779
|
function approvalContinuationText(input) {
|
|
@@ -2726,25 +2802,79 @@ function recoveryInstructionText(input) {
|
|
|
2726
2802
|
"The prior successful run did not leave a terminal task disposition.",
|
|
2727
2803
|
"Do not repeat the original source work or create or revise deliverables.",
|
|
2728
2804
|
"Inspect the existing task and run evidence, then choose exactly one explicit disposition: done, in_review, input, blocked, delegated, or queued.",
|
|
2729
|
-
"If more work remains on this issue,
|
|
2805
|
+
"If more work remains on this issue, choose continuation/todo and then execute the Runtime Action Continuation Option rendered below. Do not guess its transport shape.",
|
|
2730
2806
|
"Do not keep or write `in_progress`: only the actual `todo` issue update is a recognized resume disposition and lets the control plane queue a normal-model continuation.",
|
|
2731
2807
|
"Record the disposition with its concrete owner or next action and update the task accordingly."
|
|
2732
2808
|
].join("\n");
|
|
2733
2809
|
}
|
|
2734
2810
|
if (input.wakeReason === "source_scoped_recovery_action") {
|
|
2811
|
+
const wake = asRecord(context.paperclipWake);
|
|
2812
|
+
const unresolvedBlockerIssueIds = Array.isArray(wake.unresolvedBlockerIssueIds) ? wake.unresolvedBlockerIssueIds.map(readString).filter(Boolean) : [];
|
|
2813
|
+
const blockedDisposition = unresolvedBlockerIssueIds.length > 0 ? `- blocked only when the issue already has an unresolved first-class blocker; name the exact blocker and wait condition from: ${unresolvedBlockerIssueIds.join(", ")};` : [
|
|
2814
|
+
"- if source work remains on this issue, choose continuation/todo and then execute the Runtime Action Continuation Option rendered below so the control plane can queue a normal-model continuation;",
|
|
2815
|
+
"- do not set `blocked` without an unresolved first-class blocker; a named owner or proposed next action alone is not a blocker;"
|
|
2816
|
+
].join("\n");
|
|
2735
2817
|
return [
|
|
2736
2818
|
"This is a status-only source recovery. Do not repeat the original source work or create or revise deliverables.",
|
|
2737
2819
|
"Inspect the existing task and run evidence, then choose exactly one explicit disposition using task-governance actions:",
|
|
2738
2820
|
"- done only when the existing evidence already satisfies acceptance;",
|
|
2739
2821
|
"- in_review or input when a specific human review or answer is required;",
|
|
2740
|
-
|
|
2822
|
+
blockedDisposition,
|
|
2741
2823
|
"- delegated or queued only with a concrete continuation path.",
|
|
2742
2824
|
"Record the disposition in a comment and update the parent task accordingly."
|
|
2743
2825
|
].join("\n");
|
|
2744
2826
|
}
|
|
2745
2827
|
return "";
|
|
2746
2828
|
}
|
|
2747
|
-
function
|
|
2829
|
+
function continuationRuntimeActionEnvelope(context) {
|
|
2830
|
+
const envelope = asRecord(context.continuationRuntimeActionEnvelope);
|
|
2831
|
+
const action = asRecord(envelope.action);
|
|
2832
|
+
if (!readString(envelope.schemaVersion) || !readString(envelope.idempotencyKey) || action.type !== "update_parent" || action.status !== "todo" || !readString(action.comment)) return null;
|
|
2833
|
+
return envelope;
|
|
2834
|
+
}
|
|
2835
|
+
function runtimeActionContinuationOptionText(input) {
|
|
2836
|
+
if (!isRecoveryWakeReason(input.wakeReason)) return "";
|
|
2837
|
+
const heading = "### Runtime Action Continuation Option";
|
|
2838
|
+
if (!input.hasGovernedMcp) {
|
|
2839
|
+
return `${heading}
|
|
2840
|
+
Runtime Action continuation is unavailable: managed governed MCP capability is missing. Do not guess a tool call.`;
|
|
2841
|
+
}
|
|
2842
|
+
const envelope = continuationRuntimeActionEnvelope(asRecord(input.context));
|
|
2843
|
+
if (!envelope) {
|
|
2844
|
+
return `${heading}
|
|
2845
|
+
Runtime Action continuation is unavailable: continuationRuntimeActionEnvelope is missing or invalid. Do not guess a tool call.`;
|
|
2846
|
+
}
|
|
2847
|
+
if (input.executorKind === "pi") {
|
|
2848
|
+
if (input.managedMcpToolMode !== "proxy_only") {
|
|
2849
|
+
return `${heading}
|
|
2850
|
+
Runtime Action continuation is unavailable: managed Pi MCP tool mode is not proxy_only. Do not guess a proxy or direct-tool call.`;
|
|
2851
|
+
}
|
|
2852
|
+
const proxyCall = {
|
|
2853
|
+
server: "amaster",
|
|
2854
|
+
tool: "runtime_action.submit",
|
|
2855
|
+
args: JSON.stringify(envelope)
|
|
2856
|
+
};
|
|
2857
|
+
return [
|
|
2858
|
+
heading,
|
|
2859
|
+
"Only after selecting continuation/todo from the disposition menu, emit an actual `mcp` tool call with the exact proxy arguments below. Do not print this JSON as prose and do not add an outer `action` field.",
|
|
2860
|
+
"```json",
|
|
2861
|
+
jsonText(proxyCall),
|
|
2862
|
+
"```"
|
|
2863
|
+
].join("\n");
|
|
2864
|
+
}
|
|
2865
|
+
if (input.executorKind === "codex") {
|
|
2866
|
+
return [
|
|
2867
|
+
heading,
|
|
2868
|
+
"Only after selecting continuation/todo from the disposition menu, emit an actual `runtime_action.submit` tool call with the exact arguments below. Do not print this JSON as prose.",
|
|
2869
|
+
"```json",
|
|
2870
|
+
jsonText(envelope),
|
|
2871
|
+
"```"
|
|
2872
|
+
].join("\n");
|
|
2873
|
+
}
|
|
2874
|
+
return `${heading}
|
|
2875
|
+
Runtime Action continuation is unavailable: executor kind does not support a governed Runtime Action continuation. Do not guess a tool call.`;
|
|
2876
|
+
}
|
|
2877
|
+
function runtimeAuthorizationText(context, mode, { suppressOrdinaryCompletionContract = false } = {}) {
|
|
2748
2878
|
const envelope = asRecord(context.authorizationEnvelope);
|
|
2749
2879
|
const allowed = new Set(
|
|
2750
2880
|
Array.isArray(envelope.allowedActionClasses) ? envelope.allowedActionClasses.filter((value) => typeof value === "string") : []
|
|
@@ -2754,13 +2884,23 @@ function runtimeAuthorizationText(context) {
|
|
|
2754
2884
|
authorizationClass,
|
|
2755
2885
|
optionId: readString(optionId)
|
|
2756
2886
|
})).filter((entry) => entry.optionId && !allowed.has(entry.authorizationClass));
|
|
2757
|
-
|
|
2758
|
-
|
|
2887
|
+
const issue = asRecord(context.paperclipIssue);
|
|
2888
|
+
const completion = asRecord(asRecord(issue.taskRequirements).completion);
|
|
2889
|
+
const completionRole = readString(completion.role);
|
|
2890
|
+
const completionDeliverable = readString(completion.deliverable);
|
|
2891
|
+
const firstDocumentCheckpoint = completionDeliverable === "document_or_artifact" ? mode === "cold" ? "Before the first external browse, search, or research action, call upsert_document with a stable key. Create a checkpoint skeleton containing the acceptance criteria, a source table, and known unknowns; update the same document with each piece of verified evidence instead of waiting until broad research is complete. Immediately after upsert_document, call update_parent with status: in_progress and a concrete next action; complete both control-plane writes before the first external browse, search, or research action." : "Before the first external browse, search, or research action, read the existing stable-key document. Preserve all still-valid verified rows, source URLs, conclusions, and known unknowns; do not replace a populated document with a blank skeleton or less-informative fallback. Only when the document does not exist may you create a checkpoint skeleton. Then call update_parent with status: in_progress and a concrete next action before the first external browse, search, or research action. Update the same document with each piece of verified evidence." : "";
|
|
2892
|
+
const completionContract = !suppressOrdinaryCompletionContract && completionRole && completionDeliverable ? [
|
|
2893
|
+
"Before ending this run, persist durable progress through Runtime Actions and record one explicit task disposition. Browser and tool history alone are not durable progress.",
|
|
2894
|
+
firstDocumentCheckpoint,
|
|
2895
|
+
"If work remains in an ordinary productive run, call update_parent with status: in_progress and a concrete next action so bounded continuation recovery can preserve a live path. Status: todo does not queue a normal continuation from an ordinary productive run. Only a server-issued successful-run handoff recovery may use status: todo, and only according to its exact Recovery Instruction. Otherwise use a supported terminal or waiting disposition."
|
|
2896
|
+
].filter(Boolean).join(" ") : "";
|
|
2897
|
+
const authorizationContract = missing.length > 0 ? [
|
|
2759
2898
|
`Current allowed action classes: ${[...allowed].join(", ") || "task_governance"}.`,
|
|
2760
2899
|
"If the task requires a missing runtime action class, create a request_checkbox_confirmation interaction using the exact option id below and wait for its accepted continuation:",
|
|
2761
2900
|
...missing.map((entry) => `- ${entry.authorizationClass}: ${entry.optionId}`),
|
|
2762
2901
|
"Never use request_confirmation to authorize a runtime action class."
|
|
2763
|
-
].join("\n");
|
|
2902
|
+
].join("\n") : "";
|
|
2903
|
+
return [completionContract, authorizationContract].filter(Boolean).join("\n");
|
|
2764
2904
|
}
|
|
2765
2905
|
function runtimeDecompositionRequirementText(context) {
|
|
2766
2906
|
const issue = asRecord(context.paperclipIssue);
|
|
@@ -2778,7 +2918,10 @@ function runtimeDecompositionRequirementText(context) {
|
|
|
2778
2918
|
}
|
|
2779
2919
|
return [
|
|
2780
2920
|
"This issue has a server-enforced typed decomposition requirement.",
|
|
2781
|
-
"Create real direct child
|
|
2921
|
+
"Create the complete real direct child graph before doing any substantial source work.",
|
|
2922
|
+
"Before browsing, searching, commenting, or doing any source work, call runtime_action.describe for create_child_task, persist the complete child graph with runtime_action.plan, and execute it with runtime_action.commit.",
|
|
2923
|
+
"Once runtime_action.commit succeeds, the committed required child graph is this parent run's durable delegated live disposition.",
|
|
2924
|
+
"After runtime_action.commit succeeds, yield and end the parent run immediately. Do not browse, search, research, or execute any delegated child acceptance scope, and do not poll child runs. Child assignment runs are the sole execution path for delegated child scope.",
|
|
2782
2925
|
"Each executable child must have an owner, dependencies where needed, and acceptance criteria. Do not create probe or test children.",
|
|
2783
2926
|
"Preserve every parent safety and approval boundary in each child title, description, and acceptance criteria. If the parent prohibits registration, payment, booking, or external contact without later explicit authorization, create preparation or approval only children and do not assign the prohibited action.",
|
|
2784
2927
|
`Requirement source: ${sourceType}:${sourceId}@${sourceRevision}`
|
|
@@ -2816,12 +2959,12 @@ function manifestEntry(section) {
|
|
|
2816
2959
|
truncationReason: section.truncationReason ?? null
|
|
2817
2960
|
};
|
|
2818
2961
|
}
|
|
2819
|
-
function renderPrompt(sections, manifest) {
|
|
2962
|
+
function renderPrompt(sections, manifest, compactManifest = false) {
|
|
2820
2963
|
return [
|
|
2821
2964
|
...sections.map(sectionText).filter(Boolean),
|
|
2822
2965
|
"## Context Manifest",
|
|
2823
2966
|
"```json",
|
|
2824
|
-
jsonText(manifest),
|
|
2967
|
+
compactManifest ? JSON.stringify(manifest) : jsonText(manifest),
|
|
2825
2968
|
"```"
|
|
2826
2969
|
].join("\n");
|
|
2827
2970
|
}
|
|
@@ -2845,6 +2988,10 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2845
2988
|
}
|
|
2846
2989
|
const context = asRecord(input.context);
|
|
2847
2990
|
const mode = contextMode(input);
|
|
2991
|
+
const recoveryInstruction = recoveryInstructionText(input);
|
|
2992
|
+
const recoveryContinuationOption = recoveryInstruction ? runtimeActionContinuationOptionText(input) : "";
|
|
2993
|
+
const completeRecoveryInstruction = [recoveryInstruction, recoveryContinuationOption].filter(Boolean).join("\n\n");
|
|
2994
|
+
const suppressOrdinaryCompletionContract = completeRecoveryInstruction.length > 0;
|
|
2848
2995
|
const governedReads = governedReadSection(context);
|
|
2849
2996
|
const hasTask = Boolean(readString(input.taskMarkdown));
|
|
2850
2997
|
const taskText = readString(input.taskMarkdown) ?? "";
|
|
@@ -2869,9 +3016,9 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2869
3016
|
const snapshotFreshness = { kind: "run_snapshot" };
|
|
2870
3017
|
const rawSections = [
|
|
2871
3018
|
{ name: "runtime_rules", title: "", priority: 100, sourceRef: `command:${input.commandId}`, content: fixedRules(input, !hasTask) },
|
|
2872
|
-
{ name: "recovery_instruction", title: "Recovery Instruction", priority:
|
|
3019
|
+
{ name: "recovery_instruction", title: "Recovery Instruction", priority: 100, sourceRef: `run:${input.runId ?? "unknown"}`, content: completeRecoveryInstruction, truncationReason: completeRecoveryInstruction ? null : "mode_selection" },
|
|
2873
3020
|
{ name: "approval_continuation", title: "Approved Runtime Action Continuation", priority: 99, sourceRef: `run:${input.runId ?? "unknown"}`, content: approvalContinuationText(input), truncationReason: mode === "continuation" ? null : "mode_selection" },
|
|
2874
|
-
{ name: "runtime_authorization", title: "Runtime Action
|
|
3021
|
+
{ name: "runtime_authorization", title: "Runtime Action Contract", priority: 98, sourceRef: `run:${input.runId ?? "unknown"}`, content: runtimeAuthorizationText(context, mode, { suppressOrdinaryCompletionContract }) },
|
|
2875
3022
|
{ name: "runtime_decomposition_requirement", title: "Required Task Decomposition", priority: 98, sourceRef: `issue:${input.issueId ?? "unknown"}`, content: runtimeDecompositionRequirementText(context) },
|
|
2876
3023
|
{ name: "continuation_summary", title: "Continuation Summary", priority: 97, sourceRef: readString(asRecord(context.paperclipContinuationSummary).key) ?? `issue:${input.issueId ?? "unknown"}`, observedAt: readString(asRecord(context.paperclipContinuationSummary).updatedAt), originalContent: continuationSummary, content: mode === "cold" ? "" : continuationSummary, truncationReason: mode === "cold" ? "mode_selection" : null },
|
|
2877
3024
|
{ name: "wake_comments", title: wakeDeltaTitle, priority: interactionResolution ? 99 : 95, sourceRef: wakeDeltaSourceRefs, originalContent: wakeDeltaOriginal, content: wakeDeltaContent, truncationReason: commentsSelected ? null : "duplicate_task_context" },
|
|
@@ -2901,12 +3048,13 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2901
3048
|
truncationReason: section.content ? section.truncationReason : section.truncationReason ?? "source_absent"
|
|
2902
3049
|
}));
|
|
2903
3050
|
let prompt = "";
|
|
3051
|
+
let compactManifest = false;
|
|
2904
3052
|
let manifest = buildManifest(mode, maxChars, sections, governedReads.provenance, 0);
|
|
2905
3053
|
for (let pass = 0; pass < 20; pass += 1) {
|
|
2906
3054
|
let usedChars = 0;
|
|
2907
3055
|
for (let telemetryPass = 0; telemetryPass < 3; telemetryPass += 1) {
|
|
2908
3056
|
manifest = buildManifest(mode, maxChars, sections, governedReads.provenance, usedChars);
|
|
2909
|
-
prompt = renderPrompt(sections, manifest);
|
|
3057
|
+
prompt = renderPrompt(sections, manifest, compactManifest);
|
|
2910
3058
|
if (prompt.length === usedChars) break;
|
|
2911
3059
|
usedChars = prompt.length;
|
|
2912
3060
|
}
|
|
@@ -2914,8 +3062,13 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2914
3062
|
const overflow = Math.max(1, prompt.length - maxChars);
|
|
2915
3063
|
const candidate = [...sections].filter((section) => section.priority < 100 && section.content.length > 0).sort((left, right) => left.priority - right.priority)[0];
|
|
2916
3064
|
if (!candidate) {
|
|
3065
|
+
if (!compactManifest) {
|
|
3066
|
+
compactManifest = true;
|
|
3067
|
+
manifest = buildManifest(mode, maxChars, sections, governedReads.provenance, 0);
|
|
3068
|
+
continue;
|
|
3069
|
+
}
|
|
2917
3070
|
const fixedChars = sections.filter((section) => section.priority === 100).map(sectionText).join("\n").length;
|
|
2918
|
-
const manifestChars =
|
|
3071
|
+
const manifestChars = JSON.stringify(manifest).length;
|
|
2919
3072
|
throw new Error(`Prompt fixed sections exceed the unified ${maxChars}-character budget: promptChars=${prompt.length} fixedChars=${fixedChars} manifestChars=${manifestChars}`);
|
|
2920
3073
|
}
|
|
2921
3074
|
truncateSection(candidate, Math.max(0, candidate.content.length - overflow - 32));
|
|
@@ -2954,22 +3107,22 @@ function executorDiscoveryEnv(env = process.env) {
|
|
|
2954
3107
|
}
|
|
2955
3108
|
function commandExists(command, env = process.env) {
|
|
2956
3109
|
const commandEnv = executorDiscoveryEnv(env);
|
|
2957
|
-
const
|
|
3110
|
+
const result3 = spawnSync3("sh", ["-lc", `command -v ${quoteShell(command)} >/dev/null 2>&1`], {
|
|
2958
3111
|
env: commandEnv,
|
|
2959
3112
|
stdio: "ignore"
|
|
2960
3113
|
});
|
|
2961
|
-
return
|
|
3114
|
+
return result3.status === 0;
|
|
2962
3115
|
}
|
|
2963
3116
|
function commandVersion(command, env = process.env) {
|
|
2964
3117
|
const commandEnv = executorDiscoveryEnv(env);
|
|
2965
|
-
const
|
|
3118
|
+
const result3 = spawnSync3(command, ["--version"], {
|
|
2966
3119
|
env: commandEnv,
|
|
2967
3120
|
encoding: "utf8",
|
|
2968
3121
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2969
3122
|
timeout: 3e3
|
|
2970
3123
|
});
|
|
2971
|
-
if (
|
|
2972
|
-
return
|
|
3124
|
+
if (result3.status !== 0) return void 0;
|
|
3125
|
+
return result3.stdout.trim().split(/\r?\n/)[0]?.slice(0, 120) || void 0;
|
|
2973
3126
|
}
|
|
2974
3127
|
function discoverExecutors(options = {}) {
|
|
2975
3128
|
const knownExecutors = options.knownExecutors ?? KNOWN_EXECUTORS;
|
|
@@ -3053,8 +3206,8 @@ function stateFilePath(env) {
|
|
|
3053
3206
|
return env.AMASTER_DAEMON_STATE_FILE ? expandHomePath(String(env.AMASTER_DAEMON_STATE_FILE)) : join5(runtimeHome(env), "runtime-connector-state.json");
|
|
3054
3207
|
}
|
|
3055
3208
|
function corruptStatePath(path) {
|
|
3056
|
-
const
|
|
3057
|
-
const base = `${path}.corrupt-${
|
|
3209
|
+
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
3210
|
+
const base = `${path}.corrupt-${timestamp2}Z`;
|
|
3058
3211
|
let candidate = base;
|
|
3059
3212
|
for (let suffix = 1; existsSync4(candidate); suffix += 1) {
|
|
3060
3213
|
candidate = `${base}.${suffix}`;
|
|
@@ -3111,6 +3264,12 @@ function buildConfig(env = process.env, flags = {}) {
|
|
|
3111
3264
|
const runtimeWorkspacesRoot = expandHomePath(
|
|
3112
3265
|
String(flags.runtimeWorkspacesRoot ?? env.AMASTER_RUNTIME_WORKSPACES_ROOT ?? join5(home, "workspaces"))
|
|
3113
3266
|
);
|
|
3267
|
+
const browserSessionStateRoot = expandHomePath(String(
|
|
3268
|
+
flags.browserSessionStateRoot ?? env.AMASTER_BROWSER_SESSION_STATE_ROOT ?? join5(home, "browser-auth")
|
|
3269
|
+
));
|
|
3270
|
+
const browserExecutablePath = String(
|
|
3271
|
+
flags.browserExecutablePath ?? env.AMASTER_BROWSER_EXECUTABLE_PATH ?? ""
|
|
3272
|
+
).trim();
|
|
3114
3273
|
mkdirSync4(runtimeWorkspacesRoot, { recursive: true });
|
|
3115
3274
|
const workspaceBindings = splitList(
|
|
3116
3275
|
flags.workspaceAllowlist ?? env.AMASTER_WORKSPACE_ALLOWLIST ?? env.AMASTER_WORKSPACE_BINDINGS ?? runtimeWorkspacesRoot
|
|
@@ -3177,6 +3336,8 @@ function buildConfig(env = process.env, flags = {}) {
|
|
|
3177
3336
|
flags.orphanReaperIntervalSeconds ?? env.AMASTER_RUNTIME_ORPHAN_REAPER_INTERVAL_SECONDS,
|
|
3178
3337
|
300
|
|
3179
3338
|
),
|
|
3339
|
+
browserSessionStateRoot,
|
|
3340
|
+
browserExecutablePath: browserExecutablePath ? expandHomePath(browserExecutablePath) : "",
|
|
3180
3341
|
runtimeWorkspacesRoot,
|
|
3181
3342
|
state
|
|
3182
3343
|
};
|
|
@@ -3192,6 +3353,18 @@ function tcReadString(value) {
|
|
|
3192
3353
|
function tcReadNumber(value, fallback = 0) {
|
|
3193
3354
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3194
3355
|
}
|
|
3356
|
+
var TC_PI_ARGUMENT_VALIDATION_NODE_BUDGET = 4e3;
|
|
3357
|
+
function tcContainsPiArgumentValidation(value, depth = 0, seen = /* @__PURE__ */ new WeakSet(), budget = { remaining: TC_PI_ARGUMENT_VALIDATION_NODE_BUDGET }) {
|
|
3358
|
+
if (budget.remaining <= 0) return false;
|
|
3359
|
+
budget.remaining -= 1;
|
|
3360
|
+
if (typeof value === "string") {
|
|
3361
|
+
return /\binvalid(?:[_ -]+)tool(?:[_ -]+)arguments?\b|\binvalid args json\b|\btool[- ]arguments? (?:failed )?validation\b/i.test(value.slice(0, 2e4));
|
|
3362
|
+
}
|
|
3363
|
+
if (!value || typeof value !== "object" || depth >= 6 || seen.has(value)) return false;
|
|
3364
|
+
seen.add(value);
|
|
3365
|
+
const entries = Array.isArray(value) ? value.slice(0, 40).map((entry, index) => [String(index), entry]) : Object.entries(value).slice(0, 80);
|
|
3366
|
+
return entries.some(([, entry]) => tcContainsPiArgumentValidation(entry, depth + 1, seen, budget));
|
|
3367
|
+
}
|
|
3195
3368
|
function tcTruncateText(value, maxChars = 16e3) {
|
|
3196
3369
|
const text = String(value ?? "");
|
|
3197
3370
|
if (text.length <= maxChars) return text;
|
|
@@ -3310,9 +3483,9 @@ function filterExecutionStderrForResult(executorKind, stderr) {
|
|
|
3310
3483
|
const trimmed = line.trim();
|
|
3311
3484
|
return !trimmed || !isNoise(trimmed);
|
|
3312
3485
|
});
|
|
3313
|
-
const
|
|
3314
|
-
return hadTrailingNewline &&
|
|
3315
|
-
` :
|
|
3486
|
+
const result3 = kept.join("\n");
|
|
3487
|
+
return hadTrailingNewline && result3 ? `${result3}
|
|
3488
|
+
` : result3;
|
|
3316
3489
|
}
|
|
3317
3490
|
function summarizeCodexEvent(event) {
|
|
3318
3491
|
if (isProviderBookkeepingEvent(event)) return null;
|
|
@@ -3563,6 +3736,7 @@ function summarizePiEvent(event) {
|
|
|
3563
3736
|
if (type === "tool_execution_start" || type === "tool_execution_end") {
|
|
3564
3737
|
const toolName = tcReadString(event.toolName) ?? "unknown";
|
|
3565
3738
|
const completed = type === "tool_execution_end";
|
|
3739
|
+
const argumentValidationFailed = completed && event.isError === true && tcContainsPiArgumentValidation(event.result);
|
|
3566
3740
|
return {
|
|
3567
3741
|
stream: "system",
|
|
3568
3742
|
level: completed && event.isError === true ? "error" : "info",
|
|
@@ -3572,7 +3746,8 @@ function summarizePiEvent(event) {
|
|
|
3572
3746
|
executorEventType: type,
|
|
3573
3747
|
toolName,
|
|
3574
3748
|
toolCallId: tcReadString(event.toolCallId),
|
|
3575
|
-
status: completed ? event.isError === true ? "failed" : "completed" : "started"
|
|
3749
|
+
status: completed ? event.isError === true ? "failed" : "completed" : "started",
|
|
3750
|
+
...argumentValidationFailed ? { errorCode: "invalid_tool_arguments" } : {}
|
|
3576
3751
|
}
|
|
3577
3752
|
};
|
|
3578
3753
|
}
|
|
@@ -3718,8 +3893,8 @@ var PI_TERMINAL_RUNTIME_ACTION_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3718
3893
|
function approvedMcpInvocationSucceeded(results, invocationId) {
|
|
3719
3894
|
const approvedInvocationId = readString(invocationId);
|
|
3720
3895
|
return Boolean(approvedInvocationId && (Array.isArray(results) ? results : []).some((rawResult) => {
|
|
3721
|
-
const
|
|
3722
|
-
return readString(
|
|
3896
|
+
const result3 = asRecord(rawResult);
|
|
3897
|
+
return readString(result3.invocationId) === approvedInvocationId && readString(result3.status) === "succeeded" && !["rejected", "blocked"].includes(readString(result3.providerStatus) ?? "");
|
|
3723
3898
|
}));
|
|
3724
3899
|
}
|
|
3725
3900
|
function governedMcpToolResult(structuredContent) {
|
|
@@ -3733,9 +3908,9 @@ function governedMcpToolResult(structuredContent) {
|
|
|
3733
3908
|
const intentId = readString(effectResult.artifactIntentId);
|
|
3734
3909
|
const manifestId = readString(effectResult.manifestId);
|
|
3735
3910
|
const sourceRelativePath = readString(effectResult.sourceRelativePath);
|
|
3736
|
-
const
|
|
3911
|
+
const sha2563 = readString(effectResult.sha256);
|
|
3737
3912
|
const byteSize = readNumber(effectResult.byteSize, 0);
|
|
3738
|
-
const artifactIntent = providerStatus === "pending_reconcile" && intentId && manifestId && sourceRelativePath && /^[a-f0-9]{64}$/.test(
|
|
3913
|
+
const artifactIntent = providerStatus === "pending_reconcile" && intentId && manifestId && sourceRelativePath && /^[a-f0-9]{64}$/.test(sha2563 ?? "") && Number.isSafeInteger(byteSize) && byteSize > 0 ? { intentId, manifestId, sourceRelativePath, sha256: sha2563, byteSize } : null;
|
|
3739
3914
|
const runtimeActionToolName = readString(providerContent.toolName);
|
|
3740
3915
|
const runtimeAction = runtimeActionToolName?.startsWith("runtime_action.") ? {
|
|
3741
3916
|
toolName: runtimeActionToolName,
|
|
@@ -3755,21 +3930,21 @@ function durablePiRuntimeActionEvidence(results) {
|
|
|
3755
3930
|
const normalizedResults = (Array.isArray(results) ? results : []).map(asRecord);
|
|
3756
3931
|
const writeCallIds = /* @__PURE__ */ new Set();
|
|
3757
3932
|
const writePlanIds = /* @__PURE__ */ new Set();
|
|
3758
|
-
for (const
|
|
3759
|
-
const runtimeAction = asRecord(
|
|
3933
|
+
for (const result3 of normalizedResults) {
|
|
3934
|
+
const runtimeAction = asRecord(result3.runtimeAction);
|
|
3760
3935
|
const toolName = readString(runtimeAction.toolName);
|
|
3761
3936
|
const callId = readString(runtimeAction.callId);
|
|
3762
3937
|
const planId = readString(runtimeAction.planId);
|
|
3763
3938
|
const resultStatus = readString(runtimeAction.resultStatus);
|
|
3764
|
-
const status = readString(
|
|
3765
|
-
const providerStatus = readString(
|
|
3939
|
+
const status = readString(result3.status);
|
|
3940
|
+
const providerStatus = readString(result3.providerStatus);
|
|
3766
3941
|
if (status === "succeeded" && providerStatus === "accepted" && toolName && resultStatus && PI_TERMINAL_RUNTIME_ACTION_STATUSES.has(resultStatus)) {
|
|
3767
3942
|
const isLinkedStatus = toolName === "runtime_action.status" && (callId && writeCallIds.has(callId) || planId && writePlanIds.has(planId));
|
|
3768
3943
|
const isDurableWrite = toolName === "runtime_action.submit" ? Boolean(callId) : toolName === "runtime_action.commit" && Boolean(planId || callId);
|
|
3769
3944
|
if (isLinkedStatus || isDurableWrite) {
|
|
3770
3945
|
return {
|
|
3771
3946
|
kind: "runtime_action",
|
|
3772
|
-
...readString(
|
|
3947
|
+
...readString(result3.invocationId) ? { invocationId: readString(result3.invocationId) } : {},
|
|
3773
3948
|
toolName,
|
|
3774
3949
|
...callId ? { callId } : {},
|
|
3775
3950
|
...planId ? { planId } : {}
|
|
@@ -3819,10 +3994,10 @@ function codexMcpToolResults(event) {
|
|
|
3819
3994
|
if (event?.type !== "item.completed") return [];
|
|
3820
3995
|
const item = asRecord(event.item);
|
|
3821
3996
|
if (item.type !== "mcp_tool_call") return [];
|
|
3822
|
-
const
|
|
3823
|
-
let structuredContent = asRecord(
|
|
3824
|
-
if (Object.keys(structuredContent).length === 0 && Array.isArray(
|
|
3825
|
-
for (const part of
|
|
3997
|
+
const result3 = asRecord(item.result);
|
|
3998
|
+
let structuredContent = asRecord(result3.structuredContent);
|
|
3999
|
+
if (Object.keys(structuredContent).length === 0 && Array.isArray(result3.content)) {
|
|
4000
|
+
for (const part of result3.content) {
|
|
3826
4001
|
const text = readString(asRecord(part).text);
|
|
3827
4002
|
if (!text) continue;
|
|
3828
4003
|
try {
|
|
@@ -4497,7 +4672,7 @@ async function postRuntimeConnectorJsonWithRetry(config, path, payload, options
|
|
|
4497
4672
|
} catch (error) {
|
|
4498
4673
|
lastError = error;
|
|
4499
4674
|
if (attempt >= maxAttempts || !retryableRuntimeConnectorPost(error)) throw error;
|
|
4500
|
-
if (delayMs > 0) await new Promise((
|
|
4675
|
+
if (delayMs > 0) await new Promise((resolve13) => setTimeout(resolve13, delayMs));
|
|
4501
4676
|
}
|
|
4502
4677
|
}
|
|
4503
4678
|
throw lastError;
|
|
@@ -4522,7 +4697,7 @@ var bestEffortPostJson = bestEffortPostRuntimeConnectorJson;
|
|
|
4522
4697
|
|
|
4523
4698
|
// src/amaster-runtime-daemon/runtime-artifact-upload.mjs
|
|
4524
4699
|
import { createHash as createHash3 } from "node:crypto";
|
|
4525
|
-
import { lstatSync as lstatSync3, readFileSync as readFileSync5, realpathSync } from "node:fs";
|
|
4700
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync5, realpathSync as realpathSync2 } from "node:fs";
|
|
4526
4701
|
import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
|
|
4527
4702
|
var SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
4528
4703
|
function requiredString(value, name) {
|
|
@@ -4539,10 +4714,10 @@ function pathWithin(candidate, root) {
|
|
|
4539
4714
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
4540
4715
|
}
|
|
4541
4716
|
function prepareRuntimeArtifactUploads(cwd, mcpToolResults) {
|
|
4542
|
-
const root =
|
|
4717
|
+
const root = realpathSync2(resolve3(cwd));
|
|
4543
4718
|
const uploads = /* @__PURE__ */ new Map();
|
|
4544
|
-
for (const
|
|
4545
|
-
const intent =
|
|
4719
|
+
for (const result3 of Array.isArray(mcpToolResults) ? mcpToolResults : []) {
|
|
4720
|
+
const intent = result3 && typeof result3 === "object" && !Array.isArray(result3) ? result3.artifactIntent : null;
|
|
4546
4721
|
if (!intent || typeof intent !== "object" || Array.isArray(intent)) continue;
|
|
4547
4722
|
const intentId = requiredString(intent.intentId, "intentId");
|
|
4548
4723
|
const manifestId = requiredString(intent.manifestId, "manifestId");
|
|
@@ -4560,7 +4735,7 @@ function prepareRuntimeArtifactUploads(cwd, mcpToolResults) {
|
|
|
4560
4735
|
}
|
|
4561
4736
|
const sourcePath = resolve3(root, sourceRelativePath);
|
|
4562
4737
|
const stat = lstatSync3(sourcePath);
|
|
4563
|
-
if (!stat.isFile() || stat.isSymbolicLink() || !pathWithin(
|
|
4738
|
+
if (!stat.isFile() || stat.isSymbolicLink() || !pathWithin(realpathSync2(sourcePath), root)) {
|
|
4564
4739
|
throw new Error(`Runtime Artifact ${intentId} source is not an owned regular file`);
|
|
4565
4740
|
}
|
|
4566
4741
|
const body = readFileSync5(sourcePath);
|
|
@@ -4600,8 +4775,8 @@ function createRuntimeArtifactIngestQueue({ ingest, onError }) {
|
|
|
4600
4775
|
let queue = Promise.resolve();
|
|
4601
4776
|
return {
|
|
4602
4777
|
enqueue(results) {
|
|
4603
|
-
const pending = results.filter((
|
|
4604
|
-
const intentId = readString(asRecord(
|
|
4778
|
+
const pending = results.filter((result3) => {
|
|
4779
|
+
const intentId = readString(asRecord(result3).artifactIntent?.intentId);
|
|
4605
4780
|
if (!intentId || handledIntentIds.has(intentId)) return false;
|
|
4606
4781
|
handledIntentIds.add(intentId);
|
|
4607
4782
|
return true;
|
|
@@ -4631,7 +4806,7 @@ function createRuntimeArtifactIngestQueue({ ingest, onError }) {
|
|
|
4631
4806
|
|
|
4632
4807
|
// src/amaster-runtime-daemon/workspace-guard.mjs
|
|
4633
4808
|
import { createHash as createHash4 } from "node:crypto";
|
|
4634
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, realpathSync as
|
|
4809
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
|
|
4635
4810
|
import { basename as basename4, join as join7, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4 } from "node:path";
|
|
4636
4811
|
|
|
4637
4812
|
// src/amaster-runtime-daemon/workspace-manifest.mjs
|
|
@@ -4723,10 +4898,10 @@ function resolveWorkspaceCwd(config, command) {
|
|
|
4723
4898
|
const payload = asRecord(command.payload);
|
|
4724
4899
|
const requested = readString(payload.workspacePath);
|
|
4725
4900
|
const fallback = config.workspaceBindings[0] ?? process.cwd();
|
|
4726
|
-
const cwd =
|
|
4901
|
+
const cwd = realpathSync3(resolve4(expandHomePath(requested ?? fallback)));
|
|
4727
4902
|
const allowlist = config.workspaceBindings.flatMap((entry) => {
|
|
4728
4903
|
try {
|
|
4729
|
-
return [
|
|
4904
|
+
return [realpathSync3(resolve4(expandHomePath(entry)))];
|
|
4730
4905
|
} catch {
|
|
4731
4906
|
return [];
|
|
4732
4907
|
}
|
|
@@ -4767,7 +4942,7 @@ function workspaceLabel(sourceWorkspacePath, payload) {
|
|
|
4767
4942
|
function workspacesRoot(config) {
|
|
4768
4943
|
const root = resolve4(expandHomePath(config.runtimeWorkspacesRoot ?? "~/.amaster-employee/workspaces"));
|
|
4769
4944
|
mkdirSync5(root, { recursive: true });
|
|
4770
|
-
return
|
|
4945
|
+
return realpathSync3(root);
|
|
4771
4946
|
}
|
|
4772
4947
|
function resolveExecutionWorkspace(config, command, opts = {}) {
|
|
4773
4948
|
const sourceWorkspacePath = resolveWorkspaceCwd(config, command);
|
|
@@ -4895,7 +5070,7 @@ function planManagedWorkspaceGcDryRun(input) {
|
|
|
4895
5070
|
const ttlMs = ttlHours * 60 * 60 * 1e3;
|
|
4896
5071
|
const activeRunIds = new Set(input.activeRunIds ?? []);
|
|
4897
5072
|
const activeCommandIds = new Set(input.activeCommandIds ?? []);
|
|
4898
|
-
const
|
|
5073
|
+
const result3 = {
|
|
4899
5074
|
dryRun: true,
|
|
4900
5075
|
root,
|
|
4901
5076
|
generatedAt: now.toISOString(),
|
|
@@ -4905,31 +5080,31 @@ function planManagedWorkspaceGcDryRun(input) {
|
|
|
4905
5080
|
candidates: [],
|
|
4906
5081
|
protected: []
|
|
4907
5082
|
};
|
|
4908
|
-
if (!root || !existsSync7(root)) return
|
|
5083
|
+
if (!root || !existsSync7(root)) return result3;
|
|
4909
5084
|
for (const workdir of walkWorkdirs(root)) {
|
|
4910
5085
|
const manifest = readWorkspaceManifest(workspaceManifestPath(workdir));
|
|
4911
5086
|
if (!manifest || manifest.managed !== true) continue;
|
|
4912
5087
|
const summary = summarizeWorkdir(workdir, manifest, nowMs);
|
|
4913
|
-
|
|
5088
|
+
result3.totalWorkdirs += 1;
|
|
4914
5089
|
const lastTouchedTime = readIsoTime(summary.lastTouchedAt);
|
|
4915
5090
|
const active = Boolean(
|
|
4916
5091
|
summary.runId && activeRunIds.has(summary.runId) || summary.commandId && activeCommandIds.has(summary.commandId)
|
|
4917
5092
|
);
|
|
4918
5093
|
if (active) {
|
|
4919
|
-
|
|
5094
|
+
result3.protected.push({ ...summary, reason: "active_run_or_command" });
|
|
4920
5095
|
continue;
|
|
4921
5096
|
}
|
|
4922
5097
|
if (lastTouchedTime !== null && nowMs - lastTouchedTime < ttlMs) {
|
|
4923
|
-
|
|
5098
|
+
result3.protected.push({ ...summary, reason: "within_ttl" });
|
|
4924
5099
|
continue;
|
|
4925
5100
|
}
|
|
4926
|
-
|
|
4927
|
-
|
|
5101
|
+
result3.totalCandidateBytes += summary.sizeBytes;
|
|
5102
|
+
result3.candidates.push({
|
|
4928
5103
|
...summary,
|
|
4929
5104
|
reason: lastTouchedTime === null ? "missing_last_touched_at" : "older_than_ttl"
|
|
4930
5105
|
});
|
|
4931
5106
|
}
|
|
4932
|
-
return
|
|
5107
|
+
return result3;
|
|
4933
5108
|
}
|
|
4934
5109
|
|
|
4935
5110
|
// src/amaster-runtime-daemon/runtime-status-summary.mjs
|
|
@@ -6252,8 +6427,1724 @@ function readWorkspaceStatus(cwd, opts = {}) {
|
|
|
6252
6427
|
};
|
|
6253
6428
|
}
|
|
6254
6429
|
|
|
6430
|
+
// src/amaster-runtime-daemon/source-read-command.mjs
|
|
6431
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
6432
|
+
|
|
6433
|
+
// src/amaster-runtime-daemon/constrained-source-reader.mjs
|
|
6434
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
6435
|
+
var CONSTRAINED_SOURCE_READER_CONTRACT_VERSION = "amaster.constrained-source-reader.v1";
|
|
6436
|
+
var DEFAULT_LIMITS = Object.freeze({
|
|
6437
|
+
maxPages: 10,
|
|
6438
|
+
maxSnapshotChars: 2e5,
|
|
6439
|
+
maxTotalSnapshotChars: 5e5
|
|
6440
|
+
});
|
|
6441
|
+
var REQUEST_FIELDS = /* @__PURE__ */ new Set([
|
|
6442
|
+
"sourceRevisionId",
|
|
6443
|
+
"locator",
|
|
6444
|
+
"declaredPageLocators",
|
|
6445
|
+
"allowedResourceOrigins",
|
|
6446
|
+
"pageId",
|
|
6447
|
+
"limits"
|
|
6448
|
+
]);
|
|
6449
|
+
var LIMIT_FIELDS = new Set(Object.keys(DEFAULT_LIMITS));
|
|
6450
|
+
var ERROR_METADATA_FIELDS = /* @__PURE__ */ new Set([
|
|
6451
|
+
"sourceRevisionId",
|
|
6452
|
+
"origin",
|
|
6453
|
+
"pathname",
|
|
6454
|
+
"pageIndex",
|
|
6455
|
+
"operation"
|
|
6456
|
+
]);
|
|
6457
|
+
function isRecord2(value) {
|
|
6458
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
6459
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6460
|
+
return prototype === Object.prototype || prototype === null;
|
|
6461
|
+
}
|
|
6462
|
+
function boundedMetadataValue(key, value) {
|
|
6463
|
+
if (key === "pageIndex") return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
6464
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
6465
|
+
return value.slice(0, 512);
|
|
6466
|
+
}
|
|
6467
|
+
function sanitizeErrorMetadata(metadata) {
|
|
6468
|
+
if (!isRecord2(metadata)) return {};
|
|
6469
|
+
const sanitized = {};
|
|
6470
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
6471
|
+
if (!ERROR_METADATA_FIELDS.has(key)) continue;
|
|
6472
|
+
const bounded = boundedMetadataValue(key, value);
|
|
6473
|
+
if (bounded !== void 0) sanitized[key] = bounded;
|
|
6474
|
+
}
|
|
6475
|
+
return sanitized;
|
|
6476
|
+
}
|
|
6477
|
+
var ConstrainedSourceReaderError = class extends Error {
|
|
6478
|
+
constructor(code, metadata = {}) {
|
|
6479
|
+
super(code);
|
|
6480
|
+
this.name = "ConstrainedSourceReaderError";
|
|
6481
|
+
this.code = code;
|
|
6482
|
+
this.metadata = sanitizeErrorMetadata(metadata);
|
|
6483
|
+
}
|
|
6484
|
+
};
|
|
6485
|
+
function fail(code, metadata) {
|
|
6486
|
+
throw new ConstrainedSourceReaderError(code, metadata);
|
|
6487
|
+
}
|
|
6488
|
+
function locatorMetadata(sourceRevisionId, url, pageIndex) {
|
|
6489
|
+
return {
|
|
6490
|
+
sourceRevisionId,
|
|
6491
|
+
origin: url?.origin,
|
|
6492
|
+
pathname: url?.pathname,
|
|
6493
|
+
pageIndex
|
|
6494
|
+
};
|
|
6495
|
+
}
|
|
6496
|
+
function parseHttpLocator(value, { sourceRevisionId, pageIndex, invalidCode }) {
|
|
6497
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
6498
|
+
fail(invalidCode, { sourceRevisionId, pageIndex });
|
|
6499
|
+
}
|
|
6500
|
+
const locator = value.trim();
|
|
6501
|
+
let url;
|
|
6502
|
+
try {
|
|
6503
|
+
url = new URL(locator);
|
|
6504
|
+
} catch {
|
|
6505
|
+
fail(invalidCode, { sourceRevisionId, pageIndex });
|
|
6506
|
+
}
|
|
6507
|
+
if (url.username || url.password) {
|
|
6508
|
+
fail(
|
|
6509
|
+
"source_reader_locator_credentials_forbidden",
|
|
6510
|
+
locatorMetadata(sourceRevisionId, url, pageIndex)
|
|
6511
|
+
);
|
|
6512
|
+
}
|
|
6513
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
6514
|
+
fail(invalidCode, locatorMetadata(sourceRevisionId, url, pageIndex));
|
|
6515
|
+
}
|
|
6516
|
+
return { locator, url };
|
|
6517
|
+
}
|
|
6518
|
+
function normalizeLimits(value, sourceRevisionId) {
|
|
6519
|
+
if (value === void 0) return { ...DEFAULT_LIMITS };
|
|
6520
|
+
if (!isRecord2(value) || Object.keys(value).some((key) => !LIMIT_FIELDS.has(key))) {
|
|
6521
|
+
fail("source_reader_limit_invalid", { sourceRevisionId });
|
|
6522
|
+
}
|
|
6523
|
+
const limits = { ...DEFAULT_LIMITS };
|
|
6524
|
+
for (const [key, ceiling] of Object.entries(DEFAULT_LIMITS)) {
|
|
6525
|
+
if (!(key in value)) continue;
|
|
6526
|
+
const candidate = value[key];
|
|
6527
|
+
if (!Number.isSafeInteger(candidate) || candidate <= 0 || candidate > ceiling) {
|
|
6528
|
+
fail("source_reader_limit_invalid", { sourceRevisionId });
|
|
6529
|
+
}
|
|
6530
|
+
limits[key] = candidate;
|
|
6531
|
+
}
|
|
6532
|
+
return limits;
|
|
6533
|
+
}
|
|
6534
|
+
function normalizeResourceOrigins(value, sourceRevisionId) {
|
|
6535
|
+
if (value === void 0) return [];
|
|
6536
|
+
if (!Array.isArray(value)) {
|
|
6537
|
+
fail("source_reader_request_invalid", { sourceRevisionId });
|
|
6538
|
+
}
|
|
6539
|
+
const normalized = [];
|
|
6540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6541
|
+
for (const resourceOrigin of value) {
|
|
6542
|
+
const { url } = parseHttpLocator(resourceOrigin, {
|
|
6543
|
+
sourceRevisionId,
|
|
6544
|
+
invalidCode: "source_reader_resource_origin_invalid"
|
|
6545
|
+
});
|
|
6546
|
+
if (url.pathname !== "/" || url.search || url.hash) {
|
|
6547
|
+
fail(
|
|
6548
|
+
"source_reader_resource_origin_invalid",
|
|
6549
|
+
locatorMetadata(sourceRevisionId, url)
|
|
6550
|
+
);
|
|
6551
|
+
}
|
|
6552
|
+
if (!seen.has(url.origin)) {
|
|
6553
|
+
seen.add(url.origin);
|
|
6554
|
+
normalized.push(url.origin);
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
return normalized;
|
|
6558
|
+
}
|
|
6559
|
+
function normalizeConstrainedSourceReadRequest(input) {
|
|
6560
|
+
if (!isRecord2(input) || Object.keys(input).some((key) => !REQUEST_FIELDS.has(key))) {
|
|
6561
|
+
fail("source_reader_request_invalid");
|
|
6562
|
+
}
|
|
6563
|
+
const sourceRevisionId = typeof input.sourceRevisionId === "string" ? input.sourceRevisionId.trim() : "";
|
|
6564
|
+
if (!sourceRevisionId) fail("source_reader_request_invalid");
|
|
6565
|
+
if (!Number.isSafeInteger(input.pageId) || input.pageId <= 0) {
|
|
6566
|
+
fail("source_reader_request_invalid", { sourceRevisionId });
|
|
6567
|
+
}
|
|
6568
|
+
if (input.declaredPageLocators !== void 0 && !Array.isArray(input.declaredPageLocators)) {
|
|
6569
|
+
fail("source_reader_request_invalid", { sourceRevisionId });
|
|
6570
|
+
}
|
|
6571
|
+
const limits = normalizeLimits(input.limits, sourceRevisionId);
|
|
6572
|
+
const primary = parseHttpLocator(input.locator, {
|
|
6573
|
+
sourceRevisionId,
|
|
6574
|
+
pageIndex: 0,
|
|
6575
|
+
invalidCode: "source_reader_request_invalid"
|
|
6576
|
+
});
|
|
6577
|
+
const declaredInputs = input.declaredPageLocators ?? [];
|
|
6578
|
+
const declared = declaredInputs.map((locator, index) => parseHttpLocator(locator, {
|
|
6579
|
+
sourceRevisionId,
|
|
6580
|
+
pageIndex: index + 1,
|
|
6581
|
+
invalidCode: "source_reader_request_invalid"
|
|
6582
|
+
}));
|
|
6583
|
+
const locators = [primary, ...declared];
|
|
6584
|
+
if (locators.length > limits.maxPages) {
|
|
6585
|
+
fail("source_reader_limit_invalid", { sourceRevisionId });
|
|
6586
|
+
}
|
|
6587
|
+
const seenLocators = /* @__PURE__ */ new Set();
|
|
6588
|
+
for (const [pageIndex, entry] of locators.entries()) {
|
|
6589
|
+
if (entry.url.origin !== primary.url.origin) {
|
|
6590
|
+
fail(
|
|
6591
|
+
"source_reader_navigation_scope_forbidden",
|
|
6592
|
+
locatorMetadata(sourceRevisionId, entry.url, pageIndex)
|
|
6593
|
+
);
|
|
6594
|
+
}
|
|
6595
|
+
if (seenLocators.has(entry.url.href)) {
|
|
6596
|
+
fail(
|
|
6597
|
+
"source_reader_request_invalid",
|
|
6598
|
+
locatorMetadata(sourceRevisionId, entry.url, pageIndex)
|
|
6599
|
+
);
|
|
6600
|
+
}
|
|
6601
|
+
seenLocators.add(entry.url.href);
|
|
6602
|
+
}
|
|
6603
|
+
return {
|
|
6604
|
+
contractVersion: CONSTRAINED_SOURCE_READER_CONTRACT_VERSION,
|
|
6605
|
+
sourceRevisionId,
|
|
6606
|
+
locator: primary.locator,
|
|
6607
|
+
declaredPageLocators: declared.map((entry) => entry.locator),
|
|
6608
|
+
locators: locators.map((entry) => entry.locator),
|
|
6609
|
+
contentOrigin: primary.url.origin,
|
|
6610
|
+
allowedResourceOrigins: normalizeResourceOrigins(
|
|
6611
|
+
input.allowedResourceOrigins,
|
|
6612
|
+
sourceRevisionId
|
|
6613
|
+
),
|
|
6614
|
+
pageId: input.pageId,
|
|
6615
|
+
limits
|
|
6616
|
+
};
|
|
6617
|
+
}
|
|
6618
|
+
var TRANSPORT_METHODS = Object.freeze([
|
|
6619
|
+
"listPages",
|
|
6620
|
+
"navigatePage",
|
|
6621
|
+
"takeSnapshot"
|
|
6622
|
+
]);
|
|
6623
|
+
var RESULT_OPERATION_NAMES = Object.freeze([
|
|
6624
|
+
"list_pages",
|
|
6625
|
+
"navigate_page",
|
|
6626
|
+
"take_snapshot"
|
|
6627
|
+
]);
|
|
6628
|
+
var UNSUPPORTED_INTERACTIONS = /* @__PURE__ */ new Set([
|
|
6629
|
+
"scroll_required",
|
|
6630
|
+
"click_required",
|
|
6631
|
+
"evaluate_required",
|
|
6632
|
+
"virtualized_content"
|
|
6633
|
+
]);
|
|
6634
|
+
var SAFE_TRANSPORT_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
6635
|
+
"source_reader_auth_required",
|
|
6636
|
+
"source_reader_no_access",
|
|
6637
|
+
"source_reader_network_scope_forbidden",
|
|
6638
|
+
"source_reader_download_forbidden"
|
|
6639
|
+
]);
|
|
6640
|
+
function transportFunctionNames(transport) {
|
|
6641
|
+
const names = /* @__PURE__ */ new Set();
|
|
6642
|
+
let target = transport;
|
|
6643
|
+
while (target && target !== Object.prototype) {
|
|
6644
|
+
for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(target))) {
|
|
6645
|
+
if (name !== "constructor" && typeof descriptor.value === "function") names.add(name);
|
|
6646
|
+
}
|
|
6647
|
+
target = Object.getPrototypeOf(target);
|
|
6648
|
+
}
|
|
6649
|
+
return [...names].sort();
|
|
6650
|
+
}
|
|
6651
|
+
function assertTransportSurface(transport, sourceRevisionId) {
|
|
6652
|
+
if (!isRecord2(transport) && (typeof transport !== "object" || transport === null)) {
|
|
6653
|
+
fail("source_reader_transport_invalid", { sourceRevisionId });
|
|
6654
|
+
}
|
|
6655
|
+
const methods = transportFunctionNames(transport);
|
|
6656
|
+
if (methods.length !== TRANSPORT_METHODS.length || TRANSPORT_METHODS.some((method) => !methods.includes(method))) {
|
|
6657
|
+
fail("source_reader_transport_invalid", { sourceRevisionId });
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
function readSignal(value) {
|
|
6661
|
+
if (value === void 0) return new AbortController().signal;
|
|
6662
|
+
if (typeof value !== "object" || value === null || typeof value.aborted !== "boolean" || typeof value.addEventListener !== "function") {
|
|
6663
|
+
fail("source_reader_request_invalid");
|
|
6664
|
+
}
|
|
6665
|
+
return value;
|
|
6666
|
+
}
|
|
6667
|
+
function checkAborted(signal, sourceRevisionId, operation) {
|
|
6668
|
+
if (signal.aborted) fail("source_reader_aborted", { sourceRevisionId, operation });
|
|
6669
|
+
}
|
|
6670
|
+
async function callTransport({ sourceRevisionId, signal, operation, action }) {
|
|
6671
|
+
checkAborted(signal, sourceRevisionId, operation);
|
|
6672
|
+
try {
|
|
6673
|
+
const result3 = await action();
|
|
6674
|
+
checkAborted(signal, sourceRevisionId, operation);
|
|
6675
|
+
if (isRecord2(result3) && result3.isError === true) {
|
|
6676
|
+
fail("source_reader_transport_failed", { sourceRevisionId, operation });
|
|
6677
|
+
}
|
|
6678
|
+
return result3;
|
|
6679
|
+
} catch (error) {
|
|
6680
|
+
if (error instanceof ConstrainedSourceReaderError) throw error;
|
|
6681
|
+
if (SAFE_TRANSPORT_FAILURE_CODES.has(error?.code)) {
|
|
6682
|
+
fail(error.code, { sourceRevisionId, operation });
|
|
6683
|
+
}
|
|
6684
|
+
if (signal.aborted || error?.name === "AbortError") {
|
|
6685
|
+
fail("source_reader_aborted", { sourceRevisionId, operation });
|
|
6686
|
+
}
|
|
6687
|
+
fail("source_reader_transport_failed", { sourceRevisionId, operation });
|
|
6688
|
+
}
|
|
6689
|
+
}
|
|
6690
|
+
function normalizePageRows(value, sourceRevisionId) {
|
|
6691
|
+
if (!Array.isArray(value)) {
|
|
6692
|
+
fail("source_reader_transport_invalid", { sourceRevisionId, operation: "list_pages" });
|
|
6693
|
+
}
|
|
6694
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6695
|
+
return value.map((row) => {
|
|
6696
|
+
if (!isRecord2(row) || !Number.isSafeInteger(row.pageId) || row.pageId <= 0 || typeof row.url !== "string" || seen.has(row.pageId)) {
|
|
6697
|
+
fail("source_reader_transport_invalid", { sourceRevisionId, operation: "list_pages" });
|
|
6698
|
+
}
|
|
6699
|
+
seen.add(row.pageId);
|
|
6700
|
+
return { pageId: row.pageId, url: row.url };
|
|
6701
|
+
});
|
|
6702
|
+
}
|
|
6703
|
+
function pageIdSet(rows) {
|
|
6704
|
+
return rows.map((row) => row.pageId).sort((left, right) => left - right);
|
|
6705
|
+
}
|
|
6706
|
+
function assertStableTargetSet(before, after, request) {
|
|
6707
|
+
if (!after.some((row) => row.pageId === request.pageId)) {
|
|
6708
|
+
fail("source_reader_target_missing", { sourceRevisionId: request.sourceRevisionId });
|
|
6709
|
+
}
|
|
6710
|
+
if (before.length !== after.length || pageIdSet(before).some((pageId, index) => pageId !== pageIdSet(after)[index])) {
|
|
6711
|
+
fail("source_reader_target_set_changed", { sourceRevisionId: request.sourceRevisionId });
|
|
6712
|
+
}
|
|
6713
|
+
}
|
|
6714
|
+
function parseObservedUrl(value, request, pageIndex, operation) {
|
|
6715
|
+
const { url } = parseHttpLocator(value, {
|
|
6716
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6717
|
+
pageIndex,
|
|
6718
|
+
invalidCode: "source_reader_transport_invalid"
|
|
6719
|
+
});
|
|
6720
|
+
if (url.origin !== request.contentOrigin) {
|
|
6721
|
+
fail(
|
|
6722
|
+
"source_reader_cross_scope_redirect",
|
|
6723
|
+
{ ...locatorMetadata(request.sourceRevisionId, url, pageIndex), operation }
|
|
6724
|
+
);
|
|
6725
|
+
}
|
|
6726
|
+
return url;
|
|
6727
|
+
}
|
|
6728
|
+
async function assertNetworkAllowed(locator, dependencies, request, pageIndex, operation) {
|
|
6729
|
+
let allowed;
|
|
6730
|
+
try {
|
|
6731
|
+
allowed = await dependencies.assertNetworkAddressAllowed(locator);
|
|
6732
|
+
} catch {
|
|
6733
|
+
const url = (() => {
|
|
6734
|
+
try {
|
|
6735
|
+
return new URL(locator);
|
|
6736
|
+
} catch {
|
|
6737
|
+
return null;
|
|
6738
|
+
}
|
|
6739
|
+
})();
|
|
6740
|
+
fail(
|
|
6741
|
+
"source_reader_network_scope_forbidden",
|
|
6742
|
+
{ ...locatorMetadata(request.sourceRevisionId, url, pageIndex), operation }
|
|
6743
|
+
);
|
|
6744
|
+
}
|
|
6745
|
+
if (allowed === false) {
|
|
6746
|
+
const url = new URL(locator);
|
|
6747
|
+
fail(
|
|
6748
|
+
"source_reader_network_scope_forbidden",
|
|
6749
|
+
{ ...locatorMetadata(request.sourceRevisionId, url, pageIndex), operation }
|
|
6750
|
+
);
|
|
6751
|
+
}
|
|
6752
|
+
}
|
|
6753
|
+
function normalizeNavigationResult(value, request, pageIndex) {
|
|
6754
|
+
if (!isRecord2(value) || typeof value.finalUrl !== "string" || !Array.isArray(value.redirectChain) || value.redirectChain.some((entry) => typeof entry !== "string") || !Array.isArray(value.resourceOrigins) || value.resourceOrigins.some((entry) => typeof entry !== "string") || typeof value.downloadAttempted !== "boolean") {
|
|
6755
|
+
fail("source_reader_transport_invalid", {
|
|
6756
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6757
|
+
pageIndex,
|
|
6758
|
+
operation: "navigate_page"
|
|
6759
|
+
});
|
|
6760
|
+
}
|
|
6761
|
+
return {
|
|
6762
|
+
finalUrl: value.finalUrl,
|
|
6763
|
+
redirectChain: [...value.redirectChain],
|
|
6764
|
+
resourceOrigins: [...value.resourceOrigins],
|
|
6765
|
+
downloadAttempted: value.downloadAttempted
|
|
6766
|
+
};
|
|
6767
|
+
}
|
|
6768
|
+
function normalizeSnapshotResult(value, request, pageIndex) {
|
|
6769
|
+
if (!isRecord2(value) || typeof value.text !== "string" || typeof value.truncated !== "boolean" || !(value.unsupportedInteraction === null || UNSUPPORTED_INTERACTIONS.has(value.unsupportedInteraction))) {
|
|
6770
|
+
fail("source_reader_transport_invalid", {
|
|
6771
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6772
|
+
pageIndex,
|
|
6773
|
+
operation: "take_snapshot"
|
|
6774
|
+
});
|
|
6775
|
+
}
|
|
6776
|
+
return {
|
|
6777
|
+
text: value.text,
|
|
6778
|
+
truncated: value.truncated,
|
|
6779
|
+
unsupportedInteraction: value.unsupportedInteraction
|
|
6780
|
+
};
|
|
6781
|
+
}
|
|
6782
|
+
function timestamp(now, sourceRevisionId) {
|
|
6783
|
+
const value = now();
|
|
6784
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
6785
|
+
if (!Number.isFinite(date.getTime())) {
|
|
6786
|
+
fail("source_reader_request_invalid", { sourceRevisionId });
|
|
6787
|
+
}
|
|
6788
|
+
return date.toISOString();
|
|
6789
|
+
}
|
|
6790
|
+
async function listPages(dependencies, request, signal) {
|
|
6791
|
+
const value = await callTransport({
|
|
6792
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6793
|
+
signal,
|
|
6794
|
+
operation: "list_pages",
|
|
6795
|
+
action: () => dependencies.transport.listPages({ signal })
|
|
6796
|
+
});
|
|
6797
|
+
return normalizePageRows(value, request.sourceRevisionId);
|
|
6798
|
+
}
|
|
6799
|
+
async function validateNavigationResult(result3, dependencies, request, pageIndex) {
|
|
6800
|
+
if (result3.downloadAttempted) {
|
|
6801
|
+
fail("source_reader_download_forbidden", {
|
|
6802
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6803
|
+
pageIndex,
|
|
6804
|
+
operation: "navigate_page"
|
|
6805
|
+
});
|
|
6806
|
+
}
|
|
6807
|
+
for (const locator of [...result3.redirectChain, result3.finalUrl]) {
|
|
6808
|
+
parseObservedUrl(locator, request, pageIndex, "navigate_page");
|
|
6809
|
+
await assertNetworkAllowed(locator, dependencies, request, pageIndex, "navigate_page");
|
|
6810
|
+
}
|
|
6811
|
+
const allowedOrigins = /* @__PURE__ */ new Set([request.contentOrigin, ...request.allowedResourceOrigins]);
|
|
6812
|
+
for (const resourceOrigin of result3.resourceOrigins) {
|
|
6813
|
+
const { url } = parseHttpLocator(resourceOrigin, {
|
|
6814
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6815
|
+
pageIndex,
|
|
6816
|
+
invalidCode: "source_reader_transport_invalid"
|
|
6817
|
+
});
|
|
6818
|
+
if (url.pathname !== "/" || url.search || url.hash) {
|
|
6819
|
+
fail("source_reader_transport_invalid", {
|
|
6820
|
+
...locatorMetadata(request.sourceRevisionId, url, pageIndex),
|
|
6821
|
+
operation: "navigate_page"
|
|
6822
|
+
});
|
|
6823
|
+
}
|
|
6824
|
+
if (!allowedOrigins.has(url.origin)) {
|
|
6825
|
+
fail("source_reader_resource_origin_forbidden", {
|
|
6826
|
+
...locatorMetadata(request.sourceRevisionId, url, pageIndex),
|
|
6827
|
+
operation: "navigate_page"
|
|
6828
|
+
});
|
|
6829
|
+
}
|
|
6830
|
+
await assertNetworkAllowed(url.origin, dependencies, request, pageIndex, "navigate_page");
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
async function readConstrainedSource(input, dependencies) {
|
|
6834
|
+
const request = normalizeConstrainedSourceReadRequest(input);
|
|
6835
|
+
if (!isRecord2(dependencies) || typeof dependencies.assertNetworkAddressAllowed !== "function" || dependencies.now !== void 0 && typeof dependencies.now !== "function") {
|
|
6836
|
+
fail("source_reader_request_invalid", { sourceRevisionId: request.sourceRevisionId });
|
|
6837
|
+
}
|
|
6838
|
+
assertTransportSurface(dependencies.transport, request.sourceRevisionId);
|
|
6839
|
+
const signal = readSignal(dependencies.signal);
|
|
6840
|
+
const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
|
|
6841
|
+
const startedAt = timestamp(now, request.sourceRevisionId);
|
|
6842
|
+
const pages = [];
|
|
6843
|
+
let totalSnapshotChars = 0;
|
|
6844
|
+
let coverageGap = null;
|
|
6845
|
+
for (const [pageIndex, locator] of request.locators.entries()) {
|
|
6846
|
+
checkAborted(signal, request.sourceRevisionId, "navigate_page");
|
|
6847
|
+
await assertNetworkAllowed(locator, dependencies, request, pageIndex, "navigate_page");
|
|
6848
|
+
const beforeNavigation = await listPages(dependencies, request, signal);
|
|
6849
|
+
if (!beforeNavigation.some((row) => row.pageId === request.pageId)) {
|
|
6850
|
+
fail("source_reader_target_missing", {
|
|
6851
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6852
|
+
pageIndex,
|
|
6853
|
+
operation: "list_pages"
|
|
6854
|
+
});
|
|
6855
|
+
}
|
|
6856
|
+
const navigationValue = await callTransport({
|
|
6857
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6858
|
+
signal,
|
|
6859
|
+
operation: "navigate_page",
|
|
6860
|
+
action: () => dependencies.transport.navigatePage({
|
|
6861
|
+
pageId: request.pageId,
|
|
6862
|
+
locator,
|
|
6863
|
+
allowedResourceOrigins: [...request.allowedResourceOrigins],
|
|
6864
|
+
signal
|
|
6865
|
+
})
|
|
6866
|
+
});
|
|
6867
|
+
const navigation = normalizeNavigationResult(navigationValue, request, pageIndex);
|
|
6868
|
+
await validateNavigationResult(navigation, dependencies, request, pageIndex);
|
|
6869
|
+
const afterNavigation = await listPages(dependencies, request, signal);
|
|
6870
|
+
assertStableTargetSet(beforeNavigation, afterNavigation, request);
|
|
6871
|
+
const snapshotValue = await callTransport({
|
|
6872
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6873
|
+
signal,
|
|
6874
|
+
operation: "take_snapshot",
|
|
6875
|
+
action: () => dependencies.transport.takeSnapshot({ pageId: request.pageId, signal })
|
|
6876
|
+
});
|
|
6877
|
+
const snapshot = normalizeSnapshotResult(snapshotValue, request, pageIndex);
|
|
6878
|
+
const afterSnapshot = await listPages(dependencies, request, signal);
|
|
6879
|
+
assertStableTargetSet(beforeNavigation, afterSnapshot, request);
|
|
6880
|
+
if (snapshot.text.length > request.limits.maxSnapshotChars || totalSnapshotChars + snapshot.text.length > request.limits.maxTotalSnapshotChars) {
|
|
6881
|
+
fail("source_reader_snapshot_oversized", {
|
|
6882
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6883
|
+
pageIndex,
|
|
6884
|
+
operation: "take_snapshot"
|
|
6885
|
+
});
|
|
6886
|
+
}
|
|
6887
|
+
totalSnapshotChars += snapshot.text.length;
|
|
6888
|
+
pages.push({
|
|
6889
|
+
pageIndex,
|
|
6890
|
+
locator,
|
|
6891
|
+
finalUrl: navigation.finalUrl,
|
|
6892
|
+
capturedAt: timestamp(now, request.sourceRevisionId),
|
|
6893
|
+
text: snapshot.text,
|
|
6894
|
+
charCount: snapshot.text.length,
|
|
6895
|
+
contentHash: createHash9("sha256").update(snapshot.text, "utf8").digest("hex")
|
|
6896
|
+
});
|
|
6897
|
+
if (snapshot.truncated || snapshot.unsupportedInteraction) {
|
|
6898
|
+
coverageGap = {
|
|
6899
|
+
code: "unsupported_interaction",
|
|
6900
|
+
pageIndex,
|
|
6901
|
+
reason: snapshot.unsupportedInteraction ?? "scroll_required"
|
|
6902
|
+
};
|
|
6903
|
+
break;
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
const result3 = {
|
|
6907
|
+
contractVersion: CONSTRAINED_SOURCE_READER_CONTRACT_VERSION,
|
|
6908
|
+
sourceRevisionId: request.sourceRevisionId,
|
|
6909
|
+
status: coverageGap ? "partial" : "complete",
|
|
6910
|
+
pages,
|
|
6911
|
+
totalSnapshotChars,
|
|
6912
|
+
operationNames: [...RESULT_OPERATION_NAMES],
|
|
6913
|
+
startedAt,
|
|
6914
|
+
completedAt: timestamp(now, request.sourceRevisionId),
|
|
6915
|
+
passiveEffectsPossible: true
|
|
6916
|
+
};
|
|
6917
|
+
if (coverageGap) result3.coverageGap = coverageGap;
|
|
6918
|
+
return result3;
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
// src/amaster-runtime-daemon/source-read-command.mjs
|
|
6922
|
+
var SOURCE_SNAPSHOT_REDACTION_VERSION = "amaster.source-snapshot-redaction.v1";
|
|
6923
|
+
var COMMAND_PAYLOAD_FIELDS = /* @__PURE__ */ new Set([
|
|
6924
|
+
"contractVersion",
|
|
6925
|
+
"companyId",
|
|
6926
|
+
"sourceId",
|
|
6927
|
+
"sourceRevisionId",
|
|
6928
|
+
"sourceRevision",
|
|
6929
|
+
"locator",
|
|
6930
|
+
"declaredPageLocators",
|
|
6931
|
+
"allowedResourceOrigins",
|
|
6932
|
+
"limits",
|
|
6933
|
+
"authentication"
|
|
6934
|
+
]);
|
|
6935
|
+
var AUTHENTICATION_FIELDS = /* @__PURE__ */ new Set([
|
|
6936
|
+
"bindingId",
|
|
6937
|
+
"actionId",
|
|
6938
|
+
"interactionId",
|
|
6939
|
+
"localOpaqueRef",
|
|
6940
|
+
"provider",
|
|
6941
|
+
"origin",
|
|
6942
|
+
"leaseId",
|
|
6943
|
+
"leaseExpiresAt"
|
|
6944
|
+
]);
|
|
6945
|
+
var RESULT_OPERATION_NAMES2 = Object.freeze([
|
|
6946
|
+
"list_pages",
|
|
6947
|
+
"navigate_page",
|
|
6948
|
+
"take_snapshot"
|
|
6949
|
+
]);
|
|
6950
|
+
var REDACTION_CATEGORIES = Object.freeze([
|
|
6951
|
+
"credential",
|
|
6952
|
+
"token",
|
|
6953
|
+
"privateKey",
|
|
6954
|
+
"urlSecret",
|
|
6955
|
+
"highEntropy"
|
|
6956
|
+
]);
|
|
6957
|
+
var REDACTED = "[REDACTED]";
|
|
6958
|
+
function isRecord3(value) {
|
|
6959
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
6960
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6961
|
+
return prototype === Object.prototype || prototype === null;
|
|
6962
|
+
}
|
|
6963
|
+
function readIdentityString(value) {
|
|
6964
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
6965
|
+
}
|
|
6966
|
+
function normalizeAuthentication(value) {
|
|
6967
|
+
if (value === void 0) return void 0;
|
|
6968
|
+
if (!isRecord3(value) || Object.keys(value).some((key) => !AUTHENTICATION_FIELDS.has(key)) || !readIdentityString(value.bindingId) || !readIdentityString(value.actionId) || !readIdentityString(value.interactionId) || typeof value.localOpaqueRef !== "string" || !/^profile_[a-z0-9]{16,64}$/u.test(value.localOpaqueRef) || !readIdentityString(value.provider) || !readIdentityString(value.origin) || !readIdentityString(value.leaseId) || !readIdentityString(value.leaseExpiresAt) || !Number.isFinite(Date.parse(value.leaseExpiresAt))) {
|
|
6969
|
+
throw Object.assign(new Error("source_reader_command_invalid"), {
|
|
6970
|
+
code: "source_reader_command_invalid"
|
|
6971
|
+
});
|
|
6972
|
+
}
|
|
6973
|
+
return { ...value };
|
|
6974
|
+
}
|
|
6975
|
+
function sha256(value) {
|
|
6976
|
+
return createHash10("sha256").update(value, "utf8").digest("hex");
|
|
6977
|
+
}
|
|
6978
|
+
function safeTimestamp(now) {
|
|
6979
|
+
try {
|
|
6980
|
+
const value = now();
|
|
6981
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
6982
|
+
if (Number.isFinite(date.getTime())) return date.toISOString();
|
|
6983
|
+
} catch {
|
|
6984
|
+
}
|
|
6985
|
+
return "1970-01-01T00:00:00.000Z";
|
|
6986
|
+
}
|
|
6987
|
+
function emptyRedactionSummary() {
|
|
6988
|
+
return {
|
|
6989
|
+
replacements: 0,
|
|
6990
|
+
categories: Object.fromEntries(REDACTION_CATEGORIES.map((category) => [category, 0]))
|
|
6991
|
+
};
|
|
6992
|
+
}
|
|
6993
|
+
function scrubSnapshotText(input) {
|
|
6994
|
+
let text = input;
|
|
6995
|
+
const summary = emptyRedactionSummary();
|
|
6996
|
+
const replace = (category, pattern, replacement) => {
|
|
6997
|
+
text = text.replace(pattern, (...args) => {
|
|
6998
|
+
summary.replacements += 1;
|
|
6999
|
+
summary.categories[category] += 1;
|
|
7000
|
+
return typeof replacement === "function" ? replacement(...args) : replacement;
|
|
7001
|
+
});
|
|
7002
|
+
};
|
|
7003
|
+
replace(
|
|
7004
|
+
"privateKey",
|
|
7005
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/gu,
|
|
7006
|
+
REDACTED
|
|
7007
|
+
);
|
|
7008
|
+
replace(
|
|
7009
|
+
"urlSecret",
|
|
7010
|
+
/([?&](?:access_token|api[_-]?key|auth|code|credential|key|password|secret|signature|token)=)[^&#\s]+/giu,
|
|
7011
|
+
(_match, prefix) => `${prefix}${REDACTED}`
|
|
7012
|
+
);
|
|
7013
|
+
replace(
|
|
7014
|
+
"token",
|
|
7015
|
+
/\b(?:authorization\s*:\s*)?bearer\s+[A-Za-z0-9._~+/=-]{8,}/giu,
|
|
7016
|
+
REDACTED
|
|
7017
|
+
);
|
|
7018
|
+
replace(
|
|
7019
|
+
"token",
|
|
7020
|
+
/\b(api[_-]?key|access[_-]?token|auth[_-]?token)\s*[:=]\s*[^\s,;]+/giu,
|
|
7021
|
+
(_match, label) => `${label}=${REDACTED}`
|
|
7022
|
+
);
|
|
7023
|
+
replace(
|
|
7024
|
+
"credential",
|
|
7025
|
+
/\b(password|passwd|pwd|credential|client[_-]?secret)\s*[:=]\s*[^\s,;]+/giu,
|
|
7026
|
+
(_match, label) => `${label}=${REDACTED}`
|
|
7027
|
+
);
|
|
7028
|
+
text = text.replace(/[A-Za-z0-9_-]{32,}/gu, (candidate) => {
|
|
7029
|
+
if (!/[A-Za-z]/u.test(candidate) || !/[0-9]/u.test(candidate)) return candidate;
|
|
7030
|
+
summary.replacements += 1;
|
|
7031
|
+
summary.categories.highEntropy += 1;
|
|
7032
|
+
return REDACTED;
|
|
7033
|
+
});
|
|
7034
|
+
return { text, summary };
|
|
7035
|
+
}
|
|
7036
|
+
function normalizeCommand(command) {
|
|
7037
|
+
if (!isRecord3(command) || !isRecord3(command.payload)) throw Object.assign(new Error("source_reader_command_invalid"), { code: "source_reader_command_invalid" });
|
|
7038
|
+
const payload = command.payload;
|
|
7039
|
+
if (Object.keys(payload).some((key) => !COMMAND_PAYLOAD_FIELDS.has(key)) || payload.contractVersion !== CONSTRAINED_SOURCE_READER_CONTRACT_VERSION || !readIdentityString(command.commandId) || !readIdentityString(command.connectorId) || !readIdentityString(payload.companyId) || !readIdentityString(payload.sourceId) || !readIdentityString(payload.sourceRevisionId) || !Number.isSafeInteger(payload.sourceRevision) || payload.sourceRevision <= 0) {
|
|
7040
|
+
throw Object.assign(new Error("source_reader_command_invalid"), { code: "source_reader_command_invalid" });
|
|
7041
|
+
}
|
|
7042
|
+
return {
|
|
7043
|
+
commandId: command.commandId.trim(),
|
|
7044
|
+
connectorId: command.connectorId.trim(),
|
|
7045
|
+
payload: {
|
|
7046
|
+
...payload,
|
|
7047
|
+
...payload.authentication === void 0 ? {} : { authentication: normalizeAuthentication(payload.authentication) }
|
|
7048
|
+
}
|
|
7049
|
+
};
|
|
7050
|
+
}
|
|
7051
|
+
function baseResult(command, startedAt, completedAt) {
|
|
7052
|
+
const payload = command.payload;
|
|
7053
|
+
return {
|
|
7054
|
+
contractVersion: CONSTRAINED_SOURCE_READER_CONTRACT_VERSION,
|
|
7055
|
+
redactionVersion: SOURCE_SNAPSHOT_REDACTION_VERSION,
|
|
7056
|
+
companyId: payload.companyId,
|
|
7057
|
+
sourceId: payload.sourceId,
|
|
7058
|
+
sourceRevisionId: payload.sourceRevisionId,
|
|
7059
|
+
sourceRevision: payload.sourceRevision,
|
|
7060
|
+
connectorId: command.connectorId,
|
|
7061
|
+
commandId: command.commandId,
|
|
7062
|
+
operationNames: [...RESULT_OPERATION_NAMES2],
|
|
7063
|
+
startedAt,
|
|
7064
|
+
completedAt,
|
|
7065
|
+
passiveEffectsPossible: true
|
|
7066
|
+
};
|
|
7067
|
+
}
|
|
7068
|
+
function failureCode(error) {
|
|
7069
|
+
const code = readIdentityString(error?.code);
|
|
7070
|
+
return code && /^source_reader_[a-z0-9_]{1,110}$/u.test(code) ? code : "source_reader_transport_failed";
|
|
7071
|
+
}
|
|
7072
|
+
function failedResult(command, startedAt, completedAt, error) {
|
|
7073
|
+
return {
|
|
7074
|
+
...baseResult(command, startedAt, completedAt),
|
|
7075
|
+
status: "failed",
|
|
7076
|
+
pages: [],
|
|
7077
|
+
totalSnapshotChars: 0,
|
|
7078
|
+
aggregateContentHash: sha256(""),
|
|
7079
|
+
redaction: emptyRedactionSummary(),
|
|
7080
|
+
failureCode: failureCode(error)
|
|
7081
|
+
};
|
|
7082
|
+
}
|
|
7083
|
+
async function unavailableSourceReadBindingResolver() {
|
|
7084
|
+
throw Object.assign(new Error("source_reader_transport_unavailable"), {
|
|
7085
|
+
code: "source_reader_transport_unavailable"
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
async function executeSourceReadCommand(commandInput, dependencies = {}) {
|
|
7089
|
+
const now = typeof dependencies.now === "function" ? dependencies.now : () => /* @__PURE__ */ new Date();
|
|
7090
|
+
const startedAt = safeTimestamp(now);
|
|
7091
|
+
let command;
|
|
7092
|
+
let binding;
|
|
7093
|
+
let bindingReleased = false;
|
|
7094
|
+
const releaseBinding = async () => {
|
|
7095
|
+
if (bindingReleased || typeof binding?.release !== "function") return;
|
|
7096
|
+
bindingReleased = true;
|
|
7097
|
+
await binding.release();
|
|
7098
|
+
};
|
|
7099
|
+
try {
|
|
7100
|
+
command = normalizeCommand(commandInput);
|
|
7101
|
+
} catch (error) {
|
|
7102
|
+
const fallback = {
|
|
7103
|
+
commandId: readIdentityString(commandInput?.commandId) ?? "00000000-0000-4000-8000-000000000000",
|
|
7104
|
+
connectorId: readIdentityString(commandInput?.connectorId) ?? "00000000-0000-4000-8000-000000000000",
|
|
7105
|
+
payload: isRecord3(commandInput?.payload) ? commandInput.payload : {}
|
|
7106
|
+
};
|
|
7107
|
+
return failedResult(fallback, startedAt, safeTimestamp(now), error);
|
|
7108
|
+
}
|
|
7109
|
+
try {
|
|
7110
|
+
if (typeof dependencies.resolveBinding !== "function") {
|
|
7111
|
+
throw Object.assign(new Error("source_reader_transport_unavailable"), {
|
|
7112
|
+
code: "source_reader_transport_unavailable"
|
|
7113
|
+
});
|
|
7114
|
+
}
|
|
7115
|
+
const signal = dependencies.signal ?? new AbortController().signal;
|
|
7116
|
+
binding = await dependencies.resolveBinding({
|
|
7117
|
+
companyId: command.payload.companyId,
|
|
7118
|
+
sourceId: command.payload.sourceId,
|
|
7119
|
+
sourceRevisionId: command.payload.sourceRevisionId,
|
|
7120
|
+
sourceRevision: command.payload.sourceRevision,
|
|
7121
|
+
locator: command.payload.locator,
|
|
7122
|
+
declaredPageLocators: command.payload.declaredPageLocators,
|
|
7123
|
+
allowedResourceOrigins: command.payload.allowedResourceOrigins,
|
|
7124
|
+
limits: command.payload.limits,
|
|
7125
|
+
...command.payload.authentication ? { authentication: command.payload.authentication } : {},
|
|
7126
|
+
signal
|
|
7127
|
+
});
|
|
7128
|
+
if (!isRecord3(binding) || !Number.isSafeInteger(binding.pageId) || binding.pageId <= 0 || typeof binding.assertNetworkAddressAllowed !== "function") {
|
|
7129
|
+
throw Object.assign(new Error("source_reader_transport_invalid"), {
|
|
7130
|
+
code: "source_reader_transport_invalid"
|
|
7131
|
+
});
|
|
7132
|
+
}
|
|
7133
|
+
const read = await readConstrainedSource({
|
|
7134
|
+
sourceRevisionId: command.payload.sourceRevisionId,
|
|
7135
|
+
locator: command.payload.locator,
|
|
7136
|
+
declaredPageLocators: command.payload.declaredPageLocators,
|
|
7137
|
+
allowedResourceOrigins: command.payload.allowedResourceOrigins,
|
|
7138
|
+
pageId: binding.pageId,
|
|
7139
|
+
limits: command.payload.limits
|
|
7140
|
+
}, {
|
|
7141
|
+
transport: binding.transport,
|
|
7142
|
+
assertNetworkAddressAllowed: binding.assertNetworkAddressAllowed,
|
|
7143
|
+
signal,
|
|
7144
|
+
now
|
|
7145
|
+
});
|
|
7146
|
+
const redaction = emptyRedactionSummary();
|
|
7147
|
+
const processedPages = read.pages.map((page) => {
|
|
7148
|
+
const scrubbed = scrubSnapshotText(page.text);
|
|
7149
|
+
redaction.replacements += scrubbed.summary.replacements;
|
|
7150
|
+
for (const category of REDACTION_CATEGORIES) {
|
|
7151
|
+
redaction.categories[category] += scrubbed.summary.categories[category];
|
|
7152
|
+
}
|
|
7153
|
+
return {
|
|
7154
|
+
metadata: {
|
|
7155
|
+
pageIndex: page.pageIndex,
|
|
7156
|
+
capturedAt: page.capturedAt,
|
|
7157
|
+
charCount: scrubbed.text.length,
|
|
7158
|
+
contentHash: sha256(scrubbed.text)
|
|
7159
|
+
},
|
|
7160
|
+
content: scrubbed.text
|
|
7161
|
+
};
|
|
7162
|
+
});
|
|
7163
|
+
const pages = processedPages.map((page) => page.metadata);
|
|
7164
|
+
const aggregateContentHash = sha256(pages.map((page) => `${page.pageIndex}:${page.contentHash}:${page.charCount}`).join("\n"));
|
|
7165
|
+
const result3 = {
|
|
7166
|
+
...baseResult(command, read.startedAt, read.completedAt),
|
|
7167
|
+
status: read.status,
|
|
7168
|
+
pages,
|
|
7169
|
+
totalSnapshotChars: pages.reduce((total, page) => total + page.charCount, 0),
|
|
7170
|
+
aggregateContentHash,
|
|
7171
|
+
redaction
|
|
7172
|
+
};
|
|
7173
|
+
if (read.status === "partial") result3.coverageGap = read.coverageGap;
|
|
7174
|
+
if (read.status === "complete") {
|
|
7175
|
+
result3.knowledge = {
|
|
7176
|
+
format: "text",
|
|
7177
|
+
pages: processedPages.map((page) => ({
|
|
7178
|
+
pageIndex: page.metadata.pageIndex,
|
|
7179
|
+
content: page.content
|
|
7180
|
+
}))
|
|
7181
|
+
};
|
|
7182
|
+
}
|
|
7183
|
+
await releaseBinding();
|
|
7184
|
+
return result3;
|
|
7185
|
+
} catch (error) {
|
|
7186
|
+
let terminalError = error;
|
|
7187
|
+
try {
|
|
7188
|
+
await releaseBinding();
|
|
7189
|
+
} catch {
|
|
7190
|
+
terminalError = Object.assign(new Error("source_reader_release_failed"), {
|
|
7191
|
+
code: "source_reader_release_failed"
|
|
7192
|
+
});
|
|
7193
|
+
}
|
|
7194
|
+
return failedResult(command, startedAt, safeTimestamp(now), terminalError);
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
|
|
7198
|
+
// src/amaster-runtime-daemon/browser-session-broker.mjs
|
|
7199
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
7200
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
7201
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
7202
|
+
import {
|
|
7203
|
+
chmod,
|
|
7204
|
+
lstat,
|
|
7205
|
+
mkdir,
|
|
7206
|
+
readFile,
|
|
7207
|
+
readdir,
|
|
7208
|
+
realpath,
|
|
7209
|
+
rename,
|
|
7210
|
+
rm,
|
|
7211
|
+
writeFile
|
|
7212
|
+
} from "node:fs/promises";
|
|
7213
|
+
import { join as join13, relative as relative8, resolve as resolve11, sep as sep2 } from "node:path";
|
|
7214
|
+
|
|
7215
|
+
// src/amaster-runtime-daemon/playwright-source-browser.mjs
|
|
7216
|
+
var LOGIN_CONTROL_SELECTORS = Object.freeze([
|
|
7217
|
+
'input[type="password"]',
|
|
7218
|
+
'input[autocomplete="one-time-code"]',
|
|
7219
|
+
'[data-amaster-auth-required="true"]'
|
|
7220
|
+
]);
|
|
7221
|
+
var ACCESS_DENIED_SELECTOR = '[data-amaster-access-denied="true"]';
|
|
7222
|
+
function fail2(code) {
|
|
7223
|
+
throw Object.assign(new Error(code), { code });
|
|
7224
|
+
}
|
|
7225
|
+
function checkSignal(signal) {
|
|
7226
|
+
if (signal?.aborted) fail2("source_reader_aborted");
|
|
7227
|
+
}
|
|
7228
|
+
function pageIds(context, ids, nextId) {
|
|
7229
|
+
return context.pages().map((page) => {
|
|
7230
|
+
if (!ids.has(page)) ids.set(page, nextId.value++);
|
|
7231
|
+
return { pageId: ids.get(page), url: page.url() };
|
|
7232
|
+
});
|
|
7233
|
+
}
|
|
7234
|
+
function selectedPage(context, ids, pageId) {
|
|
7235
|
+
const page = context.pages().find((candidate) => ids.get(candidate) === pageId);
|
|
7236
|
+
if (!page) fail2("source_reader_target_missing");
|
|
7237
|
+
return page;
|
|
7238
|
+
}
|
|
7239
|
+
function redirectChain(response) {
|
|
7240
|
+
const chain = [];
|
|
7241
|
+
let request = response?.request?.()?.redirectedFrom?.() ?? null;
|
|
7242
|
+
while (request) {
|
|
7243
|
+
const locator = request.url?.();
|
|
7244
|
+
if (typeof locator !== "string") fail2("source_reader_transport_invalid");
|
|
7245
|
+
chain.push(locator);
|
|
7246
|
+
request = request.redirectedFrom?.() ?? null;
|
|
7247
|
+
}
|
|
7248
|
+
return chain.reverse();
|
|
7249
|
+
}
|
|
7250
|
+
function originOf(locator) {
|
|
7251
|
+
try {
|
|
7252
|
+
const url = new URL(locator);
|
|
7253
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
7254
|
+
return url.origin;
|
|
7255
|
+
} catch {
|
|
7256
|
+
return null;
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
async function locatorVisible(page, selector) {
|
|
7260
|
+
const locator = page.locator(selector);
|
|
7261
|
+
if (await locator.count() === 0) return false;
|
|
7262
|
+
const target = typeof locator.first === "function" ? locator.first() : locator;
|
|
7263
|
+
return target.isVisible();
|
|
7264
|
+
}
|
|
7265
|
+
async function createPlaywrightSourceBrowser(options) {
|
|
7266
|
+
if (!options?.context || !options?.page || typeof options.assertNetworkAddressAllowed !== "function") {
|
|
7267
|
+
fail2("source_reader_transport_invalid");
|
|
7268
|
+
}
|
|
7269
|
+
const { context, page, assertNetworkAddressAllowed } = options;
|
|
7270
|
+
const maxSnapshotChars = Number.isSafeInteger(options.maxSnapshotChars) && options.maxSnapshotChars > 0 ? options.maxSnapshotChars : 2e5;
|
|
7271
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
7272
|
+
const nextId = { value: 1 };
|
|
7273
|
+
ids.set(page, nextId.value++);
|
|
7274
|
+
let networkFailure = null;
|
|
7275
|
+
let allowedOrigins = new Set(Array.isArray(options.allowedOrigins) ? options.allowedOrigins : []);
|
|
7276
|
+
const accessDeniedPages = /* @__PURE__ */ new WeakSet();
|
|
7277
|
+
if (typeof context.route !== "function") {
|
|
7278
|
+
fail2("source_reader_transport_invalid");
|
|
7279
|
+
}
|
|
7280
|
+
await context.route("**/*", async (route) => {
|
|
7281
|
+
try {
|
|
7282
|
+
const locator = route.request().url();
|
|
7283
|
+
const origin = originOf(locator);
|
|
7284
|
+
if (!origin || !allowedOrigins.has(origin)) {
|
|
7285
|
+
networkFailure = "source_reader_resource_origin_forbidden";
|
|
7286
|
+
await route.abort("blockedbyclient");
|
|
7287
|
+
return;
|
|
7288
|
+
}
|
|
7289
|
+
await assertNetworkAddressAllowed(locator);
|
|
7290
|
+
await route.continue();
|
|
7291
|
+
} catch {
|
|
7292
|
+
networkFailure = "source_reader_network_scope_forbidden";
|
|
7293
|
+
await route.abort("blockedbyclient");
|
|
7294
|
+
}
|
|
7295
|
+
});
|
|
7296
|
+
if (typeof context.routeWebSocket !== "function") {
|
|
7297
|
+
fail2("source_reader_transport_invalid");
|
|
7298
|
+
}
|
|
7299
|
+
await context.routeWebSocket("**/*", async (webSocket) => {
|
|
7300
|
+
networkFailure = "source_reader_network_scope_forbidden";
|
|
7301
|
+
await webSocket.close({
|
|
7302
|
+
code: 1008,
|
|
7303
|
+
reason: "source_reader_websocket_forbidden"
|
|
7304
|
+
});
|
|
7305
|
+
});
|
|
7306
|
+
const transport = {
|
|
7307
|
+
async listPages({ signal } = {}) {
|
|
7308
|
+
checkSignal(signal);
|
|
7309
|
+
if (networkFailure) fail2(networkFailure);
|
|
7310
|
+
return pageIds(context, ids, nextId);
|
|
7311
|
+
},
|
|
7312
|
+
async navigatePage({ pageId, locator, allowedResourceOrigins = [], signal } = {}) {
|
|
7313
|
+
checkSignal(signal);
|
|
7314
|
+
const target = selectedPage(context, ids, pageId);
|
|
7315
|
+
await assertNetworkAddressAllowed(locator);
|
|
7316
|
+
const contentOrigin = originOf(locator);
|
|
7317
|
+
if (!contentOrigin || !Array.isArray(allowedResourceOrigins)) {
|
|
7318
|
+
fail2("source_reader_transport_invalid");
|
|
7319
|
+
}
|
|
7320
|
+
allowedOrigins = /* @__PURE__ */ new Set([contentOrigin, ...allowedResourceOrigins]);
|
|
7321
|
+
const resources = /* @__PURE__ */ new Set();
|
|
7322
|
+
const checks = [];
|
|
7323
|
+
let downloadAttempted = false;
|
|
7324
|
+
const onRequest = (request) => {
|
|
7325
|
+
const resourceLocator = request.url();
|
|
7326
|
+
checks.push(Promise.resolve(assertNetworkAddressAllowed(resourceLocator)));
|
|
7327
|
+
try {
|
|
7328
|
+
resources.add(new URL(resourceLocator).origin);
|
|
7329
|
+
} catch {
|
|
7330
|
+
networkFailure = "source_reader_network_scope_forbidden";
|
|
7331
|
+
}
|
|
7332
|
+
};
|
|
7333
|
+
const onDownload = () => {
|
|
7334
|
+
downloadAttempted = true;
|
|
7335
|
+
};
|
|
7336
|
+
target.on("request", onRequest);
|
|
7337
|
+
target.on("download", onDownload);
|
|
7338
|
+
let response;
|
|
7339
|
+
try {
|
|
7340
|
+
response = await target.goto(locator, { waitUntil: "domcontentloaded" });
|
|
7341
|
+
await Promise.all(checks);
|
|
7342
|
+
} catch (error) {
|
|
7343
|
+
if (networkFailure || error?.code === "source_reader_network_scope_forbidden") {
|
|
7344
|
+
fail2("source_reader_network_scope_forbidden");
|
|
7345
|
+
}
|
|
7346
|
+
fail2("source_reader_transport_failed");
|
|
7347
|
+
} finally {
|
|
7348
|
+
target.off("request", onRequest);
|
|
7349
|
+
target.off("download", onDownload);
|
|
7350
|
+
}
|
|
7351
|
+
checkSignal(signal);
|
|
7352
|
+
if (networkFailure) fail2(networkFailure);
|
|
7353
|
+
const status = response?.status?.();
|
|
7354
|
+
if (status === 401 || status === 403) accessDeniedPages.add(target);
|
|
7355
|
+
const finalUrl = response?.url?.() ?? target.url();
|
|
7356
|
+
const redirects = redirectChain(response);
|
|
7357
|
+
for (const redirect of redirects) await assertNetworkAddressAllowed(redirect);
|
|
7358
|
+
await assertNetworkAddressAllowed(finalUrl);
|
|
7359
|
+
return {
|
|
7360
|
+
finalUrl,
|
|
7361
|
+
redirectChain: redirects,
|
|
7362
|
+
resourceOrigins: [...resources].sort(),
|
|
7363
|
+
downloadAttempted
|
|
7364
|
+
};
|
|
7365
|
+
},
|
|
7366
|
+
async takeSnapshot({ pageId, signal } = {}) {
|
|
7367
|
+
checkSignal(signal);
|
|
7368
|
+
if (networkFailure) fail2(networkFailure);
|
|
7369
|
+
const target = selectedPage(context, ids, pageId);
|
|
7370
|
+
if (accessDeniedPages.has(target) || await locatorVisible(target, ACCESS_DENIED_SELECTOR)) {
|
|
7371
|
+
fail2("source_reader_no_access");
|
|
7372
|
+
}
|
|
7373
|
+
for (const selector of LOGIN_CONTROL_SELECTORS) {
|
|
7374
|
+
if (await locatorVisible(target, selector)) fail2("source_reader_auth_required");
|
|
7375
|
+
}
|
|
7376
|
+
const text = await target.locator("body").innerText();
|
|
7377
|
+
if (typeof text !== "string") fail2("source_reader_transport_invalid");
|
|
7378
|
+
checkSignal(signal);
|
|
7379
|
+
if (networkFailure) fail2(networkFailure);
|
|
7380
|
+
return {
|
|
7381
|
+
text: text.slice(0, maxSnapshotChars),
|
|
7382
|
+
truncated: text.length > maxSnapshotChars,
|
|
7383
|
+
unsupportedInteraction: null
|
|
7384
|
+
};
|
|
7385
|
+
}
|
|
7386
|
+
};
|
|
7387
|
+
return Object.freeze({
|
|
7388
|
+
pageId: ids.get(page),
|
|
7389
|
+
transport: Object.freeze(transport),
|
|
7390
|
+
assertNetworkAddressAllowed
|
|
7391
|
+
});
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
// src/amaster-runtime-daemon/browser-session-broker.mjs
|
|
7395
|
+
var MARKER_NAME = ".amaster-browser-session.json";
|
|
7396
|
+
var MARKER_VERSION = 1;
|
|
7397
|
+
var SUPPORTED_BROWSER_VERSION = /(?:Google Chrome|Chromium|Microsoft Edge|Chrome for Testing)/iu;
|
|
7398
|
+
function fail3(code) {
|
|
7399
|
+
throw Object.assign(new Error(code), { code });
|
|
7400
|
+
}
|
|
7401
|
+
function sha2562(value) {
|
|
7402
|
+
return createHash11("sha256").update(value, "utf8").digest("hex");
|
|
7403
|
+
}
|
|
7404
|
+
function configuredBrowserExecutableReady(executablePath) {
|
|
7405
|
+
if (!executablePath || !existsSync12(executablePath)) return false;
|
|
7406
|
+
const probe = spawnSync5(executablePath, ["--version"], {
|
|
7407
|
+
encoding: "utf8",
|
|
7408
|
+
env: { PATH: process.env.PATH ?? "" },
|
|
7409
|
+
maxBuffer: 64 * 1024,
|
|
7410
|
+
timeout: 5e3,
|
|
7411
|
+
windowsHide: true
|
|
7412
|
+
});
|
|
7413
|
+
if (probe.error || probe.signal || probe.status !== 0) return false;
|
|
7414
|
+
const output = `${probe.stdout ?? ""}
|
|
7415
|
+
${probe.stderr ?? ""}`.slice(0, 64 * 1024);
|
|
7416
|
+
return SUPPORTED_BROWSER_VERSION.test(output);
|
|
7417
|
+
}
|
|
7418
|
+
function profileName(identity2) {
|
|
7419
|
+
return sha2562(`${identity2.companyId}\0${identity2.bindingId}\0${identity2.localOpaqueRef}`);
|
|
7420
|
+
}
|
|
7421
|
+
function expectedMarker(identity2) {
|
|
7422
|
+
return {
|
|
7423
|
+
version: MARKER_VERSION,
|
|
7424
|
+
companyId: identity2.companyId,
|
|
7425
|
+
bindingId: identity2.bindingId,
|
|
7426
|
+
localOpaqueRef: identity2.localOpaqueRef
|
|
7427
|
+
};
|
|
7428
|
+
}
|
|
7429
|
+
function markerMatches(marker, identity2) {
|
|
7430
|
+
const expected = expectedMarker(identity2);
|
|
7431
|
+
return marker?.version === expected.version && marker?.companyId === expected.companyId && marker?.bindingId === expected.bindingId && marker?.localOpaqueRef === expected.localOpaqueRef && Object.keys(marker).length === Object.keys(expected).length;
|
|
7432
|
+
}
|
|
7433
|
+
function validLease(input, now) {
|
|
7434
|
+
const expiresAt = Date.parse(input.leaseExpiresAt);
|
|
7435
|
+
if (typeof input.leaseId !== "string" || !input.leaseId || !Number.isFinite(expiresAt) || expiresAt <= now.getTime()) {
|
|
7436
|
+
fail3("browser_session_lease_expired");
|
|
7437
|
+
}
|
|
7438
|
+
return new Date(expiresAt);
|
|
7439
|
+
}
|
|
7440
|
+
async function pathStat(path, missingAllowed = false) {
|
|
7441
|
+
try {
|
|
7442
|
+
return await lstat(path);
|
|
7443
|
+
} catch (error) {
|
|
7444
|
+
if (missingAllowed && error?.code === "ENOENT") return null;
|
|
7445
|
+
throw error;
|
|
7446
|
+
}
|
|
7447
|
+
}
|
|
7448
|
+
async function ensureProtectedDirectory(path) {
|
|
7449
|
+
await mkdir(path, { recursive: true, mode: 448 });
|
|
7450
|
+
const metadata = await lstat(path);
|
|
7451
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
7452
|
+
fail3("browser_session_profile_path_forbidden");
|
|
7453
|
+
}
|
|
7454
|
+
await chmod(path, 448);
|
|
7455
|
+
return realpath(path);
|
|
7456
|
+
}
|
|
7457
|
+
function assertWithinRoot(root, target) {
|
|
7458
|
+
const pathFromRoot = relative8(root, target);
|
|
7459
|
+
if (pathFromRoot === "" || pathFromRoot === ".." || pathFromRoot.startsWith(`..${sep2}`) || resolve11(root, pathFromRoot) !== target) {
|
|
7460
|
+
fail3("browser_session_profile_path_forbidden");
|
|
7461
|
+
}
|
|
7462
|
+
}
|
|
7463
|
+
async function readMarker(profilePath) {
|
|
7464
|
+
const markerPath = join13(profilePath, MARKER_NAME);
|
|
7465
|
+
const metadata = await pathStat(markerPath, true);
|
|
7466
|
+
if (!metadata || !metadata.isFile() || metadata.isSymbolicLink()) {
|
|
7467
|
+
fail3("browser_session_profile_owner_mismatch");
|
|
7468
|
+
}
|
|
7469
|
+
try {
|
|
7470
|
+
const marker = JSON.parse(await readFile(markerPath, "utf8"));
|
|
7471
|
+
if (!marker || typeof marker !== "object" || Array.isArray(marker)) throw new Error();
|
|
7472
|
+
return marker;
|
|
7473
|
+
} catch {
|
|
7474
|
+
fail3("browser_session_profile_owner_mismatch");
|
|
7475
|
+
}
|
|
7476
|
+
}
|
|
7477
|
+
async function verifyOwnedProfile(root, profilePath, identity2) {
|
|
7478
|
+
const metadata = await pathStat(profilePath, true);
|
|
7479
|
+
if (!metadata) return false;
|
|
7480
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
7481
|
+
fail3("browser_session_profile_path_forbidden");
|
|
7482
|
+
}
|
|
7483
|
+
const canonical = await realpath(profilePath);
|
|
7484
|
+
assertWithinRoot(root, canonical);
|
|
7485
|
+
if (!markerMatches(await readMarker(canonical), identity2)) {
|
|
7486
|
+
fail3("browser_session_profile_owner_mismatch");
|
|
7487
|
+
}
|
|
7488
|
+
return true;
|
|
7489
|
+
}
|
|
7490
|
+
async function writeMarker(profilePath, identity2) {
|
|
7491
|
+
const markerPath = join13(profilePath, MARKER_NAME);
|
|
7492
|
+
const temporaryPath = `${markerPath}.tmp-${process.pid}-${Date.now()}`;
|
|
7493
|
+
try {
|
|
7494
|
+
await writeFile(temporaryPath, `${JSON.stringify(expectedMarker(identity2))}
|
|
7495
|
+
`, {
|
|
7496
|
+
flag: "wx",
|
|
7497
|
+
mode: 384
|
|
7498
|
+
});
|
|
7499
|
+
await rename(temporaryPath, markerPath);
|
|
7500
|
+
} finally {
|
|
7501
|
+
await rm(temporaryPath, { force: true });
|
|
7502
|
+
}
|
|
7503
|
+
}
|
|
7504
|
+
async function ensureOwnedProfile(root, identity2) {
|
|
7505
|
+
const profilePath = join13(root, profileName(identity2));
|
|
7506
|
+
const exists = await verifyOwnedProfile(root, profilePath, identity2);
|
|
7507
|
+
if (exists) return { profilePath, reusedProfile: true };
|
|
7508
|
+
await mkdir(profilePath, { mode: 448 });
|
|
7509
|
+
await chmod(profilePath, 448);
|
|
7510
|
+
await writeMarker(profilePath, identity2);
|
|
7511
|
+
await verifyOwnedProfile(root, profilePath, identity2);
|
|
7512
|
+
return { profilePath, reusedProfile: false };
|
|
7513
|
+
}
|
|
7514
|
+
async function assertNoSymlinks(path) {
|
|
7515
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
7516
|
+
const entryPath = join13(path, entry.name);
|
|
7517
|
+
if (entry.isSymbolicLink()) fail3("browser_session_profile_symlink_forbidden");
|
|
7518
|
+
if (entry.isDirectory()) await assertNoSymlinks(entryPath);
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
7521
|
+
async function removeOwnedProfile(root, identity2) {
|
|
7522
|
+
const profilePath = join13(root, profileName(identity2));
|
|
7523
|
+
if (!await verifyOwnedProfile(root, profilePath, identity2)) return false;
|
|
7524
|
+
await assertNoSymlinks(profilePath);
|
|
7525
|
+
await rm(profilePath, { recursive: true });
|
|
7526
|
+
return true;
|
|
7527
|
+
}
|
|
7528
|
+
async function closeQuietly(context) {
|
|
7529
|
+
try {
|
|
7530
|
+
await context?.close?.();
|
|
7531
|
+
} catch {
|
|
7532
|
+
}
|
|
7533
|
+
}
|
|
7534
|
+
function resolverHost(locator) {
|
|
7535
|
+
try {
|
|
7536
|
+
return new URL(locator).hostname.toLowerCase().replace(/\.$/u, "");
|
|
7537
|
+
} catch {
|
|
7538
|
+
fail3("source_reader_network_scope_forbidden");
|
|
7539
|
+
}
|
|
7540
|
+
}
|
|
7541
|
+
function resolverAddress(address) {
|
|
7542
|
+
return address.includes(":") ? `[${address}]` : address;
|
|
7543
|
+
}
|
|
7544
|
+
function httpNetworkLocator(locator) {
|
|
7545
|
+
try {
|
|
7546
|
+
const url = new URL(locator);
|
|
7547
|
+
if (url.protocol === "ws:") url.protocol = "http:";
|
|
7548
|
+
else if (url.protocol === "wss:") url.protocol = "https:";
|
|
7549
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
7550
|
+
fail3("browser_session_network_scope_forbidden");
|
|
7551
|
+
}
|
|
7552
|
+
return url.href;
|
|
7553
|
+
} catch (error) {
|
|
7554
|
+
if (error?.code === "browser_session_network_scope_forbidden") throw error;
|
|
7555
|
+
fail3("browser_session_network_scope_forbidden");
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
function createBrowserSessionBroker(options) {
|
|
7559
|
+
const now = typeof options?.now === "function" ? options.now : () => /* @__PURE__ */ new Date();
|
|
7560
|
+
const playwright = options?.playwright;
|
|
7561
|
+
const browserExecutablePath = typeof options?.browserExecutablePath === "string" && options.browserExecutablePath.trim() ? resolve11(options.browserExecutablePath) : null;
|
|
7562
|
+
const browserExecutableReady = browserExecutablePath ? configuredBrowserExecutableReady(browserExecutablePath) : null;
|
|
7563
|
+
const networkScope = options?.publicNetworkScope;
|
|
7564
|
+
if (!options?.stateRoot || typeof networkScope?.assertNetworkAddressAllowed !== "function" || typeof networkScope?.resolveNetworkAddresses !== "function") {
|
|
7565
|
+
fail3("browser_session_broker_config_invalid");
|
|
7566
|
+
}
|
|
7567
|
+
const scheduleTimeout = typeof options.setTimeout === "function" ? options.setTimeout : setTimeout;
|
|
7568
|
+
const cancelTimeout = typeof options.clearTimeout === "function" ? options.clearTimeout : clearTimeout;
|
|
7569
|
+
const rootPromise = (async () => {
|
|
7570
|
+
const stateRoot = await ensureProtectedDirectory(resolve11(options.stateRoot));
|
|
7571
|
+
return ensureProtectedDirectory(join13(stateRoot, "browser-sessions"));
|
|
7572
|
+
})();
|
|
7573
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
7574
|
+
async function closeBinding(bindingId) {
|
|
7575
|
+
const active = sessions.get(bindingId);
|
|
7576
|
+
if (!active) return;
|
|
7577
|
+
sessions.delete(bindingId);
|
|
7578
|
+
if (active.leaseTimer) cancelTimeout(active.leaseTimer);
|
|
7579
|
+
active.removeAbortListener?.();
|
|
7580
|
+
await closeQuietly(active.context);
|
|
7581
|
+
}
|
|
7582
|
+
function scheduleLeaseDeadline(session) {
|
|
7583
|
+
if (session.leaseTimer) cancelTimeout(session.leaseTimer);
|
|
7584
|
+
const delay = Math.max(0, session.leaseExpiresAt.getTime() - now().getTime());
|
|
7585
|
+
const timer = scheduleTimeout(async () => {
|
|
7586
|
+
const active = sessions.get(session.bindingId);
|
|
7587
|
+
if (active?.leaseId !== session.leaseId) return;
|
|
7588
|
+
await closeBinding(session.bindingId);
|
|
7589
|
+
}, delay);
|
|
7590
|
+
timer?.unref?.();
|
|
7591
|
+
session.leaseTimer = timer;
|
|
7592
|
+
}
|
|
7593
|
+
function attachAbortSignal(session, signal) {
|
|
7594
|
+
session.removeAbortListener?.();
|
|
7595
|
+
if (!signal || typeof signal.addEventListener !== "function") return;
|
|
7596
|
+
const abort = () => {
|
|
7597
|
+
void closeBinding(session.bindingId);
|
|
7598
|
+
};
|
|
7599
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
7600
|
+
session.removeAbortListener = () => signal.removeEventListener?.("abort", abort);
|
|
7601
|
+
if (signal.aborted) abort();
|
|
7602
|
+
}
|
|
7603
|
+
async function installHumanNetworkGuard(context, pinnedHosts) {
|
|
7604
|
+
if (typeof context?.route !== "function" || typeof context?.routeWebSocket !== "function") {
|
|
7605
|
+
fail3("browser_session_network_guard_unavailable");
|
|
7606
|
+
}
|
|
7607
|
+
let failure = null;
|
|
7608
|
+
await context.route("**/*", async (route) => {
|
|
7609
|
+
try {
|
|
7610
|
+
const locator = httpNetworkLocator(route.request().url());
|
|
7611
|
+
if (!pinnedHosts.has(resolverHost(locator))) {
|
|
7612
|
+
fail3("browser_session_network_scope_forbidden");
|
|
7613
|
+
}
|
|
7614
|
+
await networkScope.assertNetworkAddressAllowed(locator);
|
|
7615
|
+
await route.continue();
|
|
7616
|
+
} catch {
|
|
7617
|
+
failure = "browser_session_network_scope_forbidden";
|
|
7618
|
+
await route.abort("blockedbyclient");
|
|
7619
|
+
}
|
|
7620
|
+
});
|
|
7621
|
+
await context.routeWebSocket("**/*", async (webSocket) => {
|
|
7622
|
+
try {
|
|
7623
|
+
const locator = httpNetworkLocator(webSocket.url());
|
|
7624
|
+
if (!pinnedHosts.has(resolverHost(locator))) {
|
|
7625
|
+
fail3("browser_session_network_scope_forbidden");
|
|
7626
|
+
}
|
|
7627
|
+
await networkScope.assertNetworkAddressAllowed(locator);
|
|
7628
|
+
webSocket.connectToServer();
|
|
7629
|
+
} catch {
|
|
7630
|
+
failure = "browser_session_network_scope_forbidden";
|
|
7631
|
+
await webSocket.close({
|
|
7632
|
+
code: 1008,
|
|
7633
|
+
reason: "browser_session_network_scope_forbidden"
|
|
7634
|
+
});
|
|
7635
|
+
}
|
|
7636
|
+
});
|
|
7637
|
+
return () => {
|
|
7638
|
+
if (failure) fail3(failure);
|
|
7639
|
+
};
|
|
7640
|
+
}
|
|
7641
|
+
async function browserLaunchArgs(input) {
|
|
7642
|
+
const locators = [
|
|
7643
|
+
input.locator,
|
|
7644
|
+
...Array.isArray(input.declaredPageLocators) ? input.declaredPageLocators : [],
|
|
7645
|
+
...Array.isArray(input.allowedResourceOrigins) ? input.allowedResourceOrigins : []
|
|
7646
|
+
];
|
|
7647
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
7648
|
+
for (const locator of locators) {
|
|
7649
|
+
if (typeof locator !== "string" || !locator) continue;
|
|
7650
|
+
const addresses = await networkScope.resolveNetworkAddresses(locator);
|
|
7651
|
+
if (!Array.isArray(addresses) || addresses.length === 0) {
|
|
7652
|
+
fail3("source_reader_network_scope_forbidden");
|
|
7653
|
+
}
|
|
7654
|
+
const host = resolverHost(locator);
|
|
7655
|
+
const address = addresses[0];
|
|
7656
|
+
if (typeof address !== "string" || !address) fail3("source_reader_network_scope_forbidden");
|
|
7657
|
+
pinned.set(host, resolverAddress(address));
|
|
7658
|
+
}
|
|
7659
|
+
const rules = [...pinned.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([host, address]) => `MAP ${host} ${address}`);
|
|
7660
|
+
return rules.length > 0 ? [`--host-resolver-rules=${[...rules, "EXCLUDE localhost"].join(",")}`] : [];
|
|
7661
|
+
}
|
|
7662
|
+
function pinnedNetworkHosts(input) {
|
|
7663
|
+
return new Set([
|
|
7664
|
+
input.locator,
|
|
7665
|
+
...Array.isArray(input.declaredPageLocators) ? input.declaredPageLocators : [],
|
|
7666
|
+
...Array.isArray(input.allowedResourceOrigins) ? input.allowedResourceOrigins : []
|
|
7667
|
+
].filter((locator) => typeof locator === "string" && locator).map(resolverHost));
|
|
7668
|
+
}
|
|
7669
|
+
async function launchPersistent(identity2, leaseKind, leaseId, leaseExpiresAt) {
|
|
7670
|
+
const root = await rootPromise;
|
|
7671
|
+
const profile = await ensureOwnedProfile(root, identity2);
|
|
7672
|
+
if (typeof playwright?.chromium?.launchPersistentContext !== "function") {
|
|
7673
|
+
fail3("browser_session_runtime_unavailable");
|
|
7674
|
+
}
|
|
7675
|
+
let context;
|
|
7676
|
+
try {
|
|
7677
|
+
const args = await browserLaunchArgs(identity2);
|
|
7678
|
+
context = await playwright.chromium.launchPersistentContext(profile.profilePath, {
|
|
7679
|
+
headless: false,
|
|
7680
|
+
acceptDownloads: false,
|
|
7681
|
+
serviceWorkers: "block",
|
|
7682
|
+
args,
|
|
7683
|
+
...browserExecutablePath ? { executablePath: browserExecutablePath } : {}
|
|
7684
|
+
});
|
|
7685
|
+
const page = context.pages()[0] ?? await context.newPage();
|
|
7686
|
+
const session = {
|
|
7687
|
+
...identity2,
|
|
7688
|
+
context,
|
|
7689
|
+
page,
|
|
7690
|
+
leaseKind,
|
|
7691
|
+
leaseId,
|
|
7692
|
+
leaseExpiresAt
|
|
7693
|
+
};
|
|
7694
|
+
sessions.set(identity2.bindingId, session);
|
|
7695
|
+
scheduleLeaseDeadline(session);
|
|
7696
|
+
return { session, reusedProfile: profile.reusedProfile };
|
|
7697
|
+
} catch (error) {
|
|
7698
|
+
await closeQuietly(context);
|
|
7699
|
+
if (error?.code?.startsWith?.("browser_session_")) throw error;
|
|
7700
|
+
fail3("browser_session_launch_failed");
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
async function openHumanSession(input) {
|
|
7704
|
+
const expiresAt = validLease(input, now());
|
|
7705
|
+
await networkScope.assertNetworkAddressAllowed(input.locator);
|
|
7706
|
+
const existing = sessions.get(input.bindingId);
|
|
7707
|
+
if (existing && existing.companyId === input.companyId && existing.localOpaqueRef === input.localOpaqueRef && existing.leaseKind === "human" && existing.leaseId === input.leaseId) {
|
|
7708
|
+
return {
|
|
7709
|
+
bindingId: input.bindingId,
|
|
7710
|
+
leaseId: input.leaseId,
|
|
7711
|
+
reusedProfile: true,
|
|
7712
|
+
status: "human_lease"
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7715
|
+
await closeBinding(input.bindingId);
|
|
7716
|
+
const { session, reusedProfile } = await launchPersistent(
|
|
7717
|
+
input,
|
|
7718
|
+
"human",
|
|
7719
|
+
input.leaseId,
|
|
7720
|
+
expiresAt
|
|
7721
|
+
);
|
|
7722
|
+
try {
|
|
7723
|
+
const assertNetworkGuardHealthy = await installHumanNetworkGuard(
|
|
7724
|
+
session.context,
|
|
7725
|
+
pinnedNetworkHosts(input)
|
|
7726
|
+
);
|
|
7727
|
+
await session.page.goto(input.locator, { waitUntil: "domcontentloaded" });
|
|
7728
|
+
assertNetworkGuardHealthy();
|
|
7729
|
+
} catch {
|
|
7730
|
+
await closeBinding(input.bindingId);
|
|
7731
|
+
fail3("browser_session_navigation_failed");
|
|
7732
|
+
}
|
|
7733
|
+
return {
|
|
7734
|
+
bindingId: input.bindingId,
|
|
7735
|
+
leaseId: input.leaseId,
|
|
7736
|
+
reusedProfile,
|
|
7737
|
+
status: "human_lease"
|
|
7738
|
+
};
|
|
7739
|
+
}
|
|
7740
|
+
async function switchAccount(input) {
|
|
7741
|
+
validLease(input, now());
|
|
7742
|
+
const previous = input.previousBinding;
|
|
7743
|
+
if (!previous || previous.bindingId === input.bindingId || typeof previous.localOpaqueRef !== "string") {
|
|
7744
|
+
fail3("browser_session_command_invalid");
|
|
7745
|
+
}
|
|
7746
|
+
const previousSession = sessions.get(previous.bindingId);
|
|
7747
|
+
if (previousSession && (previousSession.companyId !== input.companyId || previousSession.localOpaqueRef !== previous.localOpaqueRef)) {
|
|
7748
|
+
fail3("browser_session_profile_owner_mismatch");
|
|
7749
|
+
}
|
|
7750
|
+
await closeBinding(previous.bindingId);
|
|
7751
|
+
await closeBinding(input.bindingId);
|
|
7752
|
+
const root = await rootPromise;
|
|
7753
|
+
await removeOwnedProfile(root, {
|
|
7754
|
+
companyId: input.companyId,
|
|
7755
|
+
bindingId: previous.bindingId,
|
|
7756
|
+
localOpaqueRef: previous.localOpaqueRef
|
|
7757
|
+
});
|
|
7758
|
+
return openHumanSession(input);
|
|
7759
|
+
}
|
|
7760
|
+
async function revokeBinding(input) {
|
|
7761
|
+
await closeBinding(input.bindingId);
|
|
7762
|
+
const root = await rootPromise;
|
|
7763
|
+
await removeOwnedProfile(root, input);
|
|
7764
|
+
return { bindingId: input.bindingId, status: "revoked" };
|
|
7765
|
+
}
|
|
7766
|
+
async function resolveAuthenticatedBinding(input) {
|
|
7767
|
+
const expiresAt = validLease(input, now());
|
|
7768
|
+
await networkScope.assertNetworkAddressAllowed(input.locator);
|
|
7769
|
+
let session = sessions.get(input.bindingId);
|
|
7770
|
+
if (session) {
|
|
7771
|
+
if (session.companyId !== input.companyId || session.localOpaqueRef !== input.localOpaqueRef) {
|
|
7772
|
+
fail3("browser_session_profile_owner_mismatch");
|
|
7773
|
+
}
|
|
7774
|
+
if (session.leaseKind === "human" && session.leaseId === input.leaseId) {
|
|
7775
|
+
fail3("source_reader_human_lease_active");
|
|
7776
|
+
}
|
|
7777
|
+
if (session.leaseKind === "human") {
|
|
7778
|
+
await closeBinding(input.bindingId);
|
|
7779
|
+
session = (await launchPersistent(
|
|
7780
|
+
input,
|
|
7781
|
+
"agent_read",
|
|
7782
|
+
input.leaseId,
|
|
7783
|
+
expiresAt
|
|
7784
|
+
)).session;
|
|
7785
|
+
} else if (session.leaseKind === "agent_read" && session.leaseId !== input.leaseId) {
|
|
7786
|
+
fail3("source_reader_lease_conflict");
|
|
7787
|
+
} else {
|
|
7788
|
+
session.leaseKind = "agent_read";
|
|
7789
|
+
session.leaseId = input.leaseId;
|
|
7790
|
+
session.leaseExpiresAt = expiresAt;
|
|
7791
|
+
scheduleLeaseDeadline(session);
|
|
7792
|
+
}
|
|
7793
|
+
} else {
|
|
7794
|
+
const root = await rootPromise;
|
|
7795
|
+
const profilePath = join13(root, profileName(input));
|
|
7796
|
+
if (!await verifyOwnedProfile(root, profilePath, input)) {
|
|
7797
|
+
fail3("browser_session_profile_missing");
|
|
7798
|
+
}
|
|
7799
|
+
session = (await launchPersistent(
|
|
7800
|
+
input,
|
|
7801
|
+
"agent_read",
|
|
7802
|
+
input.leaseId,
|
|
7803
|
+
expiresAt
|
|
7804
|
+
)).session;
|
|
7805
|
+
}
|
|
7806
|
+
try {
|
|
7807
|
+
const binding = await createPlaywrightSourceBrowser({
|
|
7808
|
+
context: session.context,
|
|
7809
|
+
page: session.page,
|
|
7810
|
+
maxSnapshotChars: input.limits?.maxSnapshotChars,
|
|
7811
|
+
assertNetworkAddressAllowed: networkScope.assertNetworkAddressAllowed
|
|
7812
|
+
});
|
|
7813
|
+
attachAbortSignal(session, input.signal);
|
|
7814
|
+
await binding.transport.takeSnapshot({ pageId: binding.pageId });
|
|
7815
|
+
return {
|
|
7816
|
+
...binding,
|
|
7817
|
+
release: () => closeBinding(input.bindingId)
|
|
7818
|
+
};
|
|
7819
|
+
} catch (error) {
|
|
7820
|
+
await closeBinding(input.bindingId);
|
|
7821
|
+
throw error;
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
async function resolvePublicBinding(input) {
|
|
7825
|
+
if (typeof playwright?.chromium?.launch !== "function") {
|
|
7826
|
+
fail3("browser_session_runtime_unavailable");
|
|
7827
|
+
}
|
|
7828
|
+
await networkScope.assertNetworkAddressAllowed(input.locator);
|
|
7829
|
+
const args = await browserLaunchArgs(input);
|
|
7830
|
+
const browser = await playwright.chromium.launch({
|
|
7831
|
+
headless: true,
|
|
7832
|
+
args,
|
|
7833
|
+
...browserExecutablePath ? { executablePath: browserExecutablePath } : {}
|
|
7834
|
+
});
|
|
7835
|
+
const context = await browser.newContext({
|
|
7836
|
+
acceptDownloads: false,
|
|
7837
|
+
serviceWorkers: "block"
|
|
7838
|
+
});
|
|
7839
|
+
const page = await context.newPage();
|
|
7840
|
+
const binding = await createPlaywrightSourceBrowser({
|
|
7841
|
+
context,
|
|
7842
|
+
page,
|
|
7843
|
+
maxSnapshotChars: input.limits?.maxSnapshotChars,
|
|
7844
|
+
assertNetworkAddressAllowed: networkScope.assertNetworkAddressAllowed
|
|
7845
|
+
});
|
|
7846
|
+
return {
|
|
7847
|
+
...binding,
|
|
7848
|
+
release: async () => {
|
|
7849
|
+
await closeQuietly(context);
|
|
7850
|
+
await closeQuietly(browser);
|
|
7851
|
+
}
|
|
7852
|
+
};
|
|
7853
|
+
}
|
|
7854
|
+
return Object.freeze({
|
|
7855
|
+
readiness() {
|
|
7856
|
+
if (typeof playwright?.chromium?.launchPersistentContext !== "function") {
|
|
7857
|
+
return { ready: false, reason: "playwright_unavailable" };
|
|
7858
|
+
}
|
|
7859
|
+
if (browserExecutablePath && !browserExecutableReady) {
|
|
7860
|
+
return { ready: false, reason: "chrome_unavailable" };
|
|
7861
|
+
}
|
|
7862
|
+
if (!browserExecutablePath && typeof playwright.chromium.executablePath === "function") {
|
|
7863
|
+
const executablePath = playwright.chromium.executablePath();
|
|
7864
|
+
if (!executablePath || !existsSync12(executablePath)) {
|
|
7865
|
+
return { ready: false, reason: "chrome_unavailable" };
|
|
7866
|
+
}
|
|
7867
|
+
}
|
|
7868
|
+
return { ready: true, reason: null };
|
|
7869
|
+
},
|
|
7870
|
+
openHumanSession,
|
|
7871
|
+
reopenHumanSession: openHumanSession,
|
|
7872
|
+
switchAccount,
|
|
7873
|
+
revokeBinding,
|
|
7874
|
+
resolveAuthenticatedBinding,
|
|
7875
|
+
resolvePublicBinding,
|
|
7876
|
+
async releaseLease({ bindingId, leaseId }) {
|
|
7877
|
+
const session = sessions.get(bindingId);
|
|
7878
|
+
if (!session || session.leaseId !== leaseId) return false;
|
|
7879
|
+
await closeBinding(bindingId);
|
|
7880
|
+
return true;
|
|
7881
|
+
},
|
|
7882
|
+
async shutdown() {
|
|
7883
|
+
await Promise.all([...sessions.keys()].map(closeBinding));
|
|
7884
|
+
}
|
|
7885
|
+
});
|
|
7886
|
+
}
|
|
7887
|
+
|
|
7888
|
+
// src/amaster-runtime-daemon/browser-session-command.mjs
|
|
7889
|
+
var BROWSER_SESSION_CONTRACT_VERSION = "amaster.browser-session.v1";
|
|
7890
|
+
var PAYLOAD_FIELDS = /* @__PURE__ */ new Set([
|
|
7891
|
+
"contractVersion",
|
|
7892
|
+
"actionId",
|
|
7893
|
+
"companyId",
|
|
7894
|
+
"bindingId",
|
|
7895
|
+
"localOpaqueRef",
|
|
7896
|
+
"provider",
|
|
7897
|
+
"origin",
|
|
7898
|
+
"locator",
|
|
7899
|
+
"operation",
|
|
7900
|
+
"previousBinding",
|
|
7901
|
+
"leaseId",
|
|
7902
|
+
"leaseExpiresAt"
|
|
7903
|
+
]);
|
|
7904
|
+
var OPERATIONS = /* @__PURE__ */ new Set(["open", "reopen", "switch_account", "revoke"]);
|
|
7905
|
+
var PROVIDERS = /* @__PURE__ */ new Set([
|
|
7906
|
+
"feishu",
|
|
7907
|
+
"dingtalk",
|
|
7908
|
+
"wechat_work",
|
|
7909
|
+
"notion",
|
|
7910
|
+
"github_gitlab",
|
|
7911
|
+
"crm",
|
|
7912
|
+
"erp",
|
|
7913
|
+
"custom",
|
|
7914
|
+
"other"
|
|
7915
|
+
]);
|
|
7916
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
7917
|
+
var ZERO_UUID = "00000000-0000-4000-8000-000000000000";
|
|
7918
|
+
function isRecord4(value) {
|
|
7919
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7920
|
+
}
|
|
7921
|
+
function identity(value) {
|
|
7922
|
+
return typeof value === "string" && UUID.test(value.trim()) ? value.trim() : null;
|
|
7923
|
+
}
|
|
7924
|
+
function safeTimestamp2(now) {
|
|
7925
|
+
try {
|
|
7926
|
+
const value = now();
|
|
7927
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
7928
|
+
if (Number.isFinite(date.getTime())) return date.toISOString();
|
|
7929
|
+
} catch {
|
|
7930
|
+
}
|
|
7931
|
+
return "1970-01-01T00:00:00.000Z";
|
|
7932
|
+
}
|
|
7933
|
+
function parseLocator(value, exactOrigin = false) {
|
|
7934
|
+
if (typeof value !== "string" || !value.trim() || value.length > 8192) return null;
|
|
7935
|
+
let url;
|
|
7936
|
+
try {
|
|
7937
|
+
url = new URL(value);
|
|
7938
|
+
} catch {
|
|
7939
|
+
return null;
|
|
7940
|
+
}
|
|
7941
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password) return null;
|
|
7942
|
+
if (exactOrigin && (value !== url.origin || url.pathname !== "/" || url.search || url.hash)) {
|
|
7943
|
+
return null;
|
|
7944
|
+
}
|
|
7945
|
+
return value;
|
|
7946
|
+
}
|
|
7947
|
+
function normalizePayload(value) {
|
|
7948
|
+
if (!isRecord4(value) || Object.keys(value).some((field) => !PAYLOAD_FIELDS.has(field)) || value.contractVersion !== BROWSER_SESSION_CONTRACT_VERSION || !identity(value.actionId) || !identity(value.companyId) || !identity(value.bindingId) || typeof value.localOpaqueRef !== "string" || !/^profile_[a-z0-9]{16,64}$/u.test(value.localOpaqueRef) || !PROVIDERS.has(value.provider) || !OPERATIONS.has(value.operation) || typeof value.leaseId !== "string" || !value.leaseId.trim() || value.leaseId.length > 255 || typeof value.leaseExpiresAt !== "string" || !Number.isFinite(Date.parse(value.leaseExpiresAt))) return null;
|
|
7949
|
+
const previousBinding = isRecord4(value.previousBinding) && Object.keys(value.previousBinding).length === 2 && identity(value.previousBinding.bindingId) && /^profile_[a-z0-9]{16,64}$/u.test(value.previousBinding.localOpaqueRef) ? {
|
|
7950
|
+
bindingId: identity(value.previousBinding.bindingId),
|
|
7951
|
+
localOpaqueRef: value.previousBinding.localOpaqueRef
|
|
7952
|
+
} : null;
|
|
7953
|
+
if (value.operation === "switch_account" ? !previousBinding || previousBinding.bindingId === value.bindingId : value.previousBinding !== void 0) return null;
|
|
7954
|
+
const origin = parseLocator(value.origin, true);
|
|
7955
|
+
const locator = parseLocator(value.locator);
|
|
7956
|
+
if (!origin || !locator || new URL(locator).origin !== origin) return null;
|
|
7957
|
+
return {
|
|
7958
|
+
contractVersion: BROWSER_SESSION_CONTRACT_VERSION,
|
|
7959
|
+
actionId: value.actionId,
|
|
7960
|
+
companyId: value.companyId,
|
|
7961
|
+
bindingId: value.bindingId,
|
|
7962
|
+
localOpaqueRef: value.localOpaqueRef,
|
|
7963
|
+
provider: value.provider,
|
|
7964
|
+
origin,
|
|
7965
|
+
locator,
|
|
7966
|
+
operation: value.operation,
|
|
7967
|
+
...previousBinding ? { previousBinding } : {},
|
|
7968
|
+
leaseId: value.leaseId.trim(),
|
|
7969
|
+
leaseExpiresAt: new Date(value.leaseExpiresAt).toISOString()
|
|
7970
|
+
};
|
|
7971
|
+
}
|
|
7972
|
+
function safeContext(command) {
|
|
7973
|
+
const payload = isRecord4(command?.payload) ? command.payload : {};
|
|
7974
|
+
return {
|
|
7975
|
+
actionId: identity(payload.actionId) ?? ZERO_UUID,
|
|
7976
|
+
companyId: identity(payload.companyId) ?? ZERO_UUID,
|
|
7977
|
+
bindingId: identity(payload.bindingId) ?? ZERO_UUID,
|
|
7978
|
+
connectorId: identity(command?.connectorId) ?? ZERO_UUID,
|
|
7979
|
+
commandId: identity(command?.commandId) ?? ZERO_UUID,
|
|
7980
|
+
operation: OPERATIONS.has(payload.operation) ? payload.operation : "open"
|
|
7981
|
+
};
|
|
7982
|
+
}
|
|
7983
|
+
function failureCode2(error) {
|
|
7984
|
+
const code = typeof error?.code === "string" ? error.code : "";
|
|
7985
|
+
if (/^browser_auth_[a-z0-9_]{1,110}$/u.test(code)) return code;
|
|
7986
|
+
if (/^browser_session_[a-z0-9_]{1,102}$/u.test(code)) {
|
|
7987
|
+
return `browser_auth_${code.slice("browser_session_".length)}`;
|
|
7988
|
+
}
|
|
7989
|
+
return "browser_auth_runtime_failed";
|
|
7990
|
+
}
|
|
7991
|
+
function result(context, startedAt, completedAt, outcome, code = null) {
|
|
7992
|
+
return {
|
|
7993
|
+
contractVersion: BROWSER_SESSION_CONTRACT_VERSION,
|
|
7994
|
+
actionId: context.actionId,
|
|
7995
|
+
companyId: context.companyId,
|
|
7996
|
+
bindingId: context.bindingId,
|
|
7997
|
+
connectorId: context.connectorId,
|
|
7998
|
+
commandId: context.commandId,
|
|
7999
|
+
operation: context.operation,
|
|
8000
|
+
outcome,
|
|
8001
|
+
startedAt,
|
|
8002
|
+
completedAt,
|
|
8003
|
+
passiveEffectsPossible: true,
|
|
8004
|
+
failureCode: code
|
|
8005
|
+
};
|
|
8006
|
+
}
|
|
8007
|
+
async function executeBrowserSessionCommand(command, dependencies = {}) {
|
|
8008
|
+
const now = typeof dependencies.now === "function" ? dependencies.now : () => /* @__PURE__ */ new Date();
|
|
8009
|
+
const startedAt = safeTimestamp2(now);
|
|
8010
|
+
const context = safeContext(command);
|
|
8011
|
+
const payload = normalizePayload(command?.payload);
|
|
8012
|
+
if (!payload || !identity(command?.commandId) || !identity(command?.connectorId)) {
|
|
8013
|
+
return result(
|
|
8014
|
+
context,
|
|
8015
|
+
startedAt,
|
|
8016
|
+
safeTimestamp2(now),
|
|
8017
|
+
"failed",
|
|
8018
|
+
"browser_auth_command_invalid"
|
|
8019
|
+
);
|
|
8020
|
+
}
|
|
8021
|
+
const broker = dependencies.broker;
|
|
8022
|
+
const method = payload.operation === "open" ? "openHumanSession" : payload.operation === "reopen" ? "reopenHumanSession" : payload.operation === "switch_account" ? "switchAccount" : "revokeBinding";
|
|
8023
|
+
try {
|
|
8024
|
+
if (typeof broker?.[method] !== "function") {
|
|
8025
|
+
throw Object.assign(new Error("browser_session_runtime_unavailable"), {
|
|
8026
|
+
code: "browser_session_runtime_unavailable"
|
|
8027
|
+
});
|
|
8028
|
+
}
|
|
8029
|
+
await broker[method](payload);
|
|
8030
|
+
return result(
|
|
8031
|
+
context,
|
|
8032
|
+
startedAt,
|
|
8033
|
+
safeTimestamp2(now),
|
|
8034
|
+
payload.operation === "revoke" ? "revoked" : "ready"
|
|
8035
|
+
);
|
|
8036
|
+
} catch (error) {
|
|
8037
|
+
return result(
|
|
8038
|
+
context,
|
|
8039
|
+
startedAt,
|
|
8040
|
+
safeTimestamp2(now),
|
|
8041
|
+
"failed",
|
|
8042
|
+
failureCode2(error)
|
|
8043
|
+
);
|
|
8044
|
+
}
|
|
8045
|
+
}
|
|
8046
|
+
|
|
8047
|
+
// src/amaster-runtime-daemon/public-network-scope.mjs
|
|
8048
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
8049
|
+
import { BlockList, isIP } from "node:net";
|
|
8050
|
+
var blockedV4Addresses = new BlockList();
|
|
8051
|
+
for (const [network, prefix] of [
|
|
8052
|
+
["0.0.0.0", 8],
|
|
8053
|
+
["10.0.0.0", 8],
|
|
8054
|
+
["100.64.0.0", 10],
|
|
8055
|
+
["127.0.0.0", 8],
|
|
8056
|
+
["169.254.0.0", 16],
|
|
8057
|
+
["172.16.0.0", 12],
|
|
8058
|
+
["192.0.0.0", 24],
|
|
8059
|
+
["192.0.2.0", 24],
|
|
8060
|
+
["192.88.99.0", 24],
|
|
8061
|
+
["192.168.0.0", 16],
|
|
8062
|
+
["198.18.0.0", 15],
|
|
8063
|
+
["198.51.100.0", 24],
|
|
8064
|
+
["203.0.113.0", 24],
|
|
8065
|
+
["224.0.0.0", 4],
|
|
8066
|
+
["240.0.0.0", 4]
|
|
8067
|
+
]) {
|
|
8068
|
+
blockedV4Addresses.addSubnet(network, prefix, "ipv4");
|
|
8069
|
+
}
|
|
8070
|
+
var blockedV6Addresses = new BlockList();
|
|
8071
|
+
for (const [network, prefix] of [
|
|
8072
|
+
["::", 128],
|
|
8073
|
+
["::1", 128],
|
|
8074
|
+
["::", 96],
|
|
8075
|
+
["::ffff:0:0", 96],
|
|
8076
|
+
["64:ff9b::", 96],
|
|
8077
|
+
["64:ff9b:1::", 48],
|
|
8078
|
+
["100::", 64],
|
|
8079
|
+
["2001::", 32],
|
|
8080
|
+
["2001:2::", 48],
|
|
8081
|
+
["2001:db8::", 32],
|
|
8082
|
+
["2001:10::", 28],
|
|
8083
|
+
["2001:20::", 28],
|
|
8084
|
+
["2002::", 16],
|
|
8085
|
+
["fc00::", 7],
|
|
8086
|
+
["fe80::", 10],
|
|
8087
|
+
["fec0::", 10],
|
|
8088
|
+
["ff00::", 8]
|
|
8089
|
+
]) {
|
|
8090
|
+
blockedV6Addresses.addSubnet(network, prefix, "ipv6");
|
|
8091
|
+
}
|
|
8092
|
+
function forbidden() {
|
|
8093
|
+
return Object.assign(new Error("source_reader_network_scope_forbidden"), {
|
|
8094
|
+
code: "source_reader_network_scope_forbidden"
|
|
8095
|
+
});
|
|
8096
|
+
}
|
|
8097
|
+
function parsePublicLocator(value) {
|
|
8098
|
+
let url;
|
|
8099
|
+
try {
|
|
8100
|
+
url = new URL(value);
|
|
8101
|
+
} catch {
|
|
8102
|
+
throw forbidden();
|
|
8103
|
+
}
|
|
8104
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !url.hostname) {
|
|
8105
|
+
throw forbidden();
|
|
8106
|
+
}
|
|
8107
|
+
const hostname3 = url.hostname.toLowerCase().replace(/\.$/u, "");
|
|
8108
|
+
if (hostname3 === "localhost" || hostname3.endsWith(".localhost") || hostname3.endsWith(".local")) {
|
|
8109
|
+
throw forbidden();
|
|
8110
|
+
}
|
|
8111
|
+
return { url, hostname: hostname3 };
|
|
8112
|
+
}
|
|
8113
|
+
function addressIsPublic(address) {
|
|
8114
|
+
const family = isIP(address);
|
|
8115
|
+
if (family === 4) return !blockedV4Addresses.check(address, "ipv4");
|
|
8116
|
+
if (family === 6) return !blockedV6Addresses.check(address, "ipv6");
|
|
8117
|
+
return false;
|
|
8118
|
+
}
|
|
8119
|
+
function createPublicNetworkScope(options = {}) {
|
|
8120
|
+
const lookup = options.lookup ?? ((hostname3) => dnsLookup(hostname3, {
|
|
8121
|
+
all: true,
|
|
8122
|
+
verbatim: true
|
|
8123
|
+
}));
|
|
8124
|
+
async function resolveNetworkAddresses(locator) {
|
|
8125
|
+
const { hostname: hostname3 } = parsePublicLocator(locator);
|
|
8126
|
+
let addresses;
|
|
8127
|
+
try {
|
|
8128
|
+
addresses = await lookup(hostname3);
|
|
8129
|
+
} catch {
|
|
8130
|
+
throw forbidden();
|
|
8131
|
+
}
|
|
8132
|
+
if (!Array.isArray(addresses) || addresses.length === 0 || addresses.some((entry) => !entry || typeof entry.address !== "string" || !addressIsPublic(entry.address))) {
|
|
8133
|
+
throw forbidden();
|
|
8134
|
+
}
|
|
8135
|
+
return [...new Set(addresses.map((entry) => entry.address))].sort();
|
|
8136
|
+
}
|
|
8137
|
+
return Object.freeze({
|
|
8138
|
+
resolveNetworkAddresses,
|
|
8139
|
+
async assertNetworkAddressAllowed(locator) {
|
|
8140
|
+
await resolveNetworkAddresses(locator);
|
|
8141
|
+
return true;
|
|
8142
|
+
}
|
|
8143
|
+
});
|
|
8144
|
+
}
|
|
8145
|
+
|
|
6255
8146
|
// src/amaster-runtime-daemon.mjs
|
|
6256
|
-
var CONNECTOR_VERSION = "0.1.0-beta.
|
|
8147
|
+
var CONNECTOR_VERSION = "0.1.0-beta.48";
|
|
6257
8148
|
var CONNECTOR_CONTRACT_VERSION = "2026-06-04.v1";
|
|
6258
8149
|
var MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
|
|
6259
8150
|
var CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -6278,6 +8169,56 @@ var piChildIdentityAllocator = null;
|
|
|
6278
8169
|
var piChildIdentityAllocatorKey = null;
|
|
6279
8170
|
var trustedPiRuntimeProvenanceCache = createTrustedPiRuntimeProvenanceCache();
|
|
6280
8171
|
var lastPartialTrustedPiRuntimeSourceWarningKey = null;
|
|
8172
|
+
var browserSessionBroker = null;
|
|
8173
|
+
var browserSessionBrokerKey = null;
|
|
8174
|
+
var playwrightRuntime;
|
|
8175
|
+
function configuredPlaywrightRuntime() {
|
|
8176
|
+
if (playwrightRuntime !== void 0) return playwrightRuntime;
|
|
8177
|
+
try {
|
|
8178
|
+
const loaded = createRequire(import.meta.url)("playwright-core");
|
|
8179
|
+
playwrightRuntime = typeof loaded?.chromium?.launchPersistentContext === "function" ? { chromium: loaded.chromium } : null;
|
|
8180
|
+
} catch {
|
|
8181
|
+
playwrightRuntime = null;
|
|
8182
|
+
}
|
|
8183
|
+
return playwrightRuntime;
|
|
8184
|
+
}
|
|
8185
|
+
function configuredBrowserExecutablePath(config) {
|
|
8186
|
+
const explicit = readString(config.browserExecutablePath);
|
|
8187
|
+
const candidates = [
|
|
8188
|
+
explicit,
|
|
8189
|
+
process.platform === "darwin" ? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" : null,
|
|
8190
|
+
process.platform === "darwin" ? "/Applications/Chromium.app/Contents/MacOS/Chromium" : null,
|
|
8191
|
+
process.platform === "darwin" ? "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" : null,
|
|
8192
|
+
process.platform === "linux" ? "/usr/bin/google-chrome-stable" : null,
|
|
8193
|
+
process.platform === "linux" ? "/usr/bin/google-chrome" : null,
|
|
8194
|
+
process.platform === "linux" ? "/usr/bin/chromium" : null,
|
|
8195
|
+
process.platform === "linux" ? "/usr/bin/chromium-browser" : null,
|
|
8196
|
+
process.platform === "win32" && process.env.PROGRAMFILES ? join14(process.env.PROGRAMFILES, "Google", "Chrome", "Application", "chrome.exe") : null
|
|
8197
|
+
].filter(Boolean);
|
|
8198
|
+
return candidates.find((candidate) => existsSync13(candidate)) ?? explicit ?? null;
|
|
8199
|
+
}
|
|
8200
|
+
function runtimeBrowserSessionBroker(config) {
|
|
8201
|
+
const executablePath = configuredBrowserExecutablePath(config);
|
|
8202
|
+
const key = `${resolve12(config.browserSessionStateRoot)}:${executablePath ?? "missing"}`;
|
|
8203
|
+
if (!browserSessionBroker || browserSessionBrokerKey !== key) {
|
|
8204
|
+
browserSessionBroker = createBrowserSessionBroker({
|
|
8205
|
+
stateRoot: config.browserSessionStateRoot,
|
|
8206
|
+
playwright: configuredPlaywrightRuntime(),
|
|
8207
|
+
browserExecutablePath: executablePath,
|
|
8208
|
+
publicNetworkScope: createPublicNetworkScope()
|
|
8209
|
+
});
|
|
8210
|
+
browserSessionBrokerKey = key;
|
|
8211
|
+
}
|
|
8212
|
+
return browserSessionBroker;
|
|
8213
|
+
}
|
|
8214
|
+
function browserCapabilityStatus(config, capability) {
|
|
8215
|
+
const advertised = config.capabilities.includes(capability);
|
|
8216
|
+
if (!advertised) {
|
|
8217
|
+
return { advertised, ready: false, reason: "capability_not_advertised" };
|
|
8218
|
+
}
|
|
8219
|
+
const readiness = runtimeBrowserSessionBroker(config).readiness();
|
|
8220
|
+
return { advertised, ...readiness };
|
|
8221
|
+
}
|
|
6281
8222
|
function configuredPiChildIdentityAllocator(config) {
|
|
6282
8223
|
if (!(config.piChildUidBase > 0)) return null;
|
|
6283
8224
|
const key = `${config.piChildUidBase}:${config.piChildUidSpan}`;
|
|
@@ -6301,11 +8242,11 @@ function updateActiveRunWorkspaceManifest(commandId, manifestPath, patch = {}) {
|
|
|
6301
8242
|
}
|
|
6302
8243
|
function resultOutboxPendingCount(config) {
|
|
6303
8244
|
const dir = resultOutboxDir(config);
|
|
6304
|
-
if (!
|
|
8245
|
+
if (!existsSync13(dir)) return 0;
|
|
6305
8246
|
try {
|
|
6306
8247
|
let pending = 0;
|
|
6307
8248
|
for (const file of readdirSync8(dir).filter((name) => name.endsWith(".json"))) {
|
|
6308
|
-
if (readValidResultOutboxEntryOrQuarantine(config, file,
|
|
8249
|
+
if (readValidResultOutboxEntryOrQuarantine(config, file, join14(dir, file))) {
|
|
6309
8250
|
pending += 1;
|
|
6310
8251
|
}
|
|
6311
8252
|
}
|
|
@@ -6317,15 +8258,15 @@ function resultOutboxPendingCount(config) {
|
|
|
6317
8258
|
function piCompletionOutputType(event) {
|
|
6318
8259
|
const type = readString(asRecord(event).type);
|
|
6319
8260
|
if (PI_COMPLETION_OUTPUT_TYPES.has(type ?? "")) return type;
|
|
6320
|
-
return piMcpToolResults(event).some((
|
|
8261
|
+
return piMcpToolResults(event).some((result3) => result3.status === "approval_required") ? "approval_required" : null;
|
|
6321
8262
|
}
|
|
6322
8263
|
function resultOutboxActiveRunCommands(config) {
|
|
6323
8264
|
const dir = resultOutboxDir(config);
|
|
6324
|
-
if (!
|
|
8265
|
+
if (!existsSync13(dir)) return [];
|
|
6325
8266
|
const outboxPending = resultOutboxPendingCount(config);
|
|
6326
8267
|
const entries = [];
|
|
6327
8268
|
for (const file of readdirSync8(dir).filter((name) => name.endsWith(".json")).sort()) {
|
|
6328
|
-
const entry = readValidResultOutboxEntryOrQuarantine(config, file,
|
|
8269
|
+
const entry = readValidResultOutboxEntryOrQuarantine(config, file, join14(dir, file));
|
|
6329
8270
|
if (!entry) continue;
|
|
6330
8271
|
const activeRun = asRecord(entry.activeRun);
|
|
6331
8272
|
const commandId = readString(activeRun.commandId) ?? readString(entry.commandId);
|
|
@@ -6355,12 +8296,12 @@ function resultOutboxActiveRunCommands(config) {
|
|
|
6355
8296
|
}
|
|
6356
8297
|
function resultOutboxFailedRunCommands(config) {
|
|
6357
8298
|
const dir = resultOutboxInvalidDir(config);
|
|
6358
|
-
if (!
|
|
8299
|
+
if (!existsSync13(dir)) return [];
|
|
6359
8300
|
const entries = [];
|
|
6360
8301
|
for (const file of readdirSync8(dir).filter((name) => name.endsWith(".json")).sort().slice(-100)) {
|
|
6361
8302
|
let entry;
|
|
6362
8303
|
try {
|
|
6363
|
-
entry = asRecord(JSON.parse(readFileSync10(
|
|
8304
|
+
entry = asRecord(JSON.parse(readFileSync10(join14(dir, file), "utf8")));
|
|
6364
8305
|
} catch {
|
|
6365
8306
|
continue;
|
|
6366
8307
|
}
|
|
@@ -6420,7 +8361,7 @@ function piExtraArgsDiagnostics(value) {
|
|
|
6420
8361
|
}
|
|
6421
8362
|
function safeExpandPath(value) {
|
|
6422
8363
|
const text = readString(value);
|
|
6423
|
-
return text ?
|
|
8364
|
+
return text ? resolve12(expandHomePath(text)) : null;
|
|
6424
8365
|
}
|
|
6425
8366
|
function safeJsonObjectFromFile(filePath) {
|
|
6426
8367
|
try {
|
|
@@ -6467,10 +8408,10 @@ function safeSkillRootSummary(kind, source, pathValue) {
|
|
|
6467
8408
|
try {
|
|
6468
8409
|
for (const name of readdirSync8(pathValue)) {
|
|
6469
8410
|
if (name.startsWith(".")) continue;
|
|
6470
|
-
const skillDir =
|
|
8411
|
+
const skillDir = join14(pathValue, name);
|
|
6471
8412
|
try {
|
|
6472
8413
|
if (!statSync7(skillDir).isDirectory()) continue;
|
|
6473
|
-
if (!
|
|
8414
|
+
if (!existsSync13(join14(skillDir, "SKILL.md"))) continue;
|
|
6474
8415
|
skillCount += 1;
|
|
6475
8416
|
if (skillCount >= MAX_PI_CAPABILITY_SOURCE_ENTRIES) {
|
|
6476
8417
|
truncated = true;
|
|
@@ -6521,7 +8462,7 @@ function objectKeyCount(value) {
|
|
|
6521
8462
|
return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value).length : 0;
|
|
6522
8463
|
}
|
|
6523
8464
|
function defaultPiCodingAgentDir() {
|
|
6524
|
-
return
|
|
8465
|
+
return join14(homedir3(), ".pi", "agent");
|
|
6525
8466
|
}
|
|
6526
8467
|
function piCapabilitySourcesDiagnostics() {
|
|
6527
8468
|
const configuredPiCodingAgentDir = safeExpandPath(process.env.PI_CODING_AGENT_DIR);
|
|
@@ -6530,14 +8471,14 @@ function piCapabilitySourcesDiagnostics() {
|
|
|
6530
8471
|
const configuredPiAgentHome = safeExpandPath(process.env.PI_AGENT_HOME);
|
|
6531
8472
|
const piAgentHome = configuredPiAgentHome ?? piCodingAgentDir;
|
|
6532
8473
|
const piAgentHomeSource = configuredPiAgentHome ? "PI_AGENT_HOME" : piCodingAgentDirSource;
|
|
6533
|
-
const userSkillsPath = piAgentHome ?
|
|
8474
|
+
const userSkillsPath = piAgentHome ? join14(piAgentHome, "skills") : null;
|
|
6534
8475
|
const configuredMarketplaceSkillsPath = safeExpandPath(
|
|
6535
8476
|
process.env.PI_AGENT_MARKETPLACE_SKILLS_DIR
|
|
6536
8477
|
);
|
|
6537
|
-
const marketplaceSkillsPath = configuredMarketplaceSkillsPath ?? (piAgentHome ?
|
|
8478
|
+
const marketplaceSkillsPath = configuredMarketplaceSkillsPath ?? (piAgentHome ? join14(piAgentHome, "marketplace", "skills") : null);
|
|
6538
8479
|
const builtinSkillsPath = safeExpandPath(process.env.PI_AGENT_BUILTIN_SKILLS_DIR) ?? safeExpandPath(process.env.AMASTER_BUILTIN_SKILLS);
|
|
6539
|
-
const mcpConfigPath = safeExpandPath(process.env.PI_AGENT_MCP_SERVERS_FILE) ?? (piAgentHome ?
|
|
6540
|
-
const settingsConfigPath = piCodingAgentDir ?
|
|
8480
|
+
const mcpConfigPath = safeExpandPath(process.env.PI_AGENT_MCP_SERVERS_FILE) ?? (piAgentHome ? join14(piAgentHome, "mcp.json") : null);
|
|
8481
|
+
const settingsConfigPath = piCodingAgentDir ? join14(piCodingAgentDir, "settings.json") : null;
|
|
6541
8482
|
const skillRoots = [
|
|
6542
8483
|
safeSkillRootSummary("user", `${piAgentHomeSource}/skills`, userSkillsPath),
|
|
6543
8484
|
safeSkillRootSummary(
|
|
@@ -6731,6 +8672,8 @@ function buildHeartbeatPayload(config, options = {}) {
|
|
|
6731
8672
|
const activeRunCommandList = Array.from(activeRunCommandById.values()).map((entry) => buildActiveRunCommandStatus(config, entry));
|
|
6732
8673
|
const executorReadiness = buildExecutorReadiness(config);
|
|
6733
8674
|
const platformCredential = piAgentLocalPlatformCredentialStatus(config);
|
|
8675
|
+
const headedBrowserAuth = browserCapabilityStatus(config, "headed_browser_auth");
|
|
8676
|
+
const constrainedSourceRead = browserCapabilityStatus(config, "constrained_source_read");
|
|
6734
8677
|
return {
|
|
6735
8678
|
status: "online",
|
|
6736
8679
|
workspaceBindings: config.workspaceBindings,
|
|
@@ -6757,6 +8700,14 @@ function buildHeartbeatPayload(config, options = {}) {
|
|
|
6757
8700
|
stateFile: stateFilePath(process.env),
|
|
6758
8701
|
...executorReadiness.length > 0 ? { executorReadiness } : {},
|
|
6759
8702
|
...platformCredential ? { platformCredential } : {},
|
|
8703
|
+
constrainedSourceReader: {
|
|
8704
|
+
capability: "constrained_source_read",
|
|
8705
|
+
...constrainedSourceRead
|
|
8706
|
+
},
|
|
8707
|
+
browserSessionBroker: {
|
|
8708
|
+
capability: "headed_browser_auth",
|
|
8709
|
+
...headedBrowserAuth
|
|
8710
|
+
},
|
|
6760
8711
|
...versionDrift.versionDrift || versionDrift.bundleDrift ? versionDrift : {}
|
|
6761
8712
|
},
|
|
6762
8713
|
metadata: {
|
|
@@ -6770,13 +8721,13 @@ function piAgentLocalPlatformRunnerEnabled(config) {
|
|
|
6770
8721
|
}
|
|
6771
8722
|
function piAgentSystemDataDir(config) {
|
|
6772
8723
|
const configured = readString(config.AMASTER_PI_AGENT_SYSTEM_DATA_DIR ?? process.env.AMASTER_PI_AGENT_SYSTEM_DATA_DIR);
|
|
6773
|
-
return configured ?
|
|
8724
|
+
return configured ? resolve12(expandHomePath(configured)) : null;
|
|
6774
8725
|
}
|
|
6775
8726
|
function readPiAgentLocalPlatformCredential(credentialsDir) {
|
|
6776
|
-
const pointer = readJsonFile3(
|
|
8727
|
+
const pointer = readJsonFile3(join14(credentialsDir, "latest.json"));
|
|
6777
8728
|
const credentialRef = readString(pointer.credentialRef);
|
|
6778
8729
|
if (!credentialRef || !/^[a-f0-9]{64}$/i.test(credentialRef)) return null;
|
|
6779
|
-
const credential = readJsonFile3(
|
|
8730
|
+
const credential = readJsonFile3(join14(credentialsDir, `${credentialRef}.json`));
|
|
6780
8731
|
const organizationId = readString(credential.organizationId);
|
|
6781
8732
|
const apiKey = readString(credential.apiKey);
|
|
6782
8733
|
if (credential.version !== 1 || !organizationId || !apiKey) return null;
|
|
@@ -6792,7 +8743,7 @@ function piAgentLocalPlatformCredentials(config) {
|
|
|
6792
8743
|
if (!piAgentLocalPlatformRunnerEnabled(config)) return [];
|
|
6793
8744
|
const systemDataDir = piAgentSystemDataDir(config);
|
|
6794
8745
|
if (!systemDataDir) return [];
|
|
6795
|
-
const companiesDir =
|
|
8746
|
+
const companiesDir = join14(systemDataDir, "companies");
|
|
6796
8747
|
let entries = [];
|
|
6797
8748
|
try {
|
|
6798
8749
|
entries = readdirSync8(companiesDir, { withFileTypes: true });
|
|
@@ -6802,7 +8753,7 @@ function piAgentLocalPlatformCredentials(config) {
|
|
|
6802
8753
|
const credentialsByOrganizationId = /* @__PURE__ */ new Map();
|
|
6803
8754
|
for (const entry of entries) {
|
|
6804
8755
|
if (!entry.isDirectory()) continue;
|
|
6805
|
-
const credential = readPiAgentLocalPlatformCredential(
|
|
8756
|
+
const credential = readPiAgentLocalPlatformCredential(join14(companiesDir, entry.name, "model-credentials"));
|
|
6806
8757
|
if (credential) credentialsByOrganizationId.set(credential.organizationId, credential);
|
|
6807
8758
|
}
|
|
6808
8759
|
return [...credentialsByOrganizationId.values()];
|
|
@@ -6976,7 +8927,7 @@ AMASTER_WORKSPACE_ALLOWLIST=${quoteShell(config.workspaceBindings.join(","))}
|
|
|
6976
8927
|
AMASTER_EXECUTORS=${quoteShell(executorEnv)}
|
|
6977
8928
|
AMASTER_CAPABILITIES=${quoteShell(config.capabilities.join(","))}
|
|
6978
8929
|
AMASTER_NETWORK_DOMAINS=${quoteShell(config.networkDomains.join(","))}
|
|
6979
|
-
AMASTER_DAEMON_STATE_FILE=${quoteShell(
|
|
8930
|
+
AMASTER_DAEMON_STATE_FILE=${quoteShell(join14(homedir3(), ".amaster-employee", "runtime-connector-state.json"))}
|
|
6980
8931
|
EOF
|
|
6981
8932
|
|
|
6982
8933
|
set -a
|
|
@@ -7164,10 +9115,10 @@ function buildActiveRunCommandStatus(config, entry) {
|
|
|
7164
9115
|
const base = {
|
|
7165
9116
|
...entry,
|
|
7166
9117
|
phase: readString(entry.phase) ?? "executing",
|
|
7167
|
-
managedWorkdirPresent: entry.workspacePath ?
|
|
9118
|
+
managedWorkdirPresent: entry.workspacePath ? existsSync13(entry.workspacePath) : false,
|
|
7168
9119
|
outboxPending: resultOutboxPendingCount(config)
|
|
7169
9120
|
};
|
|
7170
|
-
if (!entry.workspacePath || !
|
|
9121
|
+
if (!entry.workspacePath || !existsSync13(entry.workspacePath)) return base;
|
|
7171
9122
|
const manifestPath = entry.manifestPath ?? workspaceManifestPath(entry.workspacePath);
|
|
7172
9123
|
const status = readWorkspaceStatus(entry.workspacePath, { hashCache: workspaceStatusHashCache });
|
|
7173
9124
|
const artifactCandidates = status.artifacts.slice(0, 20);
|
|
@@ -7421,7 +9372,7 @@ function safeAgentInstructionMaterializationTarget(workspace, filePath) {
|
|
|
7421
9372
|
const segments = normalized.split("/").filter(Boolean);
|
|
7422
9373
|
if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) return null;
|
|
7423
9374
|
const relativePath = segments.join("/");
|
|
7424
|
-
const targetPath =
|
|
9375
|
+
const targetPath = resolve12(workspace.cwd, relativePath);
|
|
7425
9376
|
if (!pathWithin2(targetPath, workspace.cwd)) return null;
|
|
7426
9377
|
return { relativePath, targetPath };
|
|
7427
9378
|
}
|
|
@@ -7493,6 +9444,8 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
7493
9444
|
sourceWorkspacePath: workspaceContext.sourceWorkspacePath,
|
|
7494
9445
|
wakeReason: readString(context.wakeReason),
|
|
7495
9446
|
hasGovernedMcp,
|
|
9447
|
+
executorKind: options.executorKind,
|
|
9448
|
+
managedMcpToolMode: options.managedMcpToolMode,
|
|
7496
9449
|
agentInstructions,
|
|
7497
9450
|
taskMarkdown,
|
|
7498
9451
|
attachmentsText,
|
|
@@ -7503,16 +9456,16 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
7503
9456
|
}
|
|
7504
9457
|
function companyPiHomeRoot(baseEnv) {
|
|
7505
9458
|
const explicitRoot = readString(baseEnv.AMASTER_COMPANY_PI_HOME_ROOT);
|
|
7506
|
-
if (explicitRoot) return
|
|
9459
|
+
if (explicitRoot) return resolve12(expandHomePath(explicitRoot));
|
|
7507
9460
|
const configuredPiHome = readString(baseEnv.PI_AGENT_HOME) ?? readString(baseEnv.PI_CODING_AGENT_DIR);
|
|
7508
|
-
if (configuredPiHome) return
|
|
7509
|
-
return
|
|
9461
|
+
if (configuredPiHome) return join14(dirname9(resolve12(expandHomePath(configuredPiHome))), "companies");
|
|
9462
|
+
return join14(homedir3(), ".amaster-employee", "companies");
|
|
7510
9463
|
}
|
|
7511
9464
|
function companyPiAgentHome(baseEnv, companyId) {
|
|
7512
9465
|
const rawCompanyId = readString(companyId);
|
|
7513
9466
|
if (!rawCompanyId) return null;
|
|
7514
9467
|
const segment = safeCompanyPiHomeSegment(rawCompanyId);
|
|
7515
|
-
return
|
|
9468
|
+
return join14(companyPiHomeRoot(baseEnv), segment, ".pi");
|
|
7516
9469
|
}
|
|
7517
9470
|
function commandUsesPiExecutor(command) {
|
|
7518
9471
|
return readString(asRecord(command.payload).executorKind) === "pi";
|
|
@@ -7579,9 +9532,9 @@ function resolveNativeSessionRequest(command, workspace) {
|
|
|
7579
9532
|
let cwdMatched = false;
|
|
7580
9533
|
if (requestedCwd && sourceWorkspacePath) {
|
|
7581
9534
|
try {
|
|
7582
|
-
cwdMatched =
|
|
9535
|
+
cwdMatched = realpathSync4(requestedCwd) === realpathSync4(sourceWorkspacePath);
|
|
7583
9536
|
} catch {
|
|
7584
|
-
cwdMatched =
|
|
9537
|
+
cwdMatched = resolve12(requestedCwd) === resolve12(sourceWorkspacePath);
|
|
7585
9538
|
}
|
|
7586
9539
|
}
|
|
7587
9540
|
const used = Boolean(enabled && requested && sessionId && requestedCwd && cwdMatched);
|
|
@@ -8003,13 +9956,13 @@ function sampleProcessGroupRssBytes(processGroupId) {
|
|
|
8003
9956
|
if (process.platform === "win32" || processGroupId === null) return null;
|
|
8004
9957
|
const now = Date.now();
|
|
8005
9958
|
if (!processGroupRssSampleCache || now - processGroupRssSampleCache.sampledAtMs >= PROCESS_GROUP_RSS_SAMPLE_MIN_INTERVAL_MS) {
|
|
8006
|
-
const
|
|
9959
|
+
const result3 = spawnSync6("ps", ["-axo", "pgid=,rss="], {
|
|
8007
9960
|
encoding: "utf8",
|
|
8008
9961
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8009
9962
|
});
|
|
8010
|
-
if (
|
|
9963
|
+
if (result3.status !== 0) return null;
|
|
8011
9964
|
const rssKbByProcessGroup = /* @__PURE__ */ new Map();
|
|
8012
|
-
for (const line of String(
|
|
9965
|
+
for (const line of String(result3.stdout ?? "").split(/\r?\n/)) {
|
|
8013
9966
|
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
8014
9967
|
if (!match) continue;
|
|
8015
9968
|
const pgid = Number.parseInt(match[1], 10);
|
|
@@ -8024,27 +9977,27 @@ function sampleProcessGroupRssBytes(processGroupId) {
|
|
|
8024
9977
|
}
|
|
8025
9978
|
function realOrResolvedPath(value) {
|
|
8026
9979
|
try {
|
|
8027
|
-
return
|
|
9980
|
+
return realpathSync4(value);
|
|
8028
9981
|
} catch {
|
|
8029
|
-
return
|
|
9982
|
+
return resolve12(value);
|
|
8030
9983
|
}
|
|
8031
9984
|
}
|
|
8032
|
-
var LSOF_COMMAND = process.platform === "darwin" &&
|
|
9985
|
+
var LSOF_COMMAND = process.platform === "darwin" && existsSync13("/usr/sbin/lsof") ? "/usr/sbin/lsof" : "lsof";
|
|
8033
9986
|
function processCwdForPid(pid) {
|
|
8034
9987
|
if (process.platform === "linux") {
|
|
8035
9988
|
try {
|
|
8036
|
-
return
|
|
9989
|
+
return realpathSync4(`/proc/${pid}/cwd`);
|
|
8037
9990
|
} catch {
|
|
8038
9991
|
return null;
|
|
8039
9992
|
}
|
|
8040
9993
|
}
|
|
8041
9994
|
if (process.platform === "darwin") {
|
|
8042
|
-
const
|
|
9995
|
+
const result3 = spawnSync6(LSOF_COMMAND, ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
8043
9996
|
encoding: "utf8",
|
|
8044
9997
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8045
9998
|
});
|
|
8046
|
-
if (
|
|
8047
|
-
const cwdLine = String(
|
|
9999
|
+
if (result3.status !== 0) return null;
|
|
10000
|
+
const cwdLine = String(result3.stdout ?? "").split(/\r?\n/).find((line) => line.startsWith("n"));
|
|
8048
10001
|
const cwd = cwdLine?.slice(1);
|
|
8049
10002
|
return cwd ? realOrResolvedPath(cwd) : null;
|
|
8050
10003
|
}
|
|
@@ -8069,13 +10022,13 @@ function allProcessCwdsByPid() {
|
|
|
8069
10022
|
return cwds;
|
|
8070
10023
|
}
|
|
8071
10024
|
if (process.platform === "darwin") {
|
|
8072
|
-
const
|
|
10025
|
+
const result3 = spawnSync6(LSOF_COMMAND, ["-nP", "-d", "cwd", "-Fp", "-Fn"], {
|
|
8073
10026
|
encoding: "utf8",
|
|
8074
10027
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8075
10028
|
});
|
|
8076
|
-
if (
|
|
10029
|
+
if (result3.status !== 0) return cwds;
|
|
8077
10030
|
let currentPid = null;
|
|
8078
|
-
for (const line of String(
|
|
10031
|
+
for (const line of String(result3.stdout ?? "").split(/\r?\n/)) {
|
|
8079
10032
|
if (line.startsWith("p")) {
|
|
8080
10033
|
const parsed = Number.parseInt(line.slice(1), 10);
|
|
8081
10034
|
currentPid = Number.isFinite(parsed) ? parsed : null;
|
|
@@ -8090,13 +10043,13 @@ function allProcessCwdsByPid() {
|
|
|
8090
10043
|
}
|
|
8091
10044
|
function allProcessRows() {
|
|
8092
10045
|
if (process.platform === "win32") return [];
|
|
8093
|
-
const
|
|
10046
|
+
const result3 = spawnSync6("ps", ["-axo", "pid=,pgid=,command="], {
|
|
8094
10047
|
encoding: "utf8",
|
|
8095
10048
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8096
10049
|
});
|
|
8097
|
-
if (
|
|
10050
|
+
if (result3.status !== 0) return [];
|
|
8098
10051
|
const rows = [];
|
|
8099
|
-
for (const line of String(
|
|
10052
|
+
for (const line of String(result3.stdout ?? "").split(/\r?\n/)) {
|
|
8100
10053
|
const match = line.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/);
|
|
8101
10054
|
if (!match) continue;
|
|
8102
10055
|
const pid = Number.parseInt(match[1], 10);
|
|
@@ -8151,7 +10104,7 @@ function killWorkspaceResidentProcesses(cwd, processGroupId, options = {}) {
|
|
|
8151
10104
|
}
|
|
8152
10105
|
function walkManagedWorkdirs(root) {
|
|
8153
10106
|
const workdirs = [];
|
|
8154
|
-
if (!root || !
|
|
10107
|
+
if (!root || !existsSync13(root)) return workdirs;
|
|
8155
10108
|
const stack = [root];
|
|
8156
10109
|
while (stack.length > 0) {
|
|
8157
10110
|
const current = stack.pop();
|
|
@@ -8164,8 +10117,8 @@ function walkManagedWorkdirs(root) {
|
|
|
8164
10117
|
}
|
|
8165
10118
|
for (const entry of entries) {
|
|
8166
10119
|
if (!entry.isDirectory()) continue;
|
|
8167
|
-
const fullPath =
|
|
8168
|
-
if (entry.name === "workdir" &&
|
|
10120
|
+
const fullPath = join14(current, entry.name);
|
|
10121
|
+
if (entry.name === "workdir" && existsSync13(workspaceManifestPath(fullPath))) {
|
|
8169
10122
|
workdirs.push(fullPath);
|
|
8170
10123
|
continue;
|
|
8171
10124
|
}
|
|
@@ -8203,7 +10156,7 @@ function manifestMatchesActiveRuntimeRef(manifest, refs, workdir = null) {
|
|
|
8203
10156
|
}
|
|
8204
10157
|
function buildOrphanReaperSample(root, workdir, manifest, residents) {
|
|
8205
10158
|
const relativeWorkdir = (() => {
|
|
8206
|
-
const value =
|
|
10159
|
+
const value = relative9(root, workdir);
|
|
8207
10160
|
return value && !value.startsWith("..") && !isAbsolute8(value) ? value : basename6(workdir);
|
|
8208
10161
|
})();
|
|
8209
10162
|
return {
|
|
@@ -8316,7 +10269,7 @@ function runExecutor(command, args, options) {
|
|
|
8316
10269
|
...spawnInvocation.spawnIdentity
|
|
8317
10270
|
});
|
|
8318
10271
|
const processGroupId = processGroupIdForChild(child);
|
|
8319
|
-
const finish = (
|
|
10272
|
+
const finish = (result3) => {
|
|
8320
10273
|
if (settled) return;
|
|
8321
10274
|
settled = true;
|
|
8322
10275
|
if (timer) clearTimeout(timer);
|
|
@@ -8335,7 +10288,7 @@ function runExecutor(command, args, options) {
|
|
|
8335
10288
|
completionOutputType,
|
|
8336
10289
|
killedWorkspaceResidents,
|
|
8337
10290
|
...outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
|
|
8338
|
-
...
|
|
10291
|
+
...result3
|
|
8339
10292
|
});
|
|
8340
10293
|
};
|
|
8341
10294
|
const scheduleStopKill = () => {
|
|
@@ -8676,12 +10629,12 @@ function resultOutboxActiveRunSnapshot(command) {
|
|
|
8676
10629
|
...readString(current.childExitSignal) ? { childExitSignal: readString(current.childExitSignal) } : {}
|
|
8677
10630
|
};
|
|
8678
10631
|
}
|
|
8679
|
-
async function completeCommand(config, command, status,
|
|
10632
|
+
async function completeCommand(config, command, status, result3, error) {
|
|
8680
10633
|
const connectorId = requireConnectorId(config);
|
|
8681
10634
|
const payload = {
|
|
8682
10635
|
leaseId: command.leaseId,
|
|
8683
10636
|
status,
|
|
8684
|
-
result:
|
|
10637
|
+
result: result3,
|
|
8685
10638
|
...error ? { error: truncateText(error, 4e3) } : {}
|
|
8686
10639
|
};
|
|
8687
10640
|
const path = `/api/amaster/runtime-connectors/${connectorId}/commands/${command.commandId}/result`;
|
|
@@ -8703,11 +10656,11 @@ async function completeCommand(config, command, status, result2, error) {
|
|
|
8703
10656
|
}
|
|
8704
10657
|
function resultOutboxDir(config) {
|
|
8705
10658
|
const explicit = readString(process.env.AMASTER_RESULT_OUTBOX_DIR);
|
|
8706
|
-
if (explicit) return
|
|
8707
|
-
return
|
|
10659
|
+
if (explicit) return resolve12(expandHomePath(explicit));
|
|
10660
|
+
return join14(dirname9(stateFilePath(process.env)), "result-outbox");
|
|
8708
10661
|
}
|
|
8709
10662
|
function resultOutboxInvalidDir(config) {
|
|
8710
|
-
return
|
|
10663
|
+
return join14(resultOutboxDir(config), "invalid");
|
|
8711
10664
|
}
|
|
8712
10665
|
function writeResultOutboxEntry(config, entry) {
|
|
8713
10666
|
const dir = resultOutboxDir(config);
|
|
@@ -8719,13 +10672,13 @@ function writeResultOutboxEntry(config, entry) {
|
|
|
8719
10672
|
lastAttemptAt: null,
|
|
8720
10673
|
...entry
|
|
8721
10674
|
};
|
|
8722
|
-
writeFileSync8(
|
|
10675
|
+
writeFileSync8(join14(dir, resultOutboxFileName(entry.commandId)), `${JSON.stringify(body, null, 2)}
|
|
8723
10676
|
`, { mode: 384 });
|
|
8724
10677
|
}
|
|
8725
10678
|
function moveResultOutboxEntryToInvalid(config, file, fullPath, reason, detail, original) {
|
|
8726
10679
|
const invalidDir = resultOutboxInvalidDir(config);
|
|
8727
10680
|
mkdirSync8(invalidDir, { recursive: true });
|
|
8728
|
-
const invalidPath =
|
|
10681
|
+
const invalidPath = join14(invalidDir, file);
|
|
8729
10682
|
if (original === void 0) {
|
|
8730
10683
|
try {
|
|
8731
10684
|
renameSync5(fullPath, invalidPath);
|
|
@@ -8792,7 +10745,7 @@ function quarantineResultOutboxEntry(config, fullPath, file, entry, reason, err)
|
|
|
8792
10745
|
...typeof err?.httpStatus === "number" ? { httpStatus: err.httpStatus } : {},
|
|
8793
10746
|
lastError: truncateText(err instanceof Error ? err.message : String(err), 1e3)
|
|
8794
10747
|
};
|
|
8795
|
-
const invalidPath =
|
|
10748
|
+
const invalidPath = join14(invalidDir, file);
|
|
8796
10749
|
writeFileSync8(invalidPath, `${JSON.stringify(body, null, 2)}
|
|
8797
10750
|
`, { mode: 384 });
|
|
8798
10751
|
try {
|
|
@@ -8806,11 +10759,11 @@ function quarantineResultOutboxEntry(config, fullPath, file, entry, reason, err)
|
|
|
8806
10759
|
}
|
|
8807
10760
|
async function flushResultOutbox(config) {
|
|
8808
10761
|
const dir = resultOutboxDir(config);
|
|
8809
|
-
if (!
|
|
10762
|
+
if (!existsSync13(dir)) return { attempted: 0, completed: 0 };
|
|
8810
10763
|
const files = readdirSync8(dir).filter((name) => name.endsWith(".json")).sort();
|
|
8811
10764
|
let completed = 0;
|
|
8812
10765
|
for (const file of files) {
|
|
8813
|
-
const fullPath =
|
|
10766
|
+
const fullPath = join14(dir, file);
|
|
8814
10767
|
const entry = readValidResultOutboxEntryOrQuarantine(config, file, fullPath);
|
|
8815
10768
|
if (!entry) continue;
|
|
8816
10769
|
try {
|
|
@@ -9007,7 +10960,7 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
9007
10960
|
runtimeAuth,
|
|
9008
10961
|
issueId
|
|
9009
10962
|
);
|
|
9010
|
-
const targetDir =
|
|
10963
|
+
const targetDir = join14(workspace.cwd, "input-attachments");
|
|
9011
10964
|
mkdirSync8(targetDir, { recursive: true });
|
|
9012
10965
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
9013
10966
|
const materialized = [];
|
|
@@ -9022,11 +10975,11 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
9022
10975
|
filename = `${stem}-${index + 1}${ext}`;
|
|
9023
10976
|
}
|
|
9024
10977
|
usedFilenames.add(filename);
|
|
9025
|
-
const targetPath =
|
|
10978
|
+
const targetPath = join14(targetDir, filename);
|
|
9026
10979
|
const body = await runtimeApiBuffer(runtimeAuth, contentPath);
|
|
9027
10980
|
writeFileSync8(targetPath, body);
|
|
9028
10981
|
const attachmentId = readString(attachment.id);
|
|
9029
|
-
const actualSha256 =
|
|
10982
|
+
const actualSha256 = createHash12("sha256").update(body).digest("hex");
|
|
9030
10983
|
const lineageCandidates = lineageCandidatesByAttachmentId.get(attachmentId) ?? [];
|
|
9031
10984
|
const lineage = selectAttachmentLineage(lineageCandidates, actualSha256);
|
|
9032
10985
|
if (lineageCandidates.length > 0 && !lineage) {
|
|
@@ -9049,7 +11002,7 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
9049
11002
|
id: attachmentId,
|
|
9050
11003
|
name: readString(attachment.originalFilename) ?? filename,
|
|
9051
11004
|
path: targetPath,
|
|
9052
|
-
relativePath:
|
|
11005
|
+
relativePath: relative9(workspace.cwd, targetPath),
|
|
9053
11006
|
contentType: readString(attachment.contentType),
|
|
9054
11007
|
byteSize: body.byteLength,
|
|
9055
11008
|
contentPath,
|
|
@@ -9098,7 +11051,7 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9098
11051
|
if (manifest.version !== 1 || !Array.isArray(manifest.entries)) {
|
|
9099
11052
|
throw new Error("artifact_input_manifest_invalid: expected version 1 entries array");
|
|
9100
11053
|
}
|
|
9101
|
-
const targetRoot =
|
|
11054
|
+
const targetRoot = join14(workspace.cwd, "input-artifacts");
|
|
9102
11055
|
rmSync7(targetRoot, { recursive: true, force: true });
|
|
9103
11056
|
mkdirSync8(targetRoot, { recursive: true });
|
|
9104
11057
|
const usedPaths = /* @__PURE__ */ new Set();
|
|
@@ -9107,10 +11060,10 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9107
11060
|
const entry = asRecord(rawEntry);
|
|
9108
11061
|
const workProductId = readString(entry.workProductId);
|
|
9109
11062
|
const attachmentId = readString(entry.attachmentId);
|
|
9110
|
-
const
|
|
11063
|
+
const sha2563 = readString(entry.sha256);
|
|
9111
11064
|
const contentPath = readString(entry.contentPath);
|
|
9112
11065
|
const byteSize = readNumber(entry.byteSize, null);
|
|
9113
|
-
if (!workProductId || !attachmentId || !/^[a-f0-9]{64}$/i.test(
|
|
11066
|
+
if (!workProductId || !attachmentId || !/^[a-f0-9]{64}$/i.test(sha2563 ?? "") || !contentPath || byteSize === null) {
|
|
9114
11067
|
throw new Error(`artifact_input_manifest_invalid: entry ${index} is incomplete`);
|
|
9115
11068
|
}
|
|
9116
11069
|
const expectedContentPath = `/api/attachments/${attachmentId}/content`;
|
|
@@ -9118,10 +11071,10 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9118
11071
|
throw new Error(`artifact_input_manifest_invalid: entry ${index} contentPath does not match attachmentId`);
|
|
9119
11072
|
}
|
|
9120
11073
|
const body = await runtimeApiBuffer(runtimeAuth, contentPath);
|
|
9121
|
-
const actualSha256 =
|
|
9122
|
-
if (body.byteLength !== byteSize || actualSha256 !==
|
|
11074
|
+
const actualSha256 = createHash12("sha256").update(body).digest("hex");
|
|
11075
|
+
if (body.byteLength !== byteSize || actualSha256 !== sha2563) {
|
|
9123
11076
|
throw new Error(
|
|
9124
|
-
`artifact_input_integrity_mismatch: workProductId=${workProductId} expectedBytes=${byteSize} actualBytes=${body.byteLength} expectedSha256=${
|
|
11077
|
+
`artifact_input_integrity_mismatch: workProductId=${workProductId} expectedBytes=${byteSize} actualBytes=${body.byteLength} expectedSha256=${sha2563} actualSha256=${actualSha256}`
|
|
9125
11078
|
);
|
|
9126
11079
|
}
|
|
9127
11080
|
const sourceDir = safeArtifactInputSourceDir(entry, index);
|
|
@@ -9129,15 +11082,15 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9129
11082
|
id: attachmentId,
|
|
9130
11083
|
originalFilename: readString(entry.originalFilename)
|
|
9131
11084
|
}, index);
|
|
9132
|
-
let relativePath =
|
|
11085
|
+
let relativePath = join14("input-artifacts", sourceDir, filename).split(/[\\/]+/).join("/");
|
|
9133
11086
|
if (usedPaths.has(relativePath)) {
|
|
9134
11087
|
const ext = extname2(filename);
|
|
9135
11088
|
const stem = ext ? filename.slice(0, -ext.length) : filename;
|
|
9136
11089
|
filename = `${stem}-${index + 1}${ext}`;
|
|
9137
|
-
relativePath =
|
|
11090
|
+
relativePath = join14("input-artifacts", sourceDir, filename).split(/[\\/]+/).join("/");
|
|
9138
11091
|
}
|
|
9139
11092
|
usedPaths.add(relativePath);
|
|
9140
|
-
const targetPath =
|
|
11093
|
+
const targetPath = join14(workspace.cwd, relativePath);
|
|
9141
11094
|
mkdirSync8(dirname9(targetPath), { recursive: true });
|
|
9142
11095
|
writeFileSync8(targetPath, body);
|
|
9143
11096
|
chmodSync6(targetPath, 292);
|
|
@@ -9151,7 +11104,7 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9151
11104
|
relativePath,
|
|
9152
11105
|
contentType: readString(entry.contentType),
|
|
9153
11106
|
byteSize: body.byteLength,
|
|
9154
|
-
sha256,
|
|
11107
|
+
sha256: sha2563,
|
|
9155
11108
|
contentPath
|
|
9156
11109
|
});
|
|
9157
11110
|
}
|
|
@@ -9159,7 +11112,7 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9159
11112
|
version: 1,
|
|
9160
11113
|
entries: materialized.map(({ path: _path, relativePath, ...entry }) => ({ ...entry, path: relativePath }))
|
|
9161
11114
|
};
|
|
9162
|
-
const manifestPath =
|
|
11115
|
+
const manifestPath = join14(targetRoot, "artifact-input-manifest.json");
|
|
9163
11116
|
writeFileSync8(manifestPath, JSON.stringify(localManifest, null, 2) + "\n");
|
|
9164
11117
|
chmodSync6(manifestPath, 292);
|
|
9165
11118
|
updateWorkspaceManifest(workspaceManifestPath(workspace), { artifactInputManifest: localManifest });
|
|
@@ -9170,11 +11123,11 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
9170
11123
|
return materialized;
|
|
9171
11124
|
}
|
|
9172
11125
|
function issueCheckpointDir(workspace) {
|
|
9173
|
-
return
|
|
11126
|
+
return join14(dirname9(workspace.runDir), "checkpoint");
|
|
9174
11127
|
}
|
|
9175
11128
|
async function clearIssueCheckpoint(config, command, workspace, reason) {
|
|
9176
11129
|
const checkpointDir = issueCheckpointDir(workspace);
|
|
9177
|
-
if (!
|
|
11130
|
+
if (!existsSync13(checkpointDir)) return false;
|
|
9178
11131
|
rmSync7(checkpointDir, { recursive: true, force: true });
|
|
9179
11132
|
await ingestLog(config, command, "system", "info", "Cleared issue continuation checkpoint", { reason });
|
|
9180
11133
|
return true;
|
|
@@ -9188,12 +11141,12 @@ function safeCheckpointRelativePath(rawPath) {
|
|
|
9188
11141
|
return normalized;
|
|
9189
11142
|
}
|
|
9190
11143
|
function hashFileSha256(filePath) {
|
|
9191
|
-
return
|
|
11144
|
+
return createHash12("sha256").update(readFileSync10(filePath)).digest("hex");
|
|
9192
11145
|
}
|
|
9193
11146
|
async function materializeIssueCheckpoint(config, command, workspace) {
|
|
9194
11147
|
const checkpointDir = issueCheckpointDir(workspace);
|
|
9195
|
-
const manifestPath =
|
|
9196
|
-
if (!
|
|
11148
|
+
const manifestPath = join14(checkpointDir, "manifest.json");
|
|
11149
|
+
if (!existsSync13(manifestPath)) return [];
|
|
9197
11150
|
let manifest;
|
|
9198
11151
|
try {
|
|
9199
11152
|
manifest = asRecord(JSON.parse(readFileSync10(manifestPath, "utf8")));
|
|
@@ -9209,21 +11162,21 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
9209
11162
|
try {
|
|
9210
11163
|
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
9211
11164
|
if (files.length > 20) throw new Error("issue checkpoint manifest exceeds the limit of 20 files");
|
|
9212
|
-
const filesRoot =
|
|
9213
|
-
const workspaceRoot =
|
|
11165
|
+
const filesRoot = realpathSync4(join14(checkpointDir, "files"));
|
|
11166
|
+
const workspaceRoot = realpathSync4(workspace.cwd);
|
|
9214
11167
|
const validated = [];
|
|
9215
11168
|
let totalBytes = 0;
|
|
9216
11169
|
for (const rawFile of files) {
|
|
9217
11170
|
const file = asRecord(rawFile);
|
|
9218
11171
|
const relativePath = safeCheckpointRelativePath(readString(file.path));
|
|
9219
11172
|
if (!relativePath) throw new Error("issue checkpoint manifest contains an invalid file path");
|
|
9220
|
-
const sourceCandidate =
|
|
9221
|
-
const target =
|
|
11173
|
+
const sourceCandidate = resolve12(filesRoot, relativePath);
|
|
11174
|
+
const target = resolve12(workspaceRoot, relativePath);
|
|
9222
11175
|
if (!pathWithin2(sourceCandidate, filesRoot) || !pathWithin2(target, workspaceRoot)) {
|
|
9223
11176
|
throw new Error(`issue checkpoint path escapes its workspace: ${relativePath}`);
|
|
9224
11177
|
}
|
|
9225
|
-
if (!
|
|
9226
|
-
const source =
|
|
11178
|
+
if (!existsSync13(sourceCandidate)) throw new Error(`issue checkpoint file is missing: ${relativePath}`);
|
|
11179
|
+
const source = realpathSync4(sourceCandidate);
|
|
9227
11180
|
if (!pathWithin2(source, filesRoot) || !statSync7(source).isFile()) {
|
|
9228
11181
|
throw new Error(`issue checkpoint source escapes its storage root: ${relativePath}`);
|
|
9229
11182
|
}
|
|
@@ -9248,7 +11201,7 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
9248
11201
|
if (err?.code !== "ENOENT") throw err;
|
|
9249
11202
|
}
|
|
9250
11203
|
mkdirSync8(dirname9(target), { recursive: true });
|
|
9251
|
-
const targetParent =
|
|
11204
|
+
const targetParent = realpathSync4(dirname9(target));
|
|
9252
11205
|
if (!pathWithin2(targetParent, workspaceRoot)) {
|
|
9253
11206
|
throw new Error(`issue checkpoint target escapes its workspace: ${relativePath}`);
|
|
9254
11207
|
}
|
|
@@ -9276,23 +11229,23 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
9276
11229
|
}
|
|
9277
11230
|
async function saveIssueCheckpoint(config, command, workspace, candidates) {
|
|
9278
11231
|
const checkpointDir = issueCheckpointDir(workspace);
|
|
9279
|
-
const filesDir =
|
|
11232
|
+
const filesDir = join14(checkpointDir, "files");
|
|
9280
11233
|
rmSync7(checkpointDir, { recursive: true, force: true });
|
|
9281
11234
|
mkdirSync8(filesDir, { recursive: true });
|
|
9282
11235
|
const files = [];
|
|
9283
11236
|
let totalBytes = 0;
|
|
9284
|
-
const workspaceRoot =
|
|
11237
|
+
const workspaceRoot = realpathSync4(workspace.cwd);
|
|
9285
11238
|
for (const candidate of candidates.slice(0, 20)) {
|
|
9286
11239
|
const relativePath = safeCheckpointRelativePath(candidate.rawPath);
|
|
9287
11240
|
const source = readString(candidate.filePath);
|
|
9288
|
-
if (!relativePath || !source || !
|
|
9289
|
-
const ownedSource =
|
|
11241
|
+
if (!relativePath || !source || !existsSync13(source) || !statSync7(source).isFile()) continue;
|
|
11242
|
+
const ownedSource = realpathSync4(source);
|
|
9290
11243
|
if (!pathWithin2(ownedSource, workspaceRoot)) {
|
|
9291
11244
|
throw new Error(`issue checkpoint source escapes its execution workspace: ${relativePath}`);
|
|
9292
11245
|
}
|
|
9293
11246
|
const byteSize = statSync7(ownedSource).size;
|
|
9294
11247
|
if (byteSize <= 0 || totalBytes + byteSize > MAX_CHECKPOINT_BYTES) continue;
|
|
9295
|
-
const target =
|
|
11248
|
+
const target = resolve12(filesDir, relativePath);
|
|
9296
11249
|
if (!pathWithin2(target, filesDir)) continue;
|
|
9297
11250
|
mkdirSync8(dirname9(target), { recursive: true });
|
|
9298
11251
|
copyFileSync3(ownedSource, target);
|
|
@@ -9312,7 +11265,7 @@ async function saveIssueCheckpoint(config, command, workspace, candidates) {
|
|
|
9312
11265
|
totalBytes,
|
|
9313
11266
|
files
|
|
9314
11267
|
};
|
|
9315
|
-
writeFileSync8(
|
|
11268
|
+
writeFileSync8(join14(checkpointDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
9316
11269
|
`);
|
|
9317
11270
|
await ingestLog(config, command, "system", "warn", `Saved ${files.length} required deliverable(s) for continuation recovery`, manifest);
|
|
9318
11271
|
return manifest;
|
|
@@ -9374,6 +11327,7 @@ async function executeRunCommand(config, command) {
|
|
|
9374
11327
|
await ingestWorkspaceStatus(config, command, cwd);
|
|
9375
11328
|
const promptCompilation = buildCommandPrompt(command, workspace, materializedAttachments, {
|
|
9376
11329
|
executorKind: executor.kind,
|
|
11330
|
+
managedMcpToolMode: executor.kind === "pi" && Object.keys(governedMcp).length > 0 ? MANAGED_PI_MCP_TOOL_MODE : null,
|
|
9377
11331
|
artifactVerifierCommands: config.artifactVerifierCommands
|
|
9378
11332
|
});
|
|
9379
11333
|
const nativeSessionRequest = asRecord(asRecord(command.payload).nativeSession);
|
|
@@ -9491,7 +11445,7 @@ async function executeRunCommand(config, command) {
|
|
|
9491
11445
|
executorEnv = {
|
|
9492
11446
|
...executorEnv,
|
|
9493
11447
|
AMASTER_MANAGED_RUNTIME_ASSERTION_FD: "3",
|
|
9494
|
-
AMASTER_MANAGED_RUNTIME_AUDIT_FILE:
|
|
11448
|
+
AMASTER_MANAGED_RUNTIME_AUDIT_FILE: join14(
|
|
9495
11449
|
managedMcpProfile.env.HOME,
|
|
9496
11450
|
".amaster-managed-runtime-audit.jsonl"
|
|
9497
11451
|
),
|
|
@@ -9741,12 +11695,12 @@ async function executeRunCommand(config, command) {
|
|
|
9741
11695
|
config,
|
|
9742
11696
|
command,
|
|
9743
11697
|
cwd,
|
|
9744
|
-
mcpToolResults.filter((
|
|
9745
|
-
const intentId = readString(asRecord(
|
|
11698
|
+
mcpToolResults.filter((result4) => {
|
|
11699
|
+
const intentId = readString(asRecord(result4).artifactIntent?.intentId);
|
|
9746
11700
|
return !intentId || !runtimeArtifactIngest.hasHandled(intentId);
|
|
9747
11701
|
})
|
|
9748
11702
|
));
|
|
9749
|
-
const shouldPreserveNativeSession = managedMcpProfile && parsed.sessionId && (execution.exitCode === 0 || execution.completionOutputType === "approval_required") && !execution.timedOut && execution.cancelled !== true && !execution.spawnError && ["codex", "pi"].includes(executor.kind) && mcpToolResults.some((
|
|
11703
|
+
const shouldPreserveNativeSession = managedMcpProfile && parsed.sessionId && (execution.exitCode === 0 || execution.completionOutputType === "approval_required") && !execution.timedOut && execution.cancelled !== true && !execution.spawnError && ["codex", "pi"].includes(executor.kind) && mcpToolResults.some((result4) => readString(result4.status) === "approval_required");
|
|
9750
11704
|
if (shouldPreserveNativeSession) {
|
|
9751
11705
|
try {
|
|
9752
11706
|
const preserveSessionRollout = executor.kind === "pi" ? preserveManagedPiSessionRollout : preserveManagedCodexSessionRollout;
|
|
@@ -9760,7 +11714,7 @@ async function executeRunCommand(config, command) {
|
|
|
9760
11714
|
workspace,
|
|
9761
11715
|
workspaceStatus.artifacts.map((artifact) => ({
|
|
9762
11716
|
rawPath: artifact.relativePath,
|
|
9763
|
-
filePath:
|
|
11717
|
+
filePath: resolve12(cwd, artifact.relativePath)
|
|
9764
11718
|
}))
|
|
9765
11719
|
);
|
|
9766
11720
|
nativeSessionRollout = {
|
|
@@ -9855,7 +11809,7 @@ async function executeRunCommand(config, command) {
|
|
|
9855
11809
|
status: managedMcpCleanup.status
|
|
9856
11810
|
});
|
|
9857
11811
|
}
|
|
9858
|
-
const
|
|
11812
|
+
const result3 = {
|
|
9859
11813
|
evidenceContract: { version: 1 },
|
|
9860
11814
|
executorKind: executor.kind,
|
|
9861
11815
|
command: invocation.command,
|
|
@@ -9944,7 +11898,7 @@ async function executeRunCommand(config, command) {
|
|
|
9944
11898
|
}
|
|
9945
11899
|
} : {}
|
|
9946
11900
|
};
|
|
9947
|
-
const completion = await completeCommand(config, command, cancelled ? "cancelled" : succeeded ? "succeeded" : "failed",
|
|
11901
|
+
const completion = await completeCommand(config, command, cancelled ? "cancelled" : succeeded ? "succeeded" : "failed", result3, error ?? void 0);
|
|
9948
11902
|
if (!completion) {
|
|
9949
11903
|
rememberPendingResultOutboxRunCommand(command, {
|
|
9950
11904
|
phase: "result_outbox",
|
|
@@ -9990,6 +11944,52 @@ async function processCommand(config, command) {
|
|
|
9990
11944
|
await executeTrackedModelCallCommand(config, command);
|
|
9991
11945
|
return;
|
|
9992
11946
|
}
|
|
11947
|
+
if (command.commandType === "source_read") {
|
|
11948
|
+
const sourceReadStatus = browserCapabilityStatus(config, "constrained_source_read");
|
|
11949
|
+
const broker = sourceReadStatus.ready ? runtimeBrowserSessionBroker(config) : null;
|
|
11950
|
+
await ackCommand(config, command, "spawned");
|
|
11951
|
+
const result3 = await executeSourceReadCommand(command, {
|
|
11952
|
+
resolveBinding: broker ? async (input) => input.authentication ? broker.resolveAuthenticatedBinding({
|
|
11953
|
+
companyId: input.companyId,
|
|
11954
|
+
locator: input.locator,
|
|
11955
|
+
declaredPageLocators: input.declaredPageLocators,
|
|
11956
|
+
allowedResourceOrigins: input.allowedResourceOrigins,
|
|
11957
|
+
limits: input.limits,
|
|
11958
|
+
signal: input.signal,
|
|
11959
|
+
...input.authentication
|
|
11960
|
+
}) : broker.resolvePublicBinding({
|
|
11961
|
+
locator: input.locator,
|
|
11962
|
+
declaredPageLocators: input.declaredPageLocators,
|
|
11963
|
+
allowedResourceOrigins: input.allowedResourceOrigins,
|
|
11964
|
+
limits: input.limits
|
|
11965
|
+
}) : unavailableSourceReadBindingResolver
|
|
11966
|
+
});
|
|
11967
|
+
const failed = result3.status === "failed";
|
|
11968
|
+
await completeCommand(
|
|
11969
|
+
config,
|
|
11970
|
+
command,
|
|
11971
|
+
failed ? "failed" : "succeeded",
|
|
11972
|
+
result3,
|
|
11973
|
+
failed ? result3.failureCode : void 0
|
|
11974
|
+
);
|
|
11975
|
+
return;
|
|
11976
|
+
}
|
|
11977
|
+
if (command.commandType === "browser_session") {
|
|
11978
|
+
const status = browserCapabilityStatus(config, "headed_browser_auth");
|
|
11979
|
+
await ackCommand(config, command, "spawned");
|
|
11980
|
+
const result3 = await executeBrowserSessionCommand(command, {
|
|
11981
|
+
broker: status.ready ? runtimeBrowserSessionBroker(config) : null
|
|
11982
|
+
});
|
|
11983
|
+
const failed = result3.outcome === "failed";
|
|
11984
|
+
await completeCommand(
|
|
11985
|
+
config,
|
|
11986
|
+
command,
|
|
11987
|
+
failed ? "failed" : "succeeded",
|
|
11988
|
+
result3,
|
|
11989
|
+
failed ? result3.failureCode : void 0
|
|
11990
|
+
);
|
|
11991
|
+
return;
|
|
11992
|
+
}
|
|
9993
11993
|
await completeCommand(config, command, "succeeded", {
|
|
9994
11994
|
summary: `Command ${command.commandType} acknowledged by amaster-runtime-daemon.`,
|
|
9995
11995
|
commandType: command.commandType
|
|
@@ -10026,12 +12026,12 @@ function reconcileStaleManagedMcpProfiles(config) {
|
|
|
10026
12026
|
["Codex", reconcileManagedCodexMcpProfiles],
|
|
10027
12027
|
["Pi", reconcileManagedPiMcpProfiles]
|
|
10028
12028
|
]) {
|
|
10029
|
-
const
|
|
10030
|
-
if (
|
|
10031
|
-
throw new Error(`${executorKind.toLowerCase()}_managed_mcp_restart_cleanup_failed: ${
|
|
12029
|
+
const result3 = reconcile(config.runtimeWorkspacesRoot);
|
|
12030
|
+
if (result3.failed > 0) {
|
|
12031
|
+
throw new Error(`${executorKind.toLowerCase()}_managed_mcp_restart_cleanup_failed: ${result3.failed}/${result3.scanned} owned profile(s) could not be reconciled`);
|
|
10032
12032
|
}
|
|
10033
|
-
if (
|
|
10034
|
-
process.stderr.write(`AMaster daemon removed ${
|
|
12033
|
+
if (result3.removed > 0) {
|
|
12034
|
+
process.stderr.write(`AMaster daemon removed ${result3.removed} stale marker-owned ${executorKind} managed MCP artifact(s) during startup reconciliation.
|
|
10035
12035
|
`);
|
|
10036
12036
|
}
|
|
10037
12037
|
}
|
|
@@ -10118,7 +12118,7 @@ async function ack(config, flags) {
|
|
|
10118
12118
|
process.stdout.write(`${JSON.stringify(body, null, 2)}
|
|
10119
12119
|
`);
|
|
10120
12120
|
}
|
|
10121
|
-
async function
|
|
12121
|
+
async function result2(config, flags) {
|
|
10122
12122
|
const connectorId = requireConnectorId(config);
|
|
10123
12123
|
const commandId = flags.commandId ?? process.env.AMASTER_COMMAND_ID;
|
|
10124
12124
|
if (!commandId) throw new Error("--command-id or AMASTER_COMMAND_ID is required");
|
|
@@ -10174,7 +12174,7 @@ async function runLoop(config) {
|
|
|
10174
12174
|
${message}
|
|
10175
12175
|
`);
|
|
10176
12176
|
}
|
|
10177
|
-
await new Promise((
|
|
12177
|
+
await new Promise((resolve13) => setTimeout(resolve13, config.pollIntervalSeconds * 1e3));
|
|
10178
12178
|
}
|
|
10179
12179
|
}
|
|
10180
12180
|
function help() {
|
|
@@ -10234,7 +12234,7 @@ async function main() {
|
|
|
10234
12234
|
await ack(config, flags);
|
|
10235
12235
|
break;
|
|
10236
12236
|
case "result":
|
|
10237
|
-
await
|
|
12237
|
+
await result2(config, flags);
|
|
10238
12238
|
break;
|
|
10239
12239
|
case "run-once":
|
|
10240
12240
|
await runOnce(config);
|