@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 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 createHash5 } from "node:crypto";
6
- import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync9, lstatSync as lstatSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync6, readdirSync as readdirSync6, realpathSync as realpathSync2, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync7, symlinkSync as symlinkSync2, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
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 isAbsolute5, join as join10, relative as relative5, resolve as resolve7 } from "node:path";
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: continuationText(context), content: mode === "cold" ? "" : continuationText(context), truncationReason: mode === "cold" ? "mode_selection" : null },
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: mode === "cold" ? taskText : "", truncationReason: mode === "cold" ? null : "mode_selection" },
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 structuredContent = asRecord(asRecord(item.result).structuredContent);
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
- results.push({ ...invocationId ? { invocationId } : {}, status });
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/workspace-guard.mjs
3780
+ // src/amaster-runtime-daemon/runtime-artifact-upload.mjs
3726
3781
  import { createHash as createHash3 } from "node:crypto";
3727
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, realpathSync, statSync as statSync3 } from "node:fs";
3728
- import { basename as basename4, join as join6, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
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 readFileSync4, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
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(readFileSync4(manifestPath, "utf8"));
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 pathWithin(candidate, root) {
3811
- const rel = relative3(root, candidate);
3812
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
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 = realpathSync(resolve3(expandHomePath(requested ?? fallback)));
3933
+ const cwd = realpathSync2(resolve4(expandHomePath(requested ?? fallback)));
3819
3934
  const allowlist = config.workspaceBindings.flatMap((entry) => {
3820
3935
  try {
3821
- return [realpathSync(resolve3(expandHomePath(entry)))];
3936
+ return [realpathSync2(resolve4(expandHomePath(entry)))];
3822
3937
  } catch {
3823
3938
  return [];
3824
3939
  }
3825
3940
  });
3826
- const allowed = allowlist.some((entry) => pathWithin(cwd, 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 createHash3("sha256").update(String(value)).digest("hex").slice(0, length);
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 = resolve3(expandHomePath(config.runtimeWorkspacesRoot ?? "~/.amaster-employee/workspaces"));
3975
+ const root = resolve4(expandHomePath(config.runtimeWorkspacesRoot ?? "~/.amaster-employee/workspaces"));
3861
3976
  mkdirSync4(root, { recursive: true });
3862
- return realpathSync(root);
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 resolve4 } from "node:path";
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 = resolve4(String(input.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 resolve5 } from "node:path";
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 = resolve5(String(config.AMASTER_RUNTIME_WORKSPACES_ROOT ?? config.runtimeWorkspacesRoot ?? input.runtimeWorkspacesRoot ?? ""));
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 createHash4 } from "node:crypto";
4197
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync6 } from "node:fs";
4198
- import { basename as basename5, extname, isAbsolute as isAbsolute4, join as join9, relative as relative4, resolve as resolve6 } from "node:path";
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 = relative4(root, candidate);
4241
- return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
4358
+ const rel = relative5(root, candidate);
4359
+ return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
4242
4360
  }
4243
4361
  function normalizeRelativePath(root, filePath) {
4244
- return relative4(root, filePath).split(/[\\/]+/).join("/");
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
- return !SECRET_PATH_PATTERN.test(text);
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 createHash4("sha256").update(readFileSync5(filePath)).digest("hex");
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 = resolve6(cwd);
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(resolve6(fullPath), root) || stat.size > MAX_HASH_BYTES) continue;
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(resolve6(cwd), WORKSPACE_RUNTIME_SERVICES_FILENAME);
4510
+ const snapshotPath = join9(resolve7(cwd), WORKSPACE_RUNTIME_SERVICES_FILENAME);
4392
4511
  if (!existsSync8(snapshotPath)) return [];
4393
4512
  try {
4394
- const parsed = JSON.parse(readFileSync5(snapshotPath, "utf8"));
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.20";
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 ? resolve7(expandHomePath(text)) : null;
4698
+ return text ? resolve8(expandHomePath(text)) : null;
4579
4699
  }
4580
4700
  function safeJsonObjectFromFile(filePath) {
4581
4701
  try {
4582
- const parsed = JSON.parse(readFileSync6(filePath, "utf8"));
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(readFileSync6(filePath, "utf8"));
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 (isAbsolute5(normalized)) return null;
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 = resolve7(workspace.cwd, relativePath);
5573
- if (!pathWithin(targetPath, workspace.cwd)) return null;
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 = createHash5("sha256").update(raw).digest("hex").slice(0, 12);
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 resolve7(expandHomePath(explicitRoot));
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(resolve7(expandHomePath(configuredPiHome))), "companies");
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 = realpathSync2(requestedCwd) === realpathSync2(sourceWorkspacePath);
5856
+ cwdMatched = realpathSync3(requestedCwd) === realpathSync3(sourceWorkspacePath);
5729
5857
  } catch {
5730
- cwdMatched = resolve7(requestedCwd) === resolve7(sourceWorkspacePath);
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 realpathSync2(value);
6266
+ return realpathSync3(value);
6134
6267
  } catch {
6135
- return resolve7(value);
6268
+ return resolve8(value);
6136
6269
  }
6137
6270
  }
6138
6271
  function processCwdForPid(pid) {
6139
6272
  if (process.platform === "linux") {
6140
6273
  try {
6141
- return realpathSync2(`/proc/${pid}/cwd`);
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 ? pathWithin(processCwd, normalizedCwd) : false;
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 = relative5(root, workdir);
6312
- return value && !value.startsWith("..") && !isAbsolute5(value) ? value : basename6(workdir);
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 resolve7(expandHomePath(explicit));
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(readFileSync6(fullPath, "utf8"));
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 = createHash5("sha256").update(body).digest("hex");
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: relative5(workspace.cwd, targetPath),
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 = createHash5("sha256").update(body).digest("hex");
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 (isAbsolute5(raw) || /^[A-Za-z]:[\\/]/.test(raw)) return null;
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(readFileSync6(manifestPath, "utf8")));
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 = realpathSync2(join10(checkpointDir, "files"));
7171
- const workspaceRoot = realpathSync2(workspace.cwd);
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 = resolve7(filesRoot, relativePath);
7179
- const target = resolve7(workspaceRoot, relativePath);
7180
- if (!pathWithin(sourceCandidate, filesRoot) || !pathWithin(target, workspaceRoot)) {
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 = realpathSync2(sourceCandidate);
7185
- if (!pathWithin(source, filesRoot) || !statSync7(source).isFile()) {
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
- lstatSync3(target);
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 = realpathSync2(dirname6(target));
7210
- if (!pathWithin(targetParent, workspaceRoot)) {
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
- nativeSessionRollout = { status: preservedRollout.status, sessionId: preservedRollout.sessionId };
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 approvedInvocationId = readString(nativeSessionRequest.invocationId);
7465
- const approvedContinuationSucceeded = nativeSessionRequest.mode === "governed_action_approval" && approvedInvocationId && mcpToolResults.some((result3) => readString(result3.invocationId) === approvedInvocationId && readString(result3.status) === "succeeded");
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((resolve8) => setTimeout(resolve8, config.pollIntervalSeconds * 1e3));
8049
+ await new Promise((resolve9) => setTimeout(resolve9, config.pollIntervalSeconds * 1e3));
7806
8050
  }
7807
8051
  }
7808
8052
  function help() {
@@ -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.20";
8
+ const CONNECTOR_VERSION = "0.1.0-beta.21";
9
9
 
10
10
  const CAPABILITIES = [
11
11
  "remote_registration",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/employee-runtime-connector",
3
- "version": "0.1.0-beta.20",
3
+ "version": "0.1.0-beta.21",
4
4
  "description": "AMaster Employee runtime connector CLI and daemon",
5
5
  "license": "MIT",
6
6
  "type": "module",