@amaster.ai/employee-runtime-connector 0.1.0-beta.20 → 0.1.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/amaster-runtime-daemon.mjs +322 -78
- package/dist/amaster-runtime.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,14 @@ The source of truth lives under this package's `src/` directory. The package bui
|
|
|
32
32
|
|
|
33
33
|
Runtime code belongs in the container image. Persist only connector state, the result outbox, and workspaces under the configured state directory.
|
|
34
34
|
|
|
35
|
+
## Mutation attestation
|
|
36
|
+
|
|
37
|
+
The daemon reports its connector contract, exact package/build, platform/architecture, and discovered executor versions on every heartbeat. The server compares those facts with `AMASTER_RUNTIME_RECOMMENDED_VERSION` and `AMASTER_RUNTIME_RECOMMENDED_BUILD_COMMIT`, persists a short-lived content-bound attestation, and correlates Runtime V2 commands to that proof.
|
|
38
|
+
|
|
39
|
+
`AMASTER_RUNTIME_ATTESTATION_MODE` defaults to `shadow`. Set it to `enforce` only after the expected version and build marker are configured and shadow diagnostics are clean; enforce mode refuses to create or lease Runtime V2 mutation commands without a current exact attestation. `AMASTER_RUNTIME_ATTESTATION_TTL_SECONDS` defaults to `300` and accepts `60`–`3600`. Invalid mode or TTL values fail server startup visibly.
|
|
40
|
+
|
|
41
|
+
Threat model: these facts are self-reported by a connector authenticated with its existing connector credential. There is no hardware-backed signature, remote attestation, or independent trust root. The proof detects accidental version/schema/executor drift by an honest connector and correlates commands to the observed facts; it does not prove that a compromised or malicious connector is running the claimed code. Keep the default `shadow` mode observational. Do not treat this mechanism as a production security boundary or enable `enforce` until credential-epoch identity, transactional create/lease revalidation, batch-local rejection, and recovery from honest drift have passed review.
|
|
42
|
+
|
|
35
43
|
The AMaster remote stack derives its runtime image from the Pi base image with
|
|
36
44
|
`docker/Dockerfile.amaster-employee-pi-cli-runtime`. The build installs one exact
|
|
37
45
|
connector package version under `/opt/pi-cli-runtime` and sets
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// AMaster Employee runtime connector daemon bundle.
|
|
3
3
|
|
|
4
4
|
// src/amaster-runtime-daemon.mjs
|
|
5
|
-
import { createHash as
|
|
6
|
-
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync9, lstatSync as
|
|
5
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
6
|
+
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync9, lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync6, realpathSync as realpathSync3, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync7, symlinkSync as symlinkSync2, unlinkSync, writeFileSync as writeFileSync5 } 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 dirname6, extname as extname2, isAbsolute as
|
|
8
|
+
import { basename as basename6, delimiter as delimiter2, dirname as dirname6, extname as extname2, isAbsolute as isAbsolute6, join as join10, relative as relative6, resolve as resolve8 } from "node:path";
|
|
9
9
|
import { spawn, spawnSync as spawnSync5 } from "node:child_process";
|
|
10
10
|
|
|
11
11
|
// src/amaster-runtime-daemon/common.mjs
|
|
@@ -2367,6 +2367,8 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2367
2367
|
const governedReads = governedReadSection(context);
|
|
2368
2368
|
const hasTask = Boolean(readString(input.taskMarkdown));
|
|
2369
2369
|
const taskText = readString(input.taskMarkdown) ?? "";
|
|
2370
|
+
const continuationSummary = continuationText(context);
|
|
2371
|
+
const includeTask = mode === "cold" || !continuationSummary && asRecord(input.nativeSession).mode !== "governed_action_approval";
|
|
2370
2372
|
const wakeBodies = commentBodies(context);
|
|
2371
2373
|
const commentsDuplicatedByTask = hasTask && wakeBodies.length > 0 && wakeBodies.every((body) => taskText.includes(body));
|
|
2372
2374
|
const commentsSelected = mode !== "cold" || !commentsDuplicatedByTask;
|
|
@@ -2378,9 +2380,9 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2378
2380
|
const rawSections = [
|
|
2379
2381
|
{ name: "runtime_rules", title: "", priority: 100, sourceRef: `command:${input.commandId}`, content: fixedRules(input, !hasTask) },
|
|
2380
2382
|
{ name: "approval_continuation", title: "Approved Runtime Action Continuation", priority: 99, sourceRef: `run:${input.runId ?? "unknown"}`, content: approvalContinuationText(input), truncationReason: mode === "continuation" ? null : "mode_selection" },
|
|
2381
|
-
{ 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:
|
|
2383
|
+
{ 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 },
|
|
2382
2384
|
{ name: "wake_comments", title: "Wake Comment Delta", priority: 95, sourceRef: commentRefs(context).map((id) => `comment:${id}`), originalContent: readString(input.comments) ?? "", content: commentsSelected ? readString(input.comments) ?? "" : "", truncationReason: commentsSelected ? null : "duplicate_task_context" },
|
|
2383
|
-
{ name: "task", title: "Task Context", priority: 90, sourceRef: `issue:${input.issueId ?? "unknown"}`, originalContent: taskText, content:
|
|
2385
|
+
{ name: "task", title: "Task Context", priority: 90, sourceRef: `issue:${input.issueId ?? "unknown"}`, originalContent: taskText, content: includeTask ? taskText : "", truncationReason: includeTask ? null : "mode_selection" },
|
|
2384
2386
|
{ name: "governed_reads", title: "Governed External Reads", priority: 88, sourceRef: governedReads.provenance.map((entry) => entry.sourceRef), observedAt: governedReads.provenance.map((entry) => entry.observedAt), freshness: governedReads.provenance.map((entry) => entry.freshness), scope: governedReads.provenance.map((entry) => entry.scope), content: governedReads.content },
|
|
2385
2387
|
{ name: "agent_instructions", title: "Agent Instructions", priority: 85, sourceRef: "agent_instructions_bundle", content: readString(input.agentInstructions) ?? "" },
|
|
2386
2388
|
{ name: "attachments", title: "Materialized Inputs", priority: 80, sourceRef: "materialized_attachments", content: readString(input.attachmentsText) ?? "" },
|
|
@@ -3151,6 +3153,13 @@ function shouldPreserveExecutorJsonlForTranscript(executorKind, event) {
|
|
|
3151
3153
|
var CODEX_TRANSIENT_UPSTREAM_RE = /(?:we(?:'|’)re\s+currently\s+experiencing\s+high\s+demand|temporary\s+errors|rate[-\s]?limit(?:ed)?|too\s+many\s+requests|\b429\b|server\s+overloaded|service\s+unavailable|try\s+again\s+later)/i;
|
|
3152
3154
|
var CODEX_REMOTE_COMPACTION_RE = /remote\s+compact\s+task/i;
|
|
3153
3155
|
var CODEX_USAGE_LIMIT_RE = /you(?:'|’)ve hit your usage limit for .+\.\s+switch to another model now,\s+or try again at\s+([^.!\n]+)(?:[.!]|\n|$)/i;
|
|
3156
|
+
function approvedMcpInvocationSucceeded(results, invocationId) {
|
|
3157
|
+
const approvedInvocationId = readString(invocationId);
|
|
3158
|
+
return Boolean(approvedInvocationId && (Array.isArray(results) ? results : []).some((rawResult) => {
|
|
3159
|
+
const result2 = asRecord(rawResult);
|
|
3160
|
+
return readString(result2.invocationId) === approvedInvocationId && readString(result2.status) === "succeeded" && !["rejected", "blocked"].includes(readString(result2.providerStatus) ?? "");
|
|
3161
|
+
}));
|
|
3162
|
+
}
|
|
3154
3163
|
function parseCodexJsonl(stdout) {
|
|
3155
3164
|
let sessionId = null;
|
|
3156
3165
|
let summary = "";
|
|
@@ -3171,7 +3180,19 @@ function parseCodexJsonl(stdout) {
|
|
|
3171
3180
|
summary = readString(item.text) ?? summary;
|
|
3172
3181
|
}
|
|
3173
3182
|
if (item.type === "mcp_tool_call") {
|
|
3174
|
-
const
|
|
3183
|
+
const result2 = asRecord(item.result);
|
|
3184
|
+
let structuredContent = asRecord(result2.structuredContent);
|
|
3185
|
+
if (Object.keys(structuredContent).length === 0 && Array.isArray(result2.content)) {
|
|
3186
|
+
for (const part of result2.content) {
|
|
3187
|
+
const text = readString(asRecord(part).text);
|
|
3188
|
+
if (!text) continue;
|
|
3189
|
+
try {
|
|
3190
|
+
structuredContent = asRecord(JSON.parse(text));
|
|
3191
|
+
} catch {
|
|
3192
|
+
}
|
|
3193
|
+
if (Object.keys(structuredContent).length > 0) break;
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3175
3196
|
const status = readString(structuredContent.status);
|
|
3176
3197
|
if (status) {
|
|
3177
3198
|
const invocationId = readString(structuredContent.invocationId);
|
|
@@ -3536,7 +3557,21 @@ function piMcpToolResults(event) {
|
|
|
3536
3557
|
const status = readString(structuredContent.status);
|
|
3537
3558
|
if (!status) continue;
|
|
3538
3559
|
const invocationId = readString(structuredContent.invocationId);
|
|
3539
|
-
|
|
3560
|
+
const providerContent = asRecord(structuredContent.content);
|
|
3561
|
+
const providerStatus = readString(providerContent.status);
|
|
3562
|
+
const effectResult = asRecord(asRecord(providerContent.result).effectResult);
|
|
3563
|
+
const intentId = readString(effectResult.artifactIntentId);
|
|
3564
|
+
const manifestId = readString(effectResult.manifestId);
|
|
3565
|
+
const sourceRelativePath = readString(effectResult.sourceRelativePath);
|
|
3566
|
+
const sha256 = readString(effectResult.sha256);
|
|
3567
|
+
const byteSize = readNumber(effectResult.byteSize, 0);
|
|
3568
|
+
const artifactIntent = providerContent.status === "pending_reconcile" && intentId && manifestId && sourceRelativePath && /^[a-f0-9]{64}$/.test(sha256 ?? "") && Number.isSafeInteger(byteSize) && byteSize > 0 ? { intentId, manifestId, sourceRelativePath, sha256, byteSize } : null;
|
|
3569
|
+
results.push({
|
|
3570
|
+
...invocationId ? { invocationId } : {},
|
|
3571
|
+
status,
|
|
3572
|
+
...providerStatus ? { providerStatus } : {},
|
|
3573
|
+
...artifactIntent ? { artifactIntent } : {}
|
|
3574
|
+
});
|
|
3540
3575
|
}
|
|
3541
3576
|
return results;
|
|
3542
3577
|
}
|
|
@@ -3705,6 +3740,26 @@ async function postRuntimeConnectorJson(config, path, payload) {
|
|
|
3705
3740
|
}
|
|
3706
3741
|
return body;
|
|
3707
3742
|
}
|
|
3743
|
+
async function postRuntimeConnectorBytes(config, path, body, headers = {}) {
|
|
3744
|
+
if (!Buffer.isBuffer(body)) throw new TypeError("Runtime connector byte upload body must be a Buffer");
|
|
3745
|
+
const res = await fetch(`${config.serverUrl}${path}`, {
|
|
3746
|
+
method: "POST",
|
|
3747
|
+
headers: {
|
|
3748
|
+
...headers,
|
|
3749
|
+
"content-type": "application/octet-stream",
|
|
3750
|
+
...buildRuntimeConnectorAuthHeaders(config)
|
|
3751
|
+
},
|
|
3752
|
+
body
|
|
3753
|
+
});
|
|
3754
|
+
const text = await res.text();
|
|
3755
|
+
const response = text ? JSON.parse(text) : null;
|
|
3756
|
+
if (!res.ok) {
|
|
3757
|
+
const error = new Error(`POST ${path} returned HTTP ${res.status}: ${text}`);
|
|
3758
|
+
error.httpStatus = res.status;
|
|
3759
|
+
throw error;
|
|
3760
|
+
}
|
|
3761
|
+
return response;
|
|
3762
|
+
}
|
|
3708
3763
|
async function bestEffortPostRuntimeConnectorJson(config, path, payload, options = {}) {
|
|
3709
3764
|
try {
|
|
3710
3765
|
return await postRuntimeConnectorJson(config, path, payload);
|
|
@@ -3722,13 +3777,73 @@ async function bestEffortPostRuntimeConnectorJson(config, path, payload, options
|
|
|
3722
3777
|
var postJson = postRuntimeConnectorJson;
|
|
3723
3778
|
var bestEffortPostJson = bestEffortPostRuntimeConnectorJson;
|
|
3724
3779
|
|
|
3725
|
-
// src/amaster-runtime-daemon/
|
|
3780
|
+
// src/amaster-runtime-daemon/runtime-artifact-upload.mjs
|
|
3726
3781
|
import { createHash as createHash3 } from "node:crypto";
|
|
3727
|
-
import {
|
|
3728
|
-
import {
|
|
3782
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync } from "node:fs";
|
|
3783
|
+
import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
|
|
3784
|
+
var SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
3785
|
+
function requiredString(value, name) {
|
|
3786
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`Runtime Artifact ${name} is required`);
|
|
3787
|
+
return value.trim();
|
|
3788
|
+
}
|
|
3789
|
+
function ownedRelativePath(value) {
|
|
3790
|
+
const normalized = requiredString(value, "sourceRelativePath").replaceAll("\\", "/");
|
|
3791
|
+
if (normalized.startsWith("/") || /^[a-z]:\//i.test(normalized) || normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) throw new Error(`Runtime Artifact source path is not owned: ${normalized}`);
|
|
3792
|
+
return normalized;
|
|
3793
|
+
}
|
|
3794
|
+
function pathWithin(candidate, root) {
|
|
3795
|
+
const rel = relative3(root, candidate);
|
|
3796
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
3797
|
+
}
|
|
3798
|
+
function prepareRuntimeArtifactUploads(cwd, mcpToolResults) {
|
|
3799
|
+
const root = realpathSync(resolve3(cwd));
|
|
3800
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
3801
|
+
for (const result2 of Array.isArray(mcpToolResults) ? mcpToolResults : []) {
|
|
3802
|
+
const intent = result2 && typeof result2 === "object" && !Array.isArray(result2) ? result2.artifactIntent : null;
|
|
3803
|
+
if (!intent || typeof intent !== "object" || Array.isArray(intent)) continue;
|
|
3804
|
+
const intentId = requiredString(intent.intentId, "intentId");
|
|
3805
|
+
const manifestId = requiredString(intent.manifestId, "manifestId");
|
|
3806
|
+
const sourceRelativePath = ownedRelativePath(intent.sourceRelativePath);
|
|
3807
|
+
const expectedSha256 = requiredString(intent.sha256, "sha256");
|
|
3808
|
+
const expectedByteSize = intent.byteSize;
|
|
3809
|
+
if (!SHA256_PATTERN.test(expectedSha256)) throw new Error(`Runtime Artifact ${intentId} has an invalid SHA-256`);
|
|
3810
|
+
if (!Number.isSafeInteger(expectedByteSize) || expectedByteSize <= 0) {
|
|
3811
|
+
throw new Error(`Runtime Artifact ${intentId} has an invalid byte size`);
|
|
3812
|
+
}
|
|
3813
|
+
const existing = uploads.get(intentId);
|
|
3814
|
+
if (existing) {
|
|
3815
|
+
if (existing.manifestId !== manifestId || existing.sourceRelativePath !== sourceRelativePath || existing.sha256 !== expectedSha256 || existing.byteSize !== expectedByteSize) throw new Error(`Runtime Artifact ${intentId} was emitted with conflicting ownership metadata`);
|
|
3816
|
+
continue;
|
|
3817
|
+
}
|
|
3818
|
+
const sourcePath = resolve3(root, sourceRelativePath);
|
|
3819
|
+
const stat = lstatSync3(sourcePath);
|
|
3820
|
+
if (!stat.isFile() || stat.isSymbolicLink() || !pathWithin(realpathSync(sourcePath), root)) {
|
|
3821
|
+
throw new Error(`Runtime Artifact ${intentId} source is not an owned regular file`);
|
|
3822
|
+
}
|
|
3823
|
+
const body = readFileSync4(sourcePath);
|
|
3824
|
+
const actualSha256 = createHash3("sha256").update(body).digest("hex");
|
|
3825
|
+
if (body.length !== expectedByteSize || actualSha256 !== expectedSha256) {
|
|
3826
|
+
throw new Error(`Runtime Artifact ${intentId} bytes do not match the governed ownership manifest`);
|
|
3827
|
+
}
|
|
3828
|
+
uploads.set(intentId, {
|
|
3829
|
+
intentId,
|
|
3830
|
+
manifestId,
|
|
3831
|
+
sourceRelativePath,
|
|
3832
|
+
sha256: expectedSha256,
|
|
3833
|
+
byteSize: expectedByteSize,
|
|
3834
|
+
body
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
return [...uploads.values()];
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
// src/amaster-runtime-daemon/workspace-guard.mjs
|
|
3841
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3842
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
|
|
3843
|
+
import { basename as basename4, join as join6, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4 } from "node:path";
|
|
3729
3844
|
|
|
3730
3845
|
// src/amaster-runtime-daemon/workspace-manifest.mjs
|
|
3731
|
-
import { existsSync as existsSync4, readFileSync as
|
|
3846
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3732
3847
|
import { basename as basename3, dirname as dirname4, join as join5 } from "node:path";
|
|
3733
3848
|
var WORKSPACE_MANIFEST_FILENAME = ".amaster-runtime.json";
|
|
3734
3849
|
function nowIso() {
|
|
@@ -3743,7 +3858,7 @@ function workspaceManifestPath(workspaceOrCwd) {
|
|
|
3743
3858
|
function readWorkspaceManifest(manifestPath) {
|
|
3744
3859
|
if (!manifestPath || !existsSync4(manifestPath)) return null;
|
|
3745
3860
|
try {
|
|
3746
|
-
const parsed = JSON.parse(
|
|
3861
|
+
const parsed = JSON.parse(readFileSync5(manifestPath, "utf8"));
|
|
3747
3862
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
3748
3863
|
} catch {
|
|
3749
3864
|
return null;
|
|
@@ -3807,23 +3922,23 @@ function updateWorkspaceManifest(workspaceOrManifestPath, patch = {}) {
|
|
|
3807
3922
|
}
|
|
3808
3923
|
|
|
3809
3924
|
// src/amaster-runtime-daemon/workspace-guard.mjs
|
|
3810
|
-
function
|
|
3811
|
-
const rel =
|
|
3812
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
3925
|
+
function pathWithin2(candidate, root) {
|
|
3926
|
+
const rel = relative4(root, candidate);
|
|
3927
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
|
|
3813
3928
|
}
|
|
3814
3929
|
function resolveWorkspaceCwd(config, command) {
|
|
3815
3930
|
const payload = asRecord(command.payload);
|
|
3816
3931
|
const requested = readString(payload.workspacePath);
|
|
3817
3932
|
const fallback = config.workspaceBindings[0] ?? process.cwd();
|
|
3818
|
-
const cwd =
|
|
3933
|
+
const cwd = realpathSync2(resolve4(expandHomePath(requested ?? fallback)));
|
|
3819
3934
|
const allowlist = config.workspaceBindings.flatMap((entry) => {
|
|
3820
3935
|
try {
|
|
3821
|
-
return [
|
|
3936
|
+
return [realpathSync2(resolve4(expandHomePath(entry)))];
|
|
3822
3937
|
} catch {
|
|
3823
3938
|
return [];
|
|
3824
3939
|
}
|
|
3825
3940
|
});
|
|
3826
|
-
const allowed = allowlist.some((entry) =>
|
|
3941
|
+
const allowed = allowlist.some((entry) => pathWithin2(cwd, entry));
|
|
3827
3942
|
if (!allowed) {
|
|
3828
3943
|
throw new Error(`Workspace path is outside AMASTER_WORKSPACE_ALLOWLIST: ${cwd}`);
|
|
3829
3944
|
}
|
|
@@ -3833,7 +3948,7 @@ function resolveWorkspaceCwd(config, command) {
|
|
|
3833
3948
|
return cwd;
|
|
3834
3949
|
}
|
|
3835
3950
|
function shortHash(value, length = 12) {
|
|
3836
|
-
return
|
|
3951
|
+
return createHash4("sha256").update(String(value)).digest("hex").slice(0, length);
|
|
3837
3952
|
}
|
|
3838
3953
|
function safeSegment(value, fallback) {
|
|
3839
3954
|
const raw = String(value ?? "").trim();
|
|
@@ -3857,9 +3972,9 @@ function workspaceLabel(sourceWorkspacePath, payload) {
|
|
|
3857
3972
|
return readString(payload.workspaceName) ?? readString(payload.projectName) ?? readString(context.projectName) ?? basename4(sourceWorkspacePath) ?? "workspace";
|
|
3858
3973
|
}
|
|
3859
3974
|
function workspacesRoot(config) {
|
|
3860
|
-
const root =
|
|
3975
|
+
const root = resolve4(expandHomePath(config.runtimeWorkspacesRoot ?? "~/.amaster-employee/workspaces"));
|
|
3861
3976
|
mkdirSync4(root, { recursive: true });
|
|
3862
|
-
return
|
|
3977
|
+
return realpathSync2(root);
|
|
3863
3978
|
}
|
|
3864
3979
|
function resolveExecutionWorkspace(config, command, opts = {}) {
|
|
3865
3980
|
const sourceWorkspacePath = resolveWorkspaceCwd(config, command);
|
|
@@ -3897,7 +4012,7 @@ function resolveExecutionWorkspace(config, command, opts = {}) {
|
|
|
3897
4012
|
|
|
3898
4013
|
// src/amaster-runtime-daemon/workspace-gc.mjs
|
|
3899
4014
|
import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
3900
|
-
import { join as join7, resolve as
|
|
4015
|
+
import { join as join7, resolve as resolve5 } from "node:path";
|
|
3901
4016
|
function readIsoTime(value) {
|
|
3902
4017
|
if (typeof value !== "string" || !value.trim()) return null;
|
|
3903
4018
|
const time = new Date(value).getTime();
|
|
@@ -3979,7 +4094,7 @@ function summarizeWorkdir(path, manifest, nowMs) {
|
|
|
3979
4094
|
};
|
|
3980
4095
|
}
|
|
3981
4096
|
function planManagedWorkspaceGcDryRun(input) {
|
|
3982
|
-
const root =
|
|
4097
|
+
const root = resolve5(String(input.root ?? ""));
|
|
3983
4098
|
const now = input.now instanceof Date ? input.now : /* @__PURE__ */ new Date();
|
|
3984
4099
|
const nowMs = now.getTime();
|
|
3985
4100
|
const ttlHours = Math.max(1, Number(input.ttlHours ?? 72));
|
|
@@ -4025,7 +4140,7 @@ function planManagedWorkspaceGcDryRun(input) {
|
|
|
4025
4140
|
|
|
4026
4141
|
// src/amaster-runtime-daemon/runtime-status-summary.mjs
|
|
4027
4142
|
import { existsSync as existsSync7, readdirSync as readdirSync4, statSync as statSync5 } from "node:fs";
|
|
4028
|
-
import { dirname as dirname5, join as join8, resolve as
|
|
4143
|
+
import { dirname as dirname5, join as join8, resolve as resolve6 } from "node:path";
|
|
4029
4144
|
function runtimeStatusDirectoryEntries(path) {
|
|
4030
4145
|
try {
|
|
4031
4146
|
return readdirSync4(path, { withFileTypes: true });
|
|
@@ -4117,7 +4232,7 @@ function countRuntimeStatusJsonEntries(dir) {
|
|
|
4117
4232
|
}
|
|
4118
4233
|
function summarizeRuntimeLocalState(input) {
|
|
4119
4234
|
const config = input.config ?? {};
|
|
4120
|
-
const root =
|
|
4235
|
+
const root = resolve6(String(config.AMASTER_RUNTIME_WORKSPACES_ROOT ?? config.runtimeWorkspacesRoot ?? input.runtimeWorkspacesRoot ?? ""));
|
|
4121
4236
|
const now = input.now instanceof Date ? input.now : /* @__PURE__ */ new Date();
|
|
4122
4237
|
const workdirs = walkRuntimeStatusManagedWorkdirs(root);
|
|
4123
4238
|
let managedWorkdirBytes = 0;
|
|
@@ -4193,9 +4308,9 @@ function summarizeAmasterRuntimeVersionDrift(input = {}) {
|
|
|
4193
4308
|
|
|
4194
4309
|
// src/amaster-runtime-daemon/workspace-status.mjs
|
|
4195
4310
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
4196
|
-
import { createHash as
|
|
4197
|
-
import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as
|
|
4198
|
-
import { basename as basename5, extname, isAbsolute as
|
|
4311
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
4312
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
|
|
4313
|
+
import { basename as basename5, extname, isAbsolute as isAbsolute5, join as join9, relative as relative5, resolve as resolve7 } from "node:path";
|
|
4199
4314
|
var WORKSPACE_RUNTIME_SERVICES_FILENAME = ".amaster-runtime-services.json";
|
|
4200
4315
|
var ARTIFACT_EXTENSIONS = /* @__PURE__ */ new Map([
|
|
4201
4316
|
[".md", "markdown"],
|
|
@@ -4226,9 +4341,12 @@ var SKIP_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
4226
4341
|
"build",
|
|
4227
4342
|
"coverage",
|
|
4228
4343
|
".turbo",
|
|
4229
|
-
".cache"
|
|
4344
|
+
".cache",
|
|
4345
|
+
".venv",
|
|
4346
|
+
"venv"
|
|
4230
4347
|
]);
|
|
4231
4348
|
var SECRET_PATH_PATTERN = /(^|\/)(\.env($|[._-])|.*\.(pem|key|p12|pfx)$|.*(secret|token|credential|password|authorization|cookie).*)/i;
|
|
4349
|
+
var RUNTIME_INSTRUCTION_FILENAMES = /* @__PURE__ */ new Set(["AGENTS.md", "SOUL.md"]);
|
|
4232
4350
|
var MAX_CANDIDATES = 200;
|
|
4233
4351
|
var MAX_SCAN_ENTRIES = 5e3;
|
|
4234
4352
|
var MAX_HASH_BYTES = 50 * 1024 * 1024;
|
|
@@ -4237,16 +4355,17 @@ var SAFE_RUNTIME_SERVICE_STATUSES = /* @__PURE__ */ new Set(["starting", "runnin
|
|
|
4237
4355
|
var SAFE_RUNTIME_SERVICE_HEALTH_STATUSES = /* @__PURE__ */ new Set(["unknown", "healthy", "unhealthy"]);
|
|
4238
4356
|
var SAFE_RUNTIME_SERVICE_LIFECYCLES = /* @__PURE__ */ new Set(["shared", "ephemeral"]);
|
|
4239
4357
|
function statusPathWithin(candidate, root) {
|
|
4240
|
-
const rel =
|
|
4241
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
4358
|
+
const rel = relative5(root, candidate);
|
|
4359
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
|
|
4242
4360
|
}
|
|
4243
4361
|
function normalizeRelativePath(root, filePath) {
|
|
4244
|
-
return
|
|
4362
|
+
return relative5(root, filePath).split(/[\\/]+/).join("/");
|
|
4245
4363
|
}
|
|
4246
4364
|
function isSafeRelativePath(value) {
|
|
4247
4365
|
const text = String(value ?? "").trim().split(/[\\/]+/).join("/");
|
|
4248
4366
|
if (!text || text.startsWith("../") || text === ".." || text.startsWith("/")) return false;
|
|
4249
|
-
|
|
4367
|
+
const segments = text.split("/").filter(Boolean);
|
|
4368
|
+
return !SECRET_PATH_PATTERN.test(text) && !RUNTIME_INSTRUCTION_FILENAMES.has(basename5(text)) && !segments.some((segment) => segment === ".venv" || segment === "venv");
|
|
4250
4369
|
}
|
|
4251
4370
|
function gitStatusPath(line) {
|
|
4252
4371
|
if (line.startsWith("?? ")) return line.slice(3);
|
|
@@ -4260,7 +4379,7 @@ function sanitizeTrackedChange(line) {
|
|
|
4260
4379
|
return isSafeRelativePath(path) ? line : null;
|
|
4261
4380
|
}
|
|
4262
4381
|
function sha256File(filePath) {
|
|
4263
|
-
return
|
|
4382
|
+
return createHash5("sha256").update(readFileSync6(filePath)).digest("hex");
|
|
4264
4383
|
}
|
|
4265
4384
|
function artifactHashCacheKey(relativePath, stat) {
|
|
4266
4385
|
return `${relativePath}\0${stat.size}\0${stat.mtimeMs}`;
|
|
@@ -4288,7 +4407,7 @@ function artifactSha256(filePath, relativePath, stat, opts = {}) {
|
|
|
4288
4407
|
return hash;
|
|
4289
4408
|
}
|
|
4290
4409
|
function scanArtifactCandidates(cwd, opts = {}) {
|
|
4291
|
-
const root =
|
|
4410
|
+
const root = resolve7(cwd);
|
|
4292
4411
|
const maxCandidates = opts.maxCandidates ?? MAX_CANDIDATES;
|
|
4293
4412
|
const maxEntries = opts.maxEntries ?? MAX_SCAN_ENTRIES;
|
|
4294
4413
|
const candidates = [];
|
|
@@ -4328,7 +4447,7 @@ function scanArtifactCandidates(cwd, opts = {}) {
|
|
|
4328
4447
|
} catch {
|
|
4329
4448
|
continue;
|
|
4330
4449
|
}
|
|
4331
|
-
if (!statusPathWithin(
|
|
4450
|
+
if (!statusPathWithin(resolve7(fullPath), root) || stat.size > MAX_HASH_BYTES) continue;
|
|
4332
4451
|
candidates.push({
|
|
4333
4452
|
relativePath,
|
|
4334
4453
|
name: basename5(fullPath),
|
|
@@ -4388,10 +4507,10 @@ function sanitizeRuntimeService(entry) {
|
|
|
4388
4507
|
};
|
|
4389
4508
|
}
|
|
4390
4509
|
function readRuntimeServicesSnapshot(cwd) {
|
|
4391
|
-
const snapshotPath = join9(
|
|
4510
|
+
const snapshotPath = join9(resolve7(cwd), WORKSPACE_RUNTIME_SERVICES_FILENAME);
|
|
4392
4511
|
if (!existsSync8(snapshotPath)) return [];
|
|
4393
4512
|
try {
|
|
4394
|
-
const parsed = JSON.parse(
|
|
4513
|
+
const parsed = JSON.parse(readFileSync6(snapshotPath, "utf8"));
|
|
4395
4514
|
const rawServices = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.services) ? parsed.services : [];
|
|
4396
4515
|
return rawServices.map((entry) => sanitizeRuntimeService(entry)).filter((entry) => entry !== null).slice(0, 50);
|
|
4397
4516
|
} catch {
|
|
@@ -4462,7 +4581,8 @@ function readWorkspaceStatus(cwd, opts = {}) {
|
|
|
4462
4581
|
}
|
|
4463
4582
|
|
|
4464
4583
|
// src/amaster-runtime-daemon.mjs
|
|
4465
|
-
var CONNECTOR_VERSION = "0.1.0-beta.
|
|
4584
|
+
var CONNECTOR_VERSION = "0.1.0-beta.21";
|
|
4585
|
+
var CONNECTOR_CONTRACT_VERSION = "2026-06-04.v1";
|
|
4466
4586
|
var MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
|
|
4467
4587
|
var CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4468
4588
|
var PROMPT_AGENT_INSTRUCTION_FILE_ORDER = ["AGENTS.md", "SOUL.md"];
|
|
@@ -4575,11 +4695,11 @@ function piExtraArgsDiagnostics(value) {
|
|
|
4575
4695
|
}
|
|
4576
4696
|
function safeExpandPath(value) {
|
|
4577
4697
|
const text = readString(value);
|
|
4578
|
-
return text ?
|
|
4698
|
+
return text ? resolve8(expandHomePath(text)) : null;
|
|
4579
4699
|
}
|
|
4580
4700
|
function safeJsonObjectFromFile(filePath) {
|
|
4581
4701
|
try {
|
|
4582
|
-
const parsed = JSON.parse(
|
|
4702
|
+
const parsed = JSON.parse(readFileSync7(filePath, "utf8"));
|
|
4583
4703
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
4584
4704
|
} catch {
|
|
4585
4705
|
return null;
|
|
@@ -4814,6 +4934,11 @@ function buildRegisterPayload(config) {
|
|
|
4814
4934
|
networkDomains: config.networkDomains,
|
|
4815
4935
|
executors: config.executors,
|
|
4816
4936
|
capabilities: config.capabilities,
|
|
4937
|
+
contractVersion: CONNECTOR_CONTRACT_VERSION,
|
|
4938
|
+
connectorVersion: CONNECTOR_VERSION,
|
|
4939
|
+
...buildCommit ? { buildCommit } : {},
|
|
4940
|
+
platform: process.platform,
|
|
4941
|
+
arch: process.arch,
|
|
4817
4942
|
metadata: {
|
|
4818
4943
|
daemon: "amaster-runtime-daemon.mjs",
|
|
4819
4944
|
connectorVersion: CONNECTOR_VERSION,
|
|
@@ -4856,6 +4981,9 @@ function buildHeartbeatPayload(config, options = {}) {
|
|
|
4856
4981
|
capabilities: config.capabilities,
|
|
4857
4982
|
connectorVersion: CONNECTOR_VERSION,
|
|
4858
4983
|
...buildCommit ? { buildCommit } : {},
|
|
4984
|
+
contractVersion: CONNECTOR_CONTRACT_VERSION,
|
|
4985
|
+
platform: process.platform,
|
|
4986
|
+
arch: process.arch,
|
|
4859
4987
|
runtimeStatus: {
|
|
4860
4988
|
daemon: "running",
|
|
4861
4989
|
pid: process.pid,
|
|
@@ -5395,7 +5523,7 @@ function commandExecutorEnv(command) {
|
|
|
5395
5523
|
}
|
|
5396
5524
|
function readJsonFile(filePath) {
|
|
5397
5525
|
try {
|
|
5398
|
-
const parsed = JSON.parse(
|
|
5526
|
+
const parsed = JSON.parse(readFileSync7(filePath, "utf8"));
|
|
5399
5527
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
5400
5528
|
} catch {
|
|
5401
5529
|
return {};
|
|
@@ -5565,12 +5693,12 @@ function safeAgentInstructionMaterializationTarget(workspace, filePath) {
|
|
|
5565
5693
|
const raw = readString(filePath);
|
|
5566
5694
|
if (!raw || raw.includes("\0")) return null;
|
|
5567
5695
|
const normalized = raw.replace(/\\/g, "/");
|
|
5568
|
-
if (
|
|
5696
|
+
if (isAbsolute6(normalized)) return null;
|
|
5569
5697
|
const segments = normalized.split("/").filter(Boolean);
|
|
5570
5698
|
if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) return null;
|
|
5571
5699
|
const relativePath = segments.join("/");
|
|
5572
|
-
const targetPath =
|
|
5573
|
-
if (!
|
|
5700
|
+
const targetPath = resolve8(workspace.cwd, relativePath);
|
|
5701
|
+
if (!pathWithin2(targetPath, workspace.cwd)) return null;
|
|
5574
5702
|
return { relativePath, targetPath };
|
|
5575
5703
|
}
|
|
5576
5704
|
async function materializeAgentInstructionsBundle(config, command, workspace) {
|
|
@@ -5653,14 +5781,14 @@ function safeCompanyPiHomeSegment(companyId) {
|
|
|
5653
5781
|
if (!raw) return null;
|
|
5654
5782
|
if (/^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$/.test(raw)) return raw;
|
|
5655
5783
|
const normalized = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").slice(0, 80);
|
|
5656
|
-
const hash =
|
|
5784
|
+
const hash = createHash6("sha256").update(raw).digest("hex").slice(0, 12);
|
|
5657
5785
|
return normalized ? `${normalized}-${hash}` : `company-${hash}`;
|
|
5658
5786
|
}
|
|
5659
5787
|
function companyPiHomeRoot(baseEnv) {
|
|
5660
5788
|
const explicitRoot = readString(baseEnv.AMASTER_COMPANY_PI_HOME_ROOT);
|
|
5661
|
-
if (explicitRoot) return
|
|
5789
|
+
if (explicitRoot) return resolve8(expandHomePath(explicitRoot));
|
|
5662
5790
|
const configuredPiHome = readString(baseEnv.PI_AGENT_HOME) ?? readString(baseEnv.PI_CODING_AGENT_DIR);
|
|
5663
|
-
if (configuredPiHome) return join10(dirname6(
|
|
5791
|
+
if (configuredPiHome) return join10(dirname6(resolve8(expandHomePath(configuredPiHome))), "companies");
|
|
5664
5792
|
return join10(homedir3(), ".amaster-employee", "companies");
|
|
5665
5793
|
}
|
|
5666
5794
|
function companyPiAgentHome(baseEnv, companyId) {
|
|
@@ -5725,9 +5853,9 @@ function resolveNativeSessionRequest(command, workspace) {
|
|
|
5725
5853
|
let cwdMatched = false;
|
|
5726
5854
|
if (requestedCwd && sourceWorkspacePath) {
|
|
5727
5855
|
try {
|
|
5728
|
-
cwdMatched =
|
|
5856
|
+
cwdMatched = realpathSync3(requestedCwd) === realpathSync3(sourceWorkspacePath);
|
|
5729
5857
|
} catch {
|
|
5730
|
-
cwdMatched =
|
|
5858
|
+
cwdMatched = resolve8(requestedCwd) === resolve8(sourceWorkspacePath);
|
|
5731
5859
|
}
|
|
5732
5860
|
}
|
|
5733
5861
|
const used = Boolean(enabled && requested && sessionId && requestedCwd && cwdMatched);
|
|
@@ -6105,6 +6233,11 @@ function createLiveOutputLogger(config, command, executorKind, protectedValues =
|
|
|
6105
6233
|
}
|
|
6106
6234
|
};
|
|
6107
6235
|
}
|
|
6236
|
+
function isRuntimeMetadataArtifactPath(value) {
|
|
6237
|
+
const normalized = String(value ?? "").replaceAll("\\", "/").replace(/^\.\//, "");
|
|
6238
|
+
const name = basename6(normalized);
|
|
6239
|
+
return name === WORKSPACE_MANIFEST_FILENAME || name === WORKSPACE_RUNTIME_SERVICES_FILENAME;
|
|
6240
|
+
}
|
|
6108
6241
|
function sampleProcessGroupRssBytes(processGroupId) {
|
|
6109
6242
|
if (process.platform === "win32" || processGroupId === null) return null;
|
|
6110
6243
|
const now = Date.now();
|
|
@@ -6130,15 +6263,15 @@ function sampleProcessGroupRssBytes(processGroupId) {
|
|
|
6130
6263
|
}
|
|
6131
6264
|
function realOrResolvedPath(value) {
|
|
6132
6265
|
try {
|
|
6133
|
-
return
|
|
6266
|
+
return realpathSync3(value);
|
|
6134
6267
|
} catch {
|
|
6135
|
-
return
|
|
6268
|
+
return resolve8(value);
|
|
6136
6269
|
}
|
|
6137
6270
|
}
|
|
6138
6271
|
function processCwdForPid(pid) {
|
|
6139
6272
|
if (process.platform === "linux") {
|
|
6140
6273
|
try {
|
|
6141
|
-
return
|
|
6274
|
+
return realpathSync3(`/proc/${pid}/cwd`);
|
|
6142
6275
|
} catch {
|
|
6143
6276
|
return null;
|
|
6144
6277
|
}
|
|
@@ -6229,7 +6362,7 @@ function listWorkspaceResidentProcesses(cwd, processGroupId, options = {}) {
|
|
|
6229
6362
|
if (processGroupId !== null && pgid === processGroupId) continue;
|
|
6230
6363
|
const commandMatches = commandLine.includes(cwd) || commandLine.includes(normalizedCwd);
|
|
6231
6364
|
const processCwd = commandMatches ? null : options.processCwdsByPid instanceof Map ? options.processCwdsByPid.get(pid) ?? null : processCwdForPid(pid);
|
|
6232
|
-
const cwdMatches = processCwd ?
|
|
6365
|
+
const cwdMatches = processCwd ? pathWithin2(processCwd, normalizedCwd) : false;
|
|
6233
6366
|
if (!commandMatches && !cwdMatches) continue;
|
|
6234
6367
|
rows.push({ pid, pgid, command: commandLine.slice(0, 300) });
|
|
6235
6368
|
}
|
|
@@ -6308,8 +6441,8 @@ function manifestMatchesActiveRuntimeRef(manifest, refs, workdir = null) {
|
|
|
6308
6441
|
}
|
|
6309
6442
|
function buildOrphanReaperSample(root, workdir, manifest, residents) {
|
|
6310
6443
|
const relativeWorkdir = (() => {
|
|
6311
|
-
const value =
|
|
6312
|
-
return value && !value.startsWith("..") && !
|
|
6444
|
+
const value = relative6(root, workdir);
|
|
6445
|
+
return value && !value.startsWith("..") && !isAbsolute6(value) ? value : basename6(workdir);
|
|
6313
6446
|
})();
|
|
6314
6447
|
return {
|
|
6315
6448
|
workdir: relativeWorkdir,
|
|
@@ -6613,6 +6746,53 @@ async function ingestWorkspaceStatus(config, command, cwd) {
|
|
|
6613
6746
|
runId: commandRunId(command) ?? void 0,
|
|
6614
6747
|
...status
|
|
6615
6748
|
});
|
|
6749
|
+
return status;
|
|
6750
|
+
}
|
|
6751
|
+
async function ingestRuntimeArtifacts(config, command, cwd, mcpToolResults) {
|
|
6752
|
+
const connectorId = requireConnectorId(config);
|
|
6753
|
+
const runId = commandRunId(command);
|
|
6754
|
+
if (!runId) throw new Error("Runtime Artifact ingest requires a correlated runId");
|
|
6755
|
+
const uploads = prepareRuntimeArtifactUploads(cwd, mcpToolResults);
|
|
6756
|
+
const receipts = [];
|
|
6757
|
+
for (const upload of uploads) {
|
|
6758
|
+
const path = `/api/amaster/runtime-connectors/${connectorId}/artifact-intents/${encodeURIComponent(upload.intentId)}/ingest`;
|
|
6759
|
+
try {
|
|
6760
|
+
const receipt = asRecord(await postRuntimeConnectorBytes(config, path, upload.body, {
|
|
6761
|
+
"x-amaster-command-id": command.commandId,
|
|
6762
|
+
"x-amaster-run-id": runId,
|
|
6763
|
+
"x-amaster-artifact-manifest-id": upload.manifestId,
|
|
6764
|
+
"x-amaster-artifact-source-path": upload.sourceRelativePath
|
|
6765
|
+
}));
|
|
6766
|
+
if (readString(receipt.intentId) !== upload.intentId || readString(receipt.status) !== "finalized") {
|
|
6767
|
+
throw new Error(`Runtime Artifact ${upload.intentId} was not finalized: ${JSON.stringify(receipt)}`);
|
|
6768
|
+
}
|
|
6769
|
+
receipts.push(receipt);
|
|
6770
|
+
await ingestLog(config, command, "system", "info", `Finalized Runtime Artifact ${upload.sourceRelativePath}`, {
|
|
6771
|
+
presentationKind: "runtime_artifact_ingest",
|
|
6772
|
+
intentId: upload.intentId,
|
|
6773
|
+
manifestId: upload.manifestId,
|
|
6774
|
+
sourceRelativePath: upload.sourceRelativePath,
|
|
6775
|
+
sha256: upload.sha256,
|
|
6776
|
+
byteSize: upload.byteSize,
|
|
6777
|
+
attachmentId: readString(receipt.attachmentId),
|
|
6778
|
+
workProductId: readString(receipt.workProductId),
|
|
6779
|
+
status: readString(receipt.status)
|
|
6780
|
+
});
|
|
6781
|
+
} catch (err) {
|
|
6782
|
+
const message = `Runtime Artifact ${upload.intentId} ingest failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
6783
|
+
await ingestLog(config, command, "system", "error", message, {
|
|
6784
|
+
presentationKind: "runtime_artifact_ingest",
|
|
6785
|
+
intentId: upload.intentId,
|
|
6786
|
+
manifestId: upload.manifestId,
|
|
6787
|
+
sourceRelativePath: upload.sourceRelativePath,
|
|
6788
|
+
sha256: upload.sha256,
|
|
6789
|
+
byteSize: upload.byteSize,
|
|
6790
|
+
status: "failed"
|
|
6791
|
+
});
|
|
6792
|
+
throw new Error(message);
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
return receipts;
|
|
6616
6796
|
}
|
|
6617
6797
|
function resultOutboxActiveRunSnapshot(command) {
|
|
6618
6798
|
const commandId = readString(command.commandId) ?? readString(command.id);
|
|
@@ -6664,7 +6844,7 @@ async function completeCommand(config, command, status, result2, error) {
|
|
|
6664
6844
|
}
|
|
6665
6845
|
function resultOutboxDir(config) {
|
|
6666
6846
|
const explicit = readString(process.env.AMASTER_RESULT_OUTBOX_DIR);
|
|
6667
|
-
if (explicit) return
|
|
6847
|
+
if (explicit) return resolve8(expandHomePath(explicit));
|
|
6668
6848
|
return join10(dirname6(stateFilePath(process.env)), "result-outbox");
|
|
6669
6849
|
}
|
|
6670
6850
|
function resultOutboxInvalidDir(config) {
|
|
@@ -6709,7 +6889,7 @@ function moveResultOutboxEntryToInvalid(config, file, fullPath, reason, detail,
|
|
|
6709
6889
|
function readValidResultOutboxEntryOrQuarantine(config, file, fullPath) {
|
|
6710
6890
|
let entry;
|
|
6711
6891
|
try {
|
|
6712
|
-
entry = JSON.parse(
|
|
6892
|
+
entry = JSON.parse(readFileSync7(fullPath, "utf8"));
|
|
6713
6893
|
} catch (err) {
|
|
6714
6894
|
const message = err instanceof Error ? err.message : String(err);
|
|
6715
6895
|
moveResultOutboxEntryToInvalid(config, file, fullPath, "malformed_result_outbox_json", message);
|
|
@@ -6987,7 +7167,7 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
6987
7167
|
const body = await runtimeApiBuffer(runtimeAuth, contentPath);
|
|
6988
7168
|
writeFileSync5(targetPath, body);
|
|
6989
7169
|
const attachmentId = readString(attachment.id);
|
|
6990
|
-
const actualSha256 =
|
|
7170
|
+
const actualSha256 = createHash6("sha256").update(body).digest("hex");
|
|
6991
7171
|
const lineageCandidates = lineageCandidatesByAttachmentId.get(attachmentId) ?? [];
|
|
6992
7172
|
const lineage = selectAttachmentLineage(lineageCandidates, actualSha256);
|
|
6993
7173
|
if (lineageCandidates.length > 0 && !lineage) {
|
|
@@ -7010,7 +7190,7 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
7010
7190
|
id: attachmentId,
|
|
7011
7191
|
name: readString(attachment.originalFilename) ?? filename,
|
|
7012
7192
|
path: targetPath,
|
|
7013
|
-
relativePath:
|
|
7193
|
+
relativePath: relative6(workspace.cwd, targetPath),
|
|
7014
7194
|
contentType: readString(attachment.contentType),
|
|
7015
7195
|
byteSize: body.byteLength,
|
|
7016
7196
|
contentPath,
|
|
@@ -7079,7 +7259,7 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
7079
7259
|
throw new Error(`artifact_input_manifest_invalid: entry ${index} contentPath does not match attachmentId`);
|
|
7080
7260
|
}
|
|
7081
7261
|
const body = await runtimeApiBuffer(runtimeAuth, contentPath);
|
|
7082
|
-
const actualSha256 =
|
|
7262
|
+
const actualSha256 = createHash6("sha256").update(body).digest("hex");
|
|
7083
7263
|
if (body.byteLength !== byteSize || actualSha256 !== sha256) {
|
|
7084
7264
|
throw new Error(
|
|
7085
7265
|
`artifact_input_integrity_mismatch: workProductId=${workProductId} expectedBytes=${byteSize} actualBytes=${body.byteLength} expectedSha256=${sha256} actualSha256=${actualSha256}`
|
|
@@ -7142,19 +7322,22 @@ async function clearIssueCheckpoint(config, command, workspace, reason) {
|
|
|
7142
7322
|
}
|
|
7143
7323
|
function safeCheckpointRelativePath(rawPath) {
|
|
7144
7324
|
const raw = String(rawPath ?? "").trim();
|
|
7145
|
-
if (
|
|
7325
|
+
if (isAbsolute6(raw) || /^[A-Za-z]:[\\/]/.test(raw)) return null;
|
|
7146
7326
|
const normalized = raw.split(/[\\/]+/).filter(Boolean).join("/");
|
|
7147
7327
|
if (!normalized || normalized.startsWith("../") || normalized.split("/").some((segment) => segment.startsWith("."))) return null;
|
|
7148
7328
|
if (normalized.startsWith("input-attachments/") || isRuntimeMetadataArtifactPath(normalized)) return null;
|
|
7149
7329
|
return normalized;
|
|
7150
7330
|
}
|
|
7331
|
+
function hashFileSha256(filePath) {
|
|
7332
|
+
return createHash6("sha256").update(readFileSync7(filePath)).digest("hex");
|
|
7333
|
+
}
|
|
7151
7334
|
async function materializeIssueCheckpoint(config, command, workspace) {
|
|
7152
7335
|
const checkpointDir = issueCheckpointDir(workspace);
|
|
7153
7336
|
const manifestPath = join10(checkpointDir, "manifest.json");
|
|
7154
7337
|
if (!existsSync9(manifestPath)) return [];
|
|
7155
7338
|
let manifest;
|
|
7156
7339
|
try {
|
|
7157
|
-
manifest = asRecord(JSON.parse(
|
|
7340
|
+
manifest = asRecord(JSON.parse(readFileSync7(manifestPath, "utf8")));
|
|
7158
7341
|
} catch (err) {
|
|
7159
7342
|
rmSync4(checkpointDir, { recursive: true, force: true });
|
|
7160
7343
|
throw new Error(`invalid issue checkpoint manifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7167,22 +7350,22 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
7167
7350
|
try {
|
|
7168
7351
|
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
7169
7352
|
if (files.length > 20) throw new Error("issue checkpoint manifest exceeds the limit of 20 files");
|
|
7170
|
-
const filesRoot =
|
|
7171
|
-
const workspaceRoot =
|
|
7353
|
+
const filesRoot = realpathSync3(join10(checkpointDir, "files"));
|
|
7354
|
+
const workspaceRoot = realpathSync3(workspace.cwd);
|
|
7172
7355
|
const validated = [];
|
|
7173
7356
|
let totalBytes = 0;
|
|
7174
7357
|
for (const rawFile of files) {
|
|
7175
7358
|
const file = asRecord(rawFile);
|
|
7176
7359
|
const relativePath = safeCheckpointRelativePath(readString(file.path));
|
|
7177
7360
|
if (!relativePath) throw new Error("issue checkpoint manifest contains an invalid file path");
|
|
7178
|
-
const sourceCandidate =
|
|
7179
|
-
const target =
|
|
7180
|
-
if (!
|
|
7361
|
+
const sourceCandidate = resolve8(filesRoot, relativePath);
|
|
7362
|
+
const target = resolve8(workspaceRoot, relativePath);
|
|
7363
|
+
if (!pathWithin2(sourceCandidate, filesRoot) || !pathWithin2(target, workspaceRoot)) {
|
|
7181
7364
|
throw new Error(`issue checkpoint path escapes its workspace: ${relativePath}`);
|
|
7182
7365
|
}
|
|
7183
7366
|
if (!existsSync9(sourceCandidate)) throw new Error(`issue checkpoint file is missing: ${relativePath}`);
|
|
7184
|
-
const source =
|
|
7185
|
-
if (!
|
|
7367
|
+
const source = realpathSync3(sourceCandidate);
|
|
7368
|
+
if (!pathWithin2(source, filesRoot) || !statSync7(source).isFile()) {
|
|
7186
7369
|
throw new Error(`issue checkpoint source escapes its storage root: ${relativePath}`);
|
|
7187
7370
|
}
|
|
7188
7371
|
const expectedSha256 = readString(file.sha256);
|
|
@@ -7200,14 +7383,14 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
7200
7383
|
const materialized = [];
|
|
7201
7384
|
for (const { relativePath, source, target } of validated) {
|
|
7202
7385
|
try {
|
|
7203
|
-
|
|
7386
|
+
lstatSync4(target);
|
|
7204
7387
|
continue;
|
|
7205
7388
|
} catch (err) {
|
|
7206
7389
|
if (err?.code !== "ENOENT") throw err;
|
|
7207
7390
|
}
|
|
7208
7391
|
mkdirSync5(dirname6(target), { recursive: true });
|
|
7209
|
-
const targetParent =
|
|
7210
|
-
if (!
|
|
7392
|
+
const targetParent = realpathSync3(dirname6(target));
|
|
7393
|
+
if (!pathWithin2(targetParent, workspaceRoot)) {
|
|
7211
7394
|
throw new Error(`issue checkpoint target escapes its workspace: ${relativePath}`);
|
|
7212
7395
|
}
|
|
7213
7396
|
copyFileSync2(source, target);
|
|
@@ -7232,6 +7415,49 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
7232
7415
|
throw restoreErr;
|
|
7233
7416
|
}
|
|
7234
7417
|
}
|
|
7418
|
+
async function saveIssueCheckpoint(config, command, workspace, candidates) {
|
|
7419
|
+
const checkpointDir = issueCheckpointDir(workspace);
|
|
7420
|
+
const filesDir = join10(checkpointDir, "files");
|
|
7421
|
+
rmSync4(checkpointDir, { recursive: true, force: true });
|
|
7422
|
+
mkdirSync5(filesDir, { recursive: true });
|
|
7423
|
+
const files = [];
|
|
7424
|
+
let totalBytes = 0;
|
|
7425
|
+
const workspaceRoot = realpathSync3(workspace.cwd);
|
|
7426
|
+
for (const candidate of candidates.slice(0, 20)) {
|
|
7427
|
+
const relativePath = safeCheckpointRelativePath(candidate.rawPath);
|
|
7428
|
+
const source = readString(candidate.filePath);
|
|
7429
|
+
if (!relativePath || !source || !existsSync9(source) || !statSync7(source).isFile()) continue;
|
|
7430
|
+
const ownedSource = realpathSync3(source);
|
|
7431
|
+
if (!pathWithin2(ownedSource, workspaceRoot)) {
|
|
7432
|
+
throw new Error(`issue checkpoint source escapes its execution workspace: ${relativePath}`);
|
|
7433
|
+
}
|
|
7434
|
+
const byteSize = statSync7(ownedSource).size;
|
|
7435
|
+
if (byteSize <= 0 || totalBytes + byteSize > MAX_CHECKPOINT_BYTES) continue;
|
|
7436
|
+
const target = resolve8(filesDir, relativePath);
|
|
7437
|
+
if (!pathWithin2(target, filesDir)) continue;
|
|
7438
|
+
mkdirSync5(dirname6(target), { recursive: true });
|
|
7439
|
+
copyFileSync2(ownedSource, target);
|
|
7440
|
+
totalBytes += byteSize;
|
|
7441
|
+
files.push({ path: relativePath, byteSize, sha256: hashFileSha256(ownedSource) });
|
|
7442
|
+
}
|
|
7443
|
+
if (files.length === 0) {
|
|
7444
|
+
rmSync4(checkpointDir, { recursive: true, force: true });
|
|
7445
|
+
return null;
|
|
7446
|
+
}
|
|
7447
|
+
const manifest = {
|
|
7448
|
+
version: 1,
|
|
7449
|
+
issueId: commandIssueId(command),
|
|
7450
|
+
sourceRunId: commandRunId(command),
|
|
7451
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7452
|
+
expiresAt: new Date(Date.now() + CHECKPOINT_TTL_MS).toISOString(),
|
|
7453
|
+
totalBytes,
|
|
7454
|
+
files
|
|
7455
|
+
};
|
|
7456
|
+
writeFileSync5(join10(checkpointDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
7457
|
+
`);
|
|
7458
|
+
await ingestLog(config, command, "system", "warn", `Saved ${files.length} required deliverable(s) for continuation recovery`, manifest);
|
|
7459
|
+
return manifest;
|
|
7460
|
+
}
|
|
7235
7461
|
function piOutputUsageMetadataMissing(parsed) {
|
|
7236
7462
|
const usage = parsed?.usage ?? {};
|
|
7237
7463
|
const totalTokens = Number(usage.inputTokens ?? 0) + Number(usage.cachedInputTokens ?? 0) + Number(usage.outputTokens ?? 0);
|
|
@@ -7284,6 +7510,7 @@ async function executeRunCommand(config, command) {
|
|
|
7284
7510
|
error: err instanceof Error ? err.message : String(err)
|
|
7285
7511
|
});
|
|
7286
7512
|
}
|
|
7513
|
+
await ingestWorkspaceStatus(config, command, cwd);
|
|
7287
7514
|
const promptCompilation = buildCommandPrompt(command, workspace, materializedAttachments, {
|
|
7288
7515
|
executorKind: executor.kind,
|
|
7289
7516
|
artifactVerifierCommands: config.artifactVerifierCommands
|
|
@@ -7429,7 +7656,7 @@ async function executeRunCommand(config, command) {
|
|
|
7429
7656
|
}
|
|
7430
7657
|
);
|
|
7431
7658
|
await ingestCost(config, command, executor, parsed);
|
|
7432
|
-
await ingestWorkspaceStatus(config, command, cwd);
|
|
7659
|
+
const workspaceStatus = await ingestWorkspaceStatus(config, command, cwd);
|
|
7433
7660
|
let nativeSessionRollout = null;
|
|
7434
7661
|
let nativeSessionRolloutError = null;
|
|
7435
7662
|
let nativeSessionRolloutCleanup = null;
|
|
@@ -7438,6 +7665,7 @@ async function executeRunCommand(config, command) {
|
|
|
7438
7665
|
...Array.isArray(parsed.mcpToolResults) ? parsed.mcpToolResults.map(asRecord) : [],
|
|
7439
7666
|
...liveOutputLogger.mcpToolResults().map(asRecord)
|
|
7440
7667
|
];
|
|
7668
|
+
const runtimeArtifacts = await ingestRuntimeArtifacts(config, command, cwd, mcpToolResults);
|
|
7441
7669
|
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((result3) => readString(result3.status) === "approval_required");
|
|
7442
7670
|
if (shouldPreserveNativeSession) {
|
|
7443
7671
|
try {
|
|
@@ -7446,7 +7674,20 @@ async function executeRunCommand(config, command) {
|
|
|
7446
7674
|
sessionId: parsed.sessionId,
|
|
7447
7675
|
sourceWorkspacePath: workspace.sourceWorkspacePath
|
|
7448
7676
|
});
|
|
7449
|
-
|
|
7677
|
+
const checkpoint = await saveIssueCheckpoint(
|
|
7678
|
+
config,
|
|
7679
|
+
command,
|
|
7680
|
+
workspace,
|
|
7681
|
+
workspaceStatus.artifacts.map((artifact) => ({
|
|
7682
|
+
rawPath: artifact.relativePath,
|
|
7683
|
+
filePath: resolve8(cwd, artifact.relativePath)
|
|
7684
|
+
}))
|
|
7685
|
+
);
|
|
7686
|
+
nativeSessionRollout = {
|
|
7687
|
+
status: preservedRollout.status,
|
|
7688
|
+
sessionId: preservedRollout.sessionId,
|
|
7689
|
+
...checkpoint ? { checkpoint: { fileCount: checkpoint.files.length, totalBytes: checkpoint.totalBytes } } : {}
|
|
7690
|
+
};
|
|
7450
7691
|
await ingestLog(config, command, "system", "info", `Preserved ${executor.kind} session rollout for an approved action continuation`, {
|
|
7451
7692
|
presentationKind: "managed_mcp_session_rollout",
|
|
7452
7693
|
status: nativeSessionRollout.status,
|
|
@@ -7461,8 +7702,10 @@ async function executeRunCommand(config, command) {
|
|
|
7461
7702
|
});
|
|
7462
7703
|
}
|
|
7463
7704
|
}
|
|
7464
|
-
const
|
|
7465
|
-
|
|
7705
|
+
const approvedContinuationSucceeded = nativeSessionRequest.mode === "governed_action_approval" && approvedMcpInvocationSucceeded(mcpToolResults, nativeSessionRequest.invocationId);
|
|
7706
|
+
if (approvedContinuationSucceeded) {
|
|
7707
|
+
await clearIssueCheckpoint(config, command, workspace, "approved_action_succeeded");
|
|
7708
|
+
}
|
|
7466
7709
|
if (managedMcpProfile && executor.kind === "codex" && approvedContinuationSucceeded) {
|
|
7467
7710
|
try {
|
|
7468
7711
|
nativeSessionRolloutCleanup = cleanupRestoredManagedCodexSessionRollout(managedMcpProfile);
|
|
@@ -7537,6 +7780,7 @@ async function executeRunCommand(config, command) {
|
|
|
7537
7780
|
summary: parsed.summary || (succeeded ? "Executor completed without a text summary." : ""),
|
|
7538
7781
|
usage: parsed.usage,
|
|
7539
7782
|
...costUsage ? { costUsage } : {},
|
|
7783
|
+
...runtimeArtifacts.length > 0 ? { runtimeArtifacts } : {},
|
|
7540
7784
|
outputTelemetry,
|
|
7541
7785
|
contextManifest,
|
|
7542
7786
|
stdout: truncateText(execution.stdout, 8e3),
|
|
@@ -7802,7 +8046,7 @@ async function runLoop(config) {
|
|
|
7802
8046
|
${message}
|
|
7803
8047
|
`);
|
|
7804
8048
|
}
|
|
7805
|
-
await new Promise((
|
|
8049
|
+
await new Promise((resolve9) => setTimeout(resolve9, config.pollIntervalSeconds * 1e3));
|
|
7806
8050
|
}
|
|
7807
8051
|
}
|
|
7808
8052
|
function help() {
|
package/dist/amaster-runtime.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
5
5
|
import { homedir, hostname } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
-
const CONNECTOR_VERSION = "0.1.0-beta.
|
|
8
|
+
const CONNECTOR_VERSION = "0.1.0-beta.21";
|
|
9
9
|
|
|
10
10
|
const CAPABILITIES = [
|
|
11
11
|
"remote_registration",
|