@amaster.ai/employee-runtime-connector 0.1.0-beta.7 → 0.1.0-beta.9

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.
@@ -2331,11 +2331,11 @@ function readWorkspaceStatus(cwd, opts = {}) {
2331
2331
  // ---- bundled entrypoint: src/amaster-runtime-daemon.mjs ----
2332
2332
 
2333
2333
  import { createHash } from "node:crypto";
2334
- import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2334
+ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2335
2335
  import { homedir, hostname } from "node:os";
2336
2336
  import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
2337
2337
  import { spawn, spawnSync } from "node:child_process";
2338
- const CONNECTOR_VERSION = "0.1.0-beta.7";
2338
+ const CONNECTOR_VERSION = "0.1.0-beta.9";
2339
2339
  const MAX_INFERRED_ARTIFACT_UPLOADS = 20;
2340
2340
  const MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
2341
2341
  const CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1000;
@@ -3515,11 +3515,23 @@ function canRuntimeAgentOrchestrateOrganization(context) {
3515
3515
  }
3516
3516
 
3517
3517
  function runtimeActionsInstruction(options = {}) {
3518
+ const authorizationEnvelope = options.authorizationEnvelope ?? {
3519
+ allowedActionClasses: new Set(["task_governance"]),
3520
+ sourceKind: "implicit_default",
3521
+ sourceId: "unknown",
3522
+ sourceRevision: "missing_envelope",
3523
+ };
3524
+ const knownActionClasses = ["task_governance", "organization_mutation", "executable_child"];
3525
+ const allowedActionClasses = knownActionClasses.filter((actionClass) =>
3526
+ authorizationEnvelope.allowedActionClasses.has(actionClass));
3527
+ const deniedActionClasses = knownActionClasses.filter((actionClass) =>
3528
+ !authorizationEnvelope.allowedActionClasses.has(actionClass));
3518
3529
  const allowedActionTypes = [
3519
3530
  "add_comment",
3520
3531
  "update_parent",
3521
3532
  "create_child_task",
3522
3533
  "upload_artifact",
3534
+ "upsert_document",
3523
3535
  ...(options.canOrchestrateOrganization ? ["agent_hire"] : []),
3524
3536
  "create_interaction",
3525
3537
  ].join(", ");
@@ -3530,13 +3542,20 @@ function runtimeActionsInstruction(options = {}) {
3530
3542
  const actionValidatorCommand = readString(options.actionValidatorCommand);
3531
3543
  return [
3532
3544
  "Runtime action bridge:",
3545
+ `Allowed runtime action classes for this wake: ${allowedActionClasses.join(", ") || "none"}.`,
3546
+ `Denied runtime action classes for this wake: ${deniedActionClasses.join(", ") || "none"}.`,
3547
+ `Wake authorization source: ${authorizationEnvelope.sourceKind}:${authorizationEnvelope.sourceId} revision ${authorizationEnvelope.sourceRevision}.`,
3548
+ "This capability guidance is rendered from the same authorization envelope enforced by runtime preflight. Preflight remains the final authority. task_governance covers comments, parent status, backlog children, artifacts, and interactions; organization_mutation covers agent_hire/create_agent; executable_child covers todo/in_progress child creation.",
3533
3549
  "Choose exactly one mutation path for each requested action: direct AMaster API or the runtime action bridge. Never replay the same mutation through both paths.",
3534
3550
  "If direct AMaster API requests succeed for all requested mutations, finish with a normal summary and do not emit or mention the AMASTER_RUNTIME_ACTIONS marker.",
3535
3551
  "If direct AMaster API requests are unavailable from the executor sandbox, do not stop at blocked. Use the runtime action bridge for the unapplied mutations.",
3536
3552
  "Use runtime actions as a continuation patch, not as a full replay. If the wake reason or latest comments say a confirmation was accepted, rejected, or that no more child tasks/interactions should be created, do not recreate those earlier actions. Add the final result comment and update the parent task instead.",
3553
+ "On an accepted request_confirmation or request_checkbox_confirmation continuation, any new confirmation must include payload.target.type and a non-empty payload.target.key. Missing decision identity fields fail before mutation with accepted_confirmation_decision_target_required. The server reuses identical content for the same target identity and rejects changed prompt, options, or labels until the action uses the genuinely new target revision.",
3554
+ "Every request_confirmation and request_checkbox_confirmation should bind the concrete decision to payload.target using {type:\"custom\", key, label, revisionId} or the applicable issue_document target. Keep target.key stable for the same decision purpose and change target.revisionId only when the target content changes. Replace example target values with task-specific values; when no business revision exists, use the current run id as revisionId instead of omitting target.",
3537
3555
  "If the wake reason is issue_children_completed, treat the completed child tasks as already resolved blockers. Do not recreate blocker child tasks, do not repeat the first-run blocker plan, and do not create another review path unless new human input is truly required. Summarize the completed child work and use update_parent status done when no human review is needed.",
3538
3556
  "Use update_parent status done when no human review is needed. Prefer creating a pending review path such as request_confirmation, request_checkbox_confirmation, ask_user_questions, or suggest_tasks before status in_review; if a bare in_review update is emitted, AMaster will create a fallback request_confirmation before PATCH.",
3539
3557
  "For create_child_task, do not create ownerless executable todo tasks. Set assigneeAgentId only when the target agent id came from the current AMaster task context or an agent_hire action in this same batch. Do not invent agent ids, role ids, or ids from another company. If you are unsure, omit assigneeAgentId and assigneeUserId; AMaster will assign the current executing agent for todo/in_progress children. Use backlog only when a task is intentionally unassigned.",
3558
+ "Every root create_child_task with status todo or in_progress must establish the parent disposition in the same batch. Set blockParentUntilDone true when the parent depends on that child. Only omit the wait when the same batch also closes the parent's independent scope with update_parent status done or cancelled. parentId alone is structural and never proves that the parent is waiting; missing both paths fails with delegated_parent_wait_required.",
3540
3559
  "Creating child tasks and delegating them to existing agents is task execution, not organization orchestration. Use assignee ids only from the current task context; server permissions still decide whether an assignment is allowed.",
3541
3560
  "Runtime actions use a strict schema. Unknown fields, unsupported action types, duplicate clientKey values, unknown client-key references, and client-key dependency cycles reject the entire batch before any mutation. blockedByIssueIds is supported only on create_child_task for existing issue UUIDs supplied by the current task context; connector preflight checks UUID shape, while the server validates same-company existence and permissions. Do not place it on update_parent or invent fields that are not in the canonical action examples.",
3542
3561
  "Use native JSON scalar types exactly: booleans such as create_child_task.blockParentUntilDone, top-level deliverables[].required, and create_interaction payload questions[].required must be true or false without quotes. Numbers such as minSelected/maxSelected/version must be numeric literals without quotes. String forms such as \"true\" or \"1\" reject the whole batch; AMaster does not silently coerce them.",
@@ -3556,6 +3575,9 @@ function runtimeActionsInstruction(options = {}) {
3556
3575
  options.canOrchestrateOrganization
3557
3576
  ? `For agent_hire, role must be one of: ${AGENT_HIRE_SCHEMA_ROLES.join(", ")}. Put custom business role names in name/title/capabilities/instructionsBundle, not in role. Map product/customer insight to research or product, growth/revenue to marketing or sales, finance/budget to finance, engineering delivery to engineering, legal/compliance to legal, customer success/support to customer_support. capabilities must be a string, not an array. instructionsBundle must use {entryFile:"AGENTS.md", files: {"AGENTS.md": "..."}}, and instructionsBundle.files must contain at least one file.`
3558
3577
  : "",
3578
+ options.canOrchestrateOrganization
3579
+ ? "Every agent_hire must declare exactly one reporting disposition. For an existing manager from the current task context, set reportsTo to that manager UUID. For a manager hired in the same batch, set reportsToAgentClientKey to the manager's clientKey. Only a genuinely company-root role may set topLevel true. reportsToAgentId is unsupported; use reportsTo for an existing manager UUID."
3580
+ : "",
3559
3581
  options.canOrchestrateOrganization
3560
3582
  ? "When assigning a newly hired agent's first task in the same action batch, set clientKey on the agent_hire action and set assigneeAgentClientKey or agentClientKey on the create_child_task action to that same key. Do not leave the first task assigned to the current CEO/executing agent unless the CEO should actually do the work."
3561
3583
  : "",
@@ -3567,11 +3589,12 @@ function runtimeActionsInstruction(options = {}) {
3567
3589
  ? `Before final output, write only the JSON envelope to .amaster-runtime-actions.json and run this exact strict-schema validator: ${actionValidatorCommand}. Fix every reported error and rerun until it exits 0. JSON.parse alone is not sufficient. Do not include the marker in the temporary file.`
3568
3590
  : "",
3569
3591
  `Allowed action types: ${allowedActionTypes}.`,
3570
- "For every create_interaction action, set top-level kind to the interaction kind and set payload to a JSON object. Use this complete canonical request_confirmation example: {\"type\":\"create_interaction\",\"kind\":\"request_confirmation\",\"continuationPolicy\":\"wake_assignee_on_accept\",\"payload\":{\"version\":1,\"prompt\":\"Confirm this result?\",\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\"}}. Never use interactionType.",
3592
+ "For upsert_document, write or replace a Markdown document on the current AMaster task. Use {\"type\":\"upsert_document\",\"key\":\"business-brief\",\"title\":\"2026-07-16 每日经营简报\",\"body\":\"# 每日经营简报\\n...\",\"format\":\"markdown\",\"changeSummary\":\"生成每日经营简报\"}. key, body, and format are required; format must be markdown. Never target a different issue.",
3593
+ "For every create_interaction action, set top-level kind to the interaction kind and set payload to a JSON object. Use this complete canonical request_confirmation example: {\"type\":\"create_interaction\",\"kind\":\"request_confirmation\",\"continuationPolicy\":\"wake_assignee_on_accept\",\"payload\":{\"version\":1,\"prompt\":\"Confirm this result?\",\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\",\"target\":{\"type\":\"custom\",\"key\":\"delivery-review\",\"label\":\"交付结果\",\"revisionId\":\"v1\"}}}. Never use interactionType.",
3571
3594
  "Interactions cannot be updated with PATCH after creation. For direct API calls, include continuationPolicy in the initial POST /api/issues/{issueId}/interactions request and use only the documented POST accept, reject, respond, or cancel resolution endpoint afterward.",
3572
3595
  "For create_interaction ask_user_questions, use continuationPolicy wake_assignee, never wake_assignee_on_accept, because answering questions does not produce accepted status. Every question requires 1-10 options; do not emit an open-text question without options. The UI provides an Other text answer alongside the declared options. Use payload {\"version\":1,\"title\":\"...\",\"questions\":[{\"id\":\"question_key\",\"prompt\":\"Question text\",\"selectionMode\":\"single\",\"required\":true,\"options\":[{\"id\":\"option_key\",\"label\":\"Option label\"}]}]}. Do not use question/options.value at the top level.",
3573
- "For create_interaction request_confirmation, use payload {\"version\":1,\"prompt\":\"Confirm this result?\",\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\"}. Do not use options arrays for request_confirmation.",
3574
- "For create_interaction request_checkbox_confirmation, use payload {\"version\":1,\"prompt\":\"Select approved items\",\"options\":[{\"id\":\"option_key\",\"label\":\"Option label\"}],\"minSelected\":1,\"maxSelected\":2,\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\"}.",
3596
+ "For create_interaction request_confirmation, follow the complete example above and do not use options arrays.",
3597
+ "For create_interaction request_checkbox_confirmation, use payload {\"version\":1,\"prompt\":\"Select approved items\",\"options\":[{\"id\":\"option_key\",\"label\":\"Option label\"}],\"minSelected\":1,\"maxSelected\":2,\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\",\"target\":{\"type\":\"custom\",\"key\":\"approved-items\",\"label\":\"待批准项目\",\"revisionId\":\"v1\"}}.",
3575
3598
  "For create_interaction suggest_tasks, use payload {\"version\":1,\"tasks\":[{\"clientKey\":\"task_key\",\"title\":\"Task title\",\"description\":\"Task description\",\"priority\":\"medium\"}]}.",
3576
3599
  "When a suggested task should start automatically after acceptance, set assigneeAgentId to an agent id from the current task context. Set assigneeUserId only when a known user must own the task. Omit both assignee fields only when the accepted task should remain intentionally unassigned in backlog. Never substitute a role name for an assignee id.",
3577
3600
  "An external input or human approval cannot exist only as free-text owner/next-step prose. Create a human-owned create_child_task with assigneeUserId and blockParentUntilDone=true (or another explicit interaction/dependency path), then keep the parent blocked or in_review until it resolves.",
@@ -3580,6 +3603,7 @@ function runtimeActionsInstruction(options = {}) {
3580
3603
  "upload_artifact actions do not accept required. Artifact required belongs only to top-level deliverables entries, not action objects.",
3581
3604
  "When a child artifact is promoted as a replacement for a parent artifact, run upload_artifact on the parent issue and include both sourceWorkProductId (the child artifact) and supersedesWorkProductId (the current parent artifact). Never claim the parent artifact was corrected without this durable lineage.",
3582
3605
  "For derived artifacts, set evidenceRole=derived, sourceWorkProductIds to every consumed input work product, and scenarioKeys to the unchanged source scenario set. For post-implementation review artifacts, set evidenceRole=review and sourceWorkProductIds to the exact current artifacts reviewed; stale or missing sources fail closed.",
3606
+ "When a child must consume current parent artifacts, set create_child_task.inputWorkProductIds to the exact parent work product UUIDs from task context. AMaster persists their attachment/SHA revision manifest and materializes only those required inputs under input-artifacts/. Do not assume parent attachments are implicitly copied into every child workspace.",
3583
3607
  "When creating a review child that must run after sibling implementation or governance tasks, express that ordering with blockedByClientKeys in the same create_child_task batch. Do not start a post-implementation review as an independent sibling without dependencies.",
3584
3608
  "For required deliverables, add top-level deliverables entries using {\"path\":\"relative/file\",\"required\":true}.",
3585
3609
  "update_parent accepts only status, comment, and force. Never put deliverables, artifacts, interactions, blockers, or child-task fields inside update_parent; keep top-level deliverables beside actions and use their dedicated action objects.",
@@ -3870,7 +3894,9 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
3870
3894
  const agentInstructions = renderAgentInstructionsBundle(asRecord(payload.agentInstructionsBundle));
3871
3895
  const runtimeAuth = commandRuntimeAuth(command);
3872
3896
  const hasRuntimeApi = Boolean(readString(runtimeAuth.apiUrl) && readString(runtimeAuth.apiKey));
3873
- const canOrchestrateOrganization = canRuntimeAgentOrchestrateOrganization(context);
3897
+ const authorizationEnvelope = runtimeActionAuthorizationEnvelope(command);
3898
+ const canOrchestrateOrganization = canRuntimeAgentOrchestrateOrganization(context)
3899
+ && authorizationEnvelope.allowedActionClasses.has("organization_mutation");
3874
3900
  const decompositionRequirement = commandLegacyTaskDecompositionRequirement(command);
3875
3901
  const explicitDecompositionRequired = decompositionRequirement.required
3876
3902
  && runtimeActionChildIssueSummaryEntries(command).length === 0;
@@ -3908,6 +3934,7 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
3908
3934
  explicitDecompositionRequired,
3909
3935
  artifactVerifierCommands: options.artifactVerifierCommands,
3910
3936
  actionValidatorCommand,
3937
+ authorizationEnvelope,
3911
3938
  }) : "",
3912
3939
  "",
3913
3940
  agentInstructions,
@@ -5755,6 +5782,97 @@ async function materializeIssueAttachments(config, command, workspace) {
5755
5782
  return materialized;
5756
5783
  }
5757
5784
 
5785
+ function safeArtifactInputSourceDir(entry, index) {
5786
+ const raw = readString(entry.sourceIssueIdentifier) ?? readString(entry.sourceIssueId) ?? `source-${index + 1}`;
5787
+ const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^\.+|\.+$/g, "").slice(0, 120);
5788
+ return safe && safe !== "." && safe !== ".." ? safe : `source-${index + 1}`;
5789
+ }
5790
+
5791
+ async function materializeRequiredArtifactInputs(config, command, workspace) {
5792
+ const runtimeAuth = commandRuntimeAuth(command);
5793
+ if (!readString(runtimeAuth.apiUrl) || !readString(runtimeAuth.apiKey)) return [];
5794
+ const context = asRecord(asRecord(command.payload).contextSnapshot);
5795
+ const issue = asRecord(context.paperclipIssue);
5796
+ const manifest = asRecord(issue.artifactInputManifest);
5797
+ if (Object.keys(manifest).length === 0) return [];
5798
+ if (manifest.version !== 1 || !Array.isArray(manifest.entries)) {
5799
+ throw new Error("artifact_input_manifest_invalid: expected version 1 entries array");
5800
+ }
5801
+
5802
+ const targetRoot = join(workspace.cwd, "input-artifacts");
5803
+ rmSync(targetRoot, { recursive: true, force: true });
5804
+ mkdirSync(targetRoot, { recursive: true });
5805
+ const usedPaths = new Set();
5806
+ const materialized = [];
5807
+ for (const [index, rawEntry] of manifest.entries.entries()) {
5808
+ const entry = asRecord(rawEntry);
5809
+ const workProductId = readString(entry.workProductId);
5810
+ const attachmentId = readString(entry.attachmentId);
5811
+ const sha256 = readString(entry.sha256);
5812
+ const contentPath = readString(entry.contentPath);
5813
+ const byteSize = readNumber(entry.byteSize, null);
5814
+ if (!workProductId || !attachmentId || !/^[a-f0-9]{64}$/i.test(sha256 ?? "") || !contentPath || byteSize === null) {
5815
+ throw new Error(`artifact_input_manifest_invalid: entry ${index} is incomplete`);
5816
+ }
5817
+ const expectedContentPath = `/api/attachments/${attachmentId}/content`;
5818
+ if (contentPath !== expectedContentPath) {
5819
+ throw new Error(`artifact_input_manifest_invalid: entry ${index} contentPath does not match attachmentId`);
5820
+ }
5821
+ const body = await runtimeApiBuffer(runtimeAuth, contentPath);
5822
+ const actualSha256 = createHash("sha256").update(body).digest("hex");
5823
+ if (body.byteLength !== byteSize || actualSha256 !== sha256) {
5824
+ throw new Error(
5825
+ `artifact_input_integrity_mismatch: workProductId=${workProductId} expectedBytes=${byteSize} actualBytes=${body.byteLength} expectedSha256=${sha256} actualSha256=${actualSha256}`,
5826
+ );
5827
+ }
5828
+
5829
+ const sourceDir = safeArtifactInputSourceDir(entry, index);
5830
+ let filename = safeMaterializedAttachmentFilename({
5831
+ id: attachmentId,
5832
+ originalFilename: readString(entry.originalFilename),
5833
+ }, index);
5834
+ let relativePath = join("input-artifacts", sourceDir, filename).split(/[\\/]+/).join("/");
5835
+ if (usedPaths.has(relativePath)) {
5836
+ const ext = extname(filename);
5837
+ const stem = ext ? filename.slice(0, -ext.length) : filename;
5838
+ filename = `${stem}-${index + 1}${ext}`;
5839
+ relativePath = join("input-artifacts", sourceDir, filename).split(/[\\/]+/).join("/");
5840
+ }
5841
+ usedPaths.add(relativePath);
5842
+ const targetPath = join(workspace.cwd, relativePath);
5843
+ mkdirSync(dirname(targetPath), { recursive: true });
5844
+ writeFileSync(targetPath, body);
5845
+ chmodSync(targetPath, 0o444);
5846
+ materialized.push({
5847
+ id: attachmentId,
5848
+ workProductId,
5849
+ sourceIssueId: readString(entry.sourceIssueId),
5850
+ sourceIssueIdentifier: readString(entry.sourceIssueIdentifier),
5851
+ name: readString(entry.originalFilename) ?? filename,
5852
+ path: targetPath,
5853
+ relativePath,
5854
+ contentType: readString(entry.contentType),
5855
+ byteSize: body.byteLength,
5856
+ sha256,
5857
+ contentPath,
5858
+ });
5859
+ }
5860
+
5861
+ const localManifest = {
5862
+ version: 1,
5863
+ entries: materialized.map(({ path: _path, relativePath, ...entry }) => ({ ...entry, path: relativePath })),
5864
+ };
5865
+ const manifestPath = join(targetRoot, "artifact-input-manifest.json");
5866
+ writeFileSync(manifestPath, JSON.stringify(localManifest, null, 2) + "\n");
5867
+ chmodSync(manifestPath, 0o444);
5868
+ updateWorkspaceManifest(workspaceManifestPath(workspace), { artifactInputManifest: localManifest });
5869
+ await ingestLog(config, command, "system", "info", `Materialized ${materialized.length} required artifact input(s) into the execution workspace`, {
5870
+ artifactInputCount: materialized.length,
5871
+ artifactInputs: localManifest.entries,
5872
+ });
5873
+ return materialized;
5874
+ }
5875
+
5758
5876
  function issueCheckpointDir(workspace) {
5759
5877
  return join(dirname(workspace.runDir), "checkpoint");
5760
5878
  }
@@ -6054,7 +6172,11 @@ function runtimeChildActionFingerprint(action, issueId, parentIssueId) {
6054
6172
  title,
6055
6173
  status: readActionStatus(action.status) ?? null,
6056
6174
  priority: readActionPriority(action.priority) ?? null,
6175
+ blockParentUntilDone: action.blockParentUntilDone === true || readActionStatus(action.status) === "blocked",
6057
6176
  parentClientKey: readString(action.parentClientKey) ?? null,
6177
+ inputWorkProductIds: Array.isArray(action.inputWorkProductIds)
6178
+ ? action.inputWorkProductIds.map(readString).filter(Boolean).sort()
6179
+ : [],
6058
6180
  blockedByClientKeys: Array.isArray(action.blockedByClientKeys)
6059
6181
  ? action.blockedByClientKeys.map(readString).filter(Boolean).sort()
6060
6182
  : [],
@@ -6075,7 +6197,9 @@ function runtimeAgentHireActionFingerprint(action, companyId, issueId, body) {
6075
6197
  adapterType: readString(body.adapterType) ?? null,
6076
6198
  sourceIssueId: readString(body.sourceIssueId) ?? null,
6077
6199
  sourceIssueIds: runtimeActionIssueIdList(body.sourceIssueIds).sort(),
6200
+ reportsTo: readString(body.reportsTo) ?? null,
6078
6201
  reportsToAgentClientKey: readString(action.reportsToAgentClientKey) ?? null,
6202
+ topLevel: action.topLevel === true,
6079
6203
  })}`;
6080
6204
  }
6081
6205
 
@@ -6233,7 +6357,7 @@ function runtimeActionAcceptedConfirmationContinuation(command) {
6233
6357
  if (context.acceptedConfirmationContinuation === true) return true;
6234
6358
  if (readString(context.wakeReason) === "request_confirmation_accepted") return true;
6235
6359
  if (
6236
- readString(context.interactionKind) === "request_confirmation" &&
6360
+ ["request_confirmation", "request_checkbox_confirmation"].includes(readString(context.interactionKind) ?? "") &&
6237
6361
  readString(context.interactionStatus) === "accepted"
6238
6362
  ) {
6239
6363
  return true;
@@ -6242,7 +6366,7 @@ function runtimeActionAcceptedConfirmationContinuation(command) {
6242
6366
  const interactions = Array.isArray(wake.interactions) ? wake.interactions : [];
6243
6367
  return interactions.some((entry) => {
6244
6368
  const interaction = asRecord(entry);
6245
- return readString(interaction.kind) === "request_confirmation" &&
6369
+ return ["request_confirmation", "request_checkbox_confirmation"].includes(readString(interaction.kind) ?? "") &&
6246
6370
  readString(interaction.status) === "accepted";
6247
6371
  });
6248
6372
  }
@@ -6934,16 +7058,17 @@ const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
6934
7058
  "title", "description", "status", "priority", "assigneeAgentId", "assigneeUserId",
6935
7059
  "assigneeAgentClientKey", "agentClientKey", "blockParentUntilDone", "executeBeforeConfirmation",
6936
7060
  "clientKey", "idempotencyKey", "parentClientKey", "blockedByIssueIds", "blockedByClientKeys", "create_child_task",
7061
+ "inputWorkProductIds",
6937
7062
  ])],
6938
7063
  ["agent_hire", new Set([
6939
7064
  "clientKey", "idempotencyKey", "key", "companyId", "role", "title", "icon", "reportsTo",
6940
- "reportsToAgentClientKey", "capabilities", "desiredSkills", "adapterType", "adapterConfig",
7065
+ "reportsToAgentClientKey", "topLevel", "capabilities", "desiredSkills", "adapterType", "adapterConfig",
6941
7066
  "instructionsBundle", "runtimeConfig", "defaultEnvironmentId", "budgetMonthlyCents", "permissions",
6942
7067
  "metadata", "sourceIssueId", "sourceIssueIds", "executeBeforeConfirmation", "agent_hire", "agent_hire/create_agent",
6943
7068
  ])],
6944
7069
  ["create_agent", new Set([
6945
7070
  "clientKey", "idempotencyKey", "key", "companyId", "role", "title", "icon", "reportsTo",
6946
- "reportsToAgentClientKey", "capabilities", "desiredSkills", "adapterType", "adapterConfig",
7071
+ "reportsToAgentClientKey", "topLevel", "capabilities", "desiredSkills", "adapterType", "adapterConfig",
6947
7072
  "instructionsBundle", "runtimeConfig", "defaultEnvironmentId", "budgetMonthlyCents", "permissions",
6948
7073
  "metadata", "sourceIssueId", "sourceIssueIds", "executeBeforeConfirmation", "create_agent",
6949
7074
  ])],
@@ -6954,6 +7079,9 @@ const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
6954
7079
  "sourceWorkProductId", "sourceWorkProductIds", "supersedesWorkProductId", "evidenceRole", "scenarioKeys",
6955
7080
  "idempotencyKey", "upload_artifact",
6956
7081
  ])],
7082
+ ["upsert_document", new Set([
7083
+ "key", "title", "body", "format", "changeSummary", "baseRevisionId", "upsert_document",
7084
+ ])],
6957
7085
  ["create_interaction", new Set([
6958
7086
  "kind", "continuationPolicy", "title", "summary", "idempotencyKey", "interaction", "version",
6959
7087
  "prompt", "question", "body", "content", "text", "message", "description", "acceptLabel",
@@ -6990,8 +7118,11 @@ function assertNoUnsupportedRuntimeActionFields(action, actionIndex) {
6990
7118
  if (RUNTIME_ACTION_COMMON_FIELDS.has(field) || allowed.has(field)) continue;
6991
7119
  const fieldPath = `actions[${actionIndex}].${field}`;
6992
7120
  const receivedType = runtimeActionReceivedType(value);
7121
+ const managerFieldHint = ["agent_hire", "create_agent"].includes(type ?? "") && field === "reportsToAgentId"
7122
+ ? "; reportsToAgentId is unsupported; use reportsTo for an existing manager UUID or reportsToAgentClientKey for a manager hired in the same batch"
7123
+ : "";
6993
7124
  const err = runtimeActionValidationError(
6994
- `${type} action has unsupported field ${field} at ${fieldPath}; received ${receivedType}`,
7125
+ `${type} action has unsupported field ${field} at ${fieldPath}; received ${receivedType}${managerFieldHint}`,
6995
7126
  );
6996
7127
  err.runtimeActionFieldPath = fieldPath;
6997
7128
  err.runtimeActionReceivedType = receivedType;
@@ -7002,12 +7133,13 @@ function assertNoUnsupportedRuntimeActionFields(action, actionIndex) {
7002
7133
  const RUNTIME_ACTION_BOOLEAN_FIELDS = new Set([
7003
7134
  "blockParentUntilDone", "executeBeforeConfirmation", "createWorkProduct", "isPrimary",
7004
7135
  "force", "markAutoDerived", "rejectRequiresReason", "allowDeclineReason", "supersedeOnUserComment",
7005
- "required",
7136
+ "required", "topLevel",
7006
7137
  ]);
7007
7138
  const RUNTIME_ACTION_NUMBER_FIELDS = new Set(["budgetMonthlyCents", "minSelected", "maxSelected", "version"]);
7008
7139
  const RUNTIME_ACTION_ARRAY_FIELDS = new Set([
7009
7140
  "blockedByIssueIds", "blockedByClientKeys", "desiredSkills", "sourceIssueIds", "artifactRefs", "tasks",
7010
7141
  "options", "defaultSelectedOptionIds", "questions", "sourceWorkProductIds", "scenarioKeys",
7142
+ "inputWorkProductIds",
7011
7143
  ]);
7012
7144
  const RUNTIME_ACTION_OBJECT_FIELDS = new Set([
7013
7145
  "adapterConfig", "instructionsBundle", "runtimeConfig", "permissions", "metadata", "target",
@@ -7288,6 +7420,29 @@ function assertNoAmbiguousApprovalGatedSideEffects(actions) {
7288
7420
  throw error;
7289
7421
  }
7290
7422
 
7423
+ function assertDelegatedParentWaitDisposition(actions) {
7424
+ const closesIndependentParentScope = actions.some((action) => {
7425
+ const fields = runtimeActionBusinessFields(action);
7426
+ if (readRuntimeActionType(fields) !== "update_parent") return false;
7427
+ return ["done", "cancelled"].includes(readActionStatus(fields.status) ?? "");
7428
+ });
7429
+ for (const [actionIndex, action] of actions.entries()) {
7430
+ const fields = runtimeActionBusinessFields(action);
7431
+ if (readRuntimeActionType(fields) !== "create_child_task") continue;
7432
+ if (readString(fields.parentClientKey)) continue;
7433
+ const status = readActionStatus(fields.status) ?? "todo";
7434
+ if (!["todo", "in_progress"].includes(status)) continue;
7435
+ if (fields.blockParentUntilDone === true || closesIndependentParentScope) continue;
7436
+ const err = runtimeActionValidationError(
7437
+ "delegated_parent_wait_required: root executable child tasks require blockParentUntilDone true, or the same batch must close the parent's independent scope with update_parent done/cancelled",
7438
+ );
7439
+ err.runtimeActionType = "create_child_task";
7440
+ err.runtimeActionFieldPath = `actions[${actionIndex}].blockParentUntilDone`;
7441
+ err.runtimeActionReason = err.message;
7442
+ throw err;
7443
+ }
7444
+ }
7445
+
7291
7446
  function assertExplicitTaskDecomposition(command, actions) {
7292
7447
  const requirement = commandLegacyTaskDecompositionRequirement(command);
7293
7448
  if (!requirement.required) return;
@@ -7358,14 +7513,34 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
7358
7513
  );
7359
7514
  }
7360
7515
  }
7516
+ if (fields.inputWorkProductIds !== undefined) {
7517
+ const seen = new Set();
7518
+ for (const [index, rawWorkProductId] of fields.inputWorkProductIds.entries()) {
7519
+ const workProductId = readString(rawWorkProductId);
7520
+ assertRuntimeActionValid(
7521
+ workProductId && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(workProductId),
7522
+ `create_child_task inputWorkProductIds[${index}] must be a UUID`,
7523
+ );
7524
+ assertRuntimeActionValid(
7525
+ !seen.has(workProductId),
7526
+ `create_child_task inputWorkProductIds[${index}] duplicates ${workProductId}`,
7527
+ );
7528
+ seen.add(workProductId);
7529
+ }
7530
+ }
7361
7531
  return;
7362
7532
  }
7363
7533
  if (["agent_hire", "create_agent"].includes(type ?? "")) {
7364
7534
  assertNoPlaceholderRuntimeActionText(fields, ["name", "role", "title", "instructionsBundle"]);
7365
7535
  assertRuntimeActionValid(readBoundedActionString(fields, "name", 240), `${type} action requires name`);
7536
+ const reportingDispositionCount = [
7537
+ Boolean(readString(fields.reportsTo)),
7538
+ Boolean(readString(fields.reportsToAgentClientKey)),
7539
+ fields.topLevel === true,
7540
+ ].filter(Boolean).length;
7366
7541
  assertRuntimeActionValid(
7367
- !readString(fields.reportsTo) || !readString(fields.reportsToAgentClientKey),
7368
- `${type} action cannot combine reportsTo with reportsToAgentClientKey`,
7542
+ reportingDispositionCount === 1,
7543
+ `${type} action requires exactly one of reportsTo, reportsToAgentClientKey, or topLevel true`,
7369
7544
  );
7370
7545
  return;
7371
7546
  }
@@ -7413,6 +7588,20 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
7413
7588
  resolveWorkspaceArtifactPath(fields, { cwd: preflightContext.cwd });
7414
7589
  return;
7415
7590
  }
7591
+ if (type === "upsert_document") {
7592
+ assertNoPlaceholderRuntimeActionText(fields, ["key", "title", "body", "changeSummary"]);
7593
+ const key = readBoundedActionString(fields, "key", 64);
7594
+ const body = readBoundedActionString(fields, "body", 524_288);
7595
+ assertRuntimeActionValid(key && /^[a-z0-9][a-z0-9_-]*$/.test(key), "upsert_document key must use lowercase letters, numbers, _ or -");
7596
+ assertRuntimeActionValid(body, "upsert_document action requires body");
7597
+ assertRuntimeActionValid(fields.format === "markdown", "upsert_document format must be markdown");
7598
+ const baseRevisionId = readString(fields.baseRevisionId);
7599
+ assertRuntimeActionValid(
7600
+ !baseRevisionId || /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(baseRevisionId),
7601
+ "upsert_document baseRevisionId must be a UUID",
7602
+ );
7603
+ return;
7604
+ }
7416
7605
  if (type === "create_interaction") {
7417
7606
  assertNoPlaceholderRuntimeActionText(fields, ["title", "summary", "payload"]);
7418
7607
  assertCanonicalCreateInteractionShape(fields);
@@ -7431,6 +7620,17 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
7431
7620
  `create_interaction.${kind} action requires payload; expected shape is available in runtimeActionDiagnostics.expectedShape`,
7432
7621
  );
7433
7622
  assertRuntimeCreateInteractionPayload(kind, payload);
7623
+ const confirmationTarget = asRecord(payload.target);
7624
+ if (
7625
+ preflightContext.acceptedConfirmationContinuation &&
7626
+ ["request_confirmation", "request_checkbox_confirmation"].includes(kind) &&
7627
+ (!readString(confirmationTarget.type) || !readString(confirmationTarget.key))
7628
+ ) {
7629
+ assertRuntimeActionValid(
7630
+ false,
7631
+ "accepted_confirmation_decision_target_required: an accepted confirmation continuation requires payload.target.type and a non-empty payload.target.key so the server can identify the decision; use a new target revision for genuinely changed content or finish the accepted work",
7632
+ );
7633
+ }
7434
7634
  preflightContext.createdReviewPathForInReview = true;
7435
7635
  return;
7436
7636
  }
@@ -7486,6 +7686,7 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
7486
7686
 
7487
7687
  function preflightRuntimeActionBatch(actions, options = {}) {
7488
7688
  const actionIndexByObject = new Map(actions.map((action, index) => [action, index]));
7689
+ assertDelegatedParentWaitDisposition(actions);
7489
7690
  const orderedActions = orderRuntimeActionsByClientKeyDependencies(actions);
7490
7691
  assertRuntimeActionsAllowedByContinuationScope(orderedActions, options.continuationScope);
7491
7692
  assertNoAmbiguousApprovalGatedSideEffects(orderedActions);
@@ -7494,6 +7695,7 @@ function preflightRuntimeActionBatch(actions, options = {}) {
7494
7695
  createdRootBlockingChildForInReview: false,
7495
7696
  createdHumanOwnedInputPath: false,
7496
7697
  answeredQuestionContinuation: options.answeredQuestionContinuation === true,
7698
+ acceptedConfirmationContinuation: options.acceptedConfirmationContinuation === true,
7497
7699
  allowedContentTypes: Array.isArray(options.allowedContentTypes) ? options.allowedContentTypes : [],
7498
7700
  cwd: readString(options.cwd),
7499
7701
  finalCommentBodies: [],
@@ -7596,6 +7798,7 @@ const SINGLE_KEY_RUNTIME_ACTION_TYPES = new Set([
7596
7798
  "create_child_task",
7597
7799
  "create_interaction",
7598
7800
  "create_routine",
7801
+ "upsert_document",
7599
7802
  "update_company_profile",
7600
7803
  "update_parent",
7601
7804
  "upload_artifact",
@@ -7669,6 +7872,9 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7669
7872
  assigneeAgentId: runtimeChildAssigneeAgentId(fields, runtimeAuth, command, actionContext),
7670
7873
  assigneeUserId: readString(fields.assigneeUserId),
7671
7874
  blockedByIssueIds: runtimeActionBlockedByIssueIds(fields, actionContext),
7875
+ ...(Array.isArray(fields.inputWorkProductIds)
7876
+ ? { inputWorkProductIds: fields.inputWorkProductIds.map(readString).filter(Boolean) }
7877
+ : {}),
7672
7878
  blockParentUntilDone,
7673
7879
  originKind: "amaster_runtime_child_task",
7674
7880
  originId: issueId,
@@ -7755,6 +7961,11 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7755
7961
  `runtime_action_postcondition_failed: agent reportsTo mismatch; expected ${reportsTo}, received ${readString(agent.reportsTo) ?? "missing"}`,
7756
7962
  );
7757
7963
  }
7964
+ if (fields.topLevel === true && readString(agent.reportsTo)) {
7965
+ throw new Error(
7966
+ `runtime_action_postcondition_failed: top-level agent reportsTo mismatch; expected missing, received ${readString(agent.reportsTo)}`,
7967
+ );
7968
+ }
7758
7969
  if (agentId && clientKey) actionContext.agentIdByClientKey.set(clientKey, agentId);
7759
7970
  return {
7760
7971
  type,
@@ -7767,6 +7978,8 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7767
7978
  approvalStatus: readString(approval.status),
7768
7979
  sourceIssueId: readString(body.sourceIssueId) ?? null,
7769
7980
  sourceIssueIds: runtimeActionIssueIdList(body.sourceIssueIds),
7981
+ reportsTo: reportsTo ?? null,
7982
+ topLevel: fields.topLevel === true,
7770
7983
  ...(clientKey ? { clientKey } : {}),
7771
7984
  reusedExisting: asRecord(created).reusedExisting === true,
7772
7985
  };
@@ -7824,12 +8037,22 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7824
8037
  const existingWorkProduct = existingExplicitArtifactUploadMatch(filePath, title, existingWorkProducts, fields);
7825
8038
  if (existingWorkProduct) {
7826
8039
  const metadata = asRecord(existingWorkProduct.metadata);
8040
+ const existingWorkProductId = readString(existingWorkProduct.id);
8041
+ const existingAttachmentId = readString(metadata.attachmentId);
8042
+ if (!existingWorkProductId || !existingAttachmentId) {
8043
+ throw new Error("runtime_action_postcondition_failed: reused artifact is missing work product or attachment identity");
8044
+ }
8045
+ await verifyUploadedArtifactPostcondition(runtimeAuth, issueId, {
8046
+ attachmentId: existingAttachmentId,
8047
+ workProductId: existingWorkProductId,
8048
+ sha256,
8049
+ }, fields);
7827
8050
  return {
7828
8051
  type,
7829
8052
  ...(originalType && originalType !== type ? { originalType } : {}),
7830
8053
  path: filePath,
7831
- attachmentId: readString(metadata.attachmentId),
7832
- workProductId: readString(existingWorkProduct.id),
8054
+ attachmentId: existingAttachmentId,
8055
+ workProductId: existingWorkProductId,
7833
8056
  sha256: readString(metadata.sha256) ?? sha256,
7834
8057
  byteSize: readNumber(metadata.byteSize, fileBytes.byteLength),
7835
8058
  originalFilename: readString(metadata.originalFilename) ?? filename,
@@ -7851,11 +8074,12 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7851
8074
  if (!attachmentId) throw new Error("upload_artifact attachment response did not include id");
7852
8075
 
7853
8076
  let workProduct = null;
7854
- if (fields.createWorkProduct !== false) {
7855
- const contentPath = readString(attachment.contentPath) ?? `/api/attachments/${attachmentId}/content`;
7856
- const openPath = readString(attachment.openPath) ?? contentPath;
7857
- const downloadPath = readString(attachment.downloadPath) ?? `${contentPath}?download=1`;
7858
- workProduct = asRecord(await runtimeApiJson(runtimeAuth, "POST", `/api/issues/${encodeURIComponent(issueId)}/work-products`, {
8077
+ try {
8078
+ if (fields.createWorkProduct !== false) {
8079
+ const contentPath = readString(attachment.contentPath) ?? `/api/attachments/${attachmentId}/content`;
8080
+ const openPath = readString(attachment.openPath) ?? contentPath;
8081
+ const downloadPath = readString(attachment.downloadPath) ?? `${contentPath}?download=1`;
8082
+ workProduct = asRecord(await runtimeApiJson(runtimeAuth, "POST", `/api/issues/${encodeURIComponent(issueId)}/work-products`, {
7859
8083
  type: "artifact",
7860
8084
  provider: readString(fields.provider) ?? "paperclip",
7861
8085
  title,
@@ -7880,10 +8104,20 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7880
8104
  ...(readString(fields.evidenceRole) ? { evidenceRole: readString(fields.evidenceRole) } : {}),
7881
8105
  ...(Array.isArray(fields.scenarioKeys) ? { scenarioKeys: fields.scenarioKeys.map(readString).filter(Boolean) } : {}),
7882
8106
  },
7883
- }));
7884
- if (!readString(workProduct.id)) {
7885
- throw new Error("runtime_action_postcondition_failed: artifact work product response did not include id");
8107
+ }));
8108
+ const workProductId = readString(workProduct.id);
8109
+ if (!workProductId) {
8110
+ throw new Error("runtime_action_postcondition_failed: artifact work product response did not include id");
8111
+ }
8112
+ await verifyUploadedArtifactPostcondition(runtimeAuth, issueId, {
8113
+ attachmentId,
8114
+ workProductId,
8115
+ sha256: readString(attachment.sha256) ?? sha256,
8116
+ }, fields);
7886
8117
  }
8118
+ } catch (err) {
8119
+ await cleanupFailedArtifactUpload(config, command, runtimeAuth, attachmentId, readString(workProduct?.id), err);
8120
+ throw err;
7887
8121
  }
7888
8122
 
7889
8123
  return {
@@ -7941,6 +8175,37 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
7941
8175
  };
7942
8176
  }
7943
8177
 
8178
+ if (type === "upsert_document") {
8179
+ assertNoPlaceholderRuntimeActionText(fields, ["key", "title", "body", "changeSummary"]);
8180
+ const key = readBoundedActionString(fields, "key", 64);
8181
+ if (!key || !/^[a-z0-9][a-z0-9_-]*$/.test(key)) {
8182
+ throw new Error("upsert_document key must use lowercase letters, numbers, _ or -");
8183
+ }
8184
+ const body = readBoundedActionString(fields, "body", 524_288);
8185
+ if (!body) throw new Error("upsert_document action requires body");
8186
+ if (fields.format !== "markdown") throw new Error("upsert_document format must be markdown");
8187
+ const document = asRecord(await runtimeApiJson(
8188
+ runtimeAuth,
8189
+ "PUT",
8190
+ `/api/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}`,
8191
+ {
8192
+ title: readBoundedActionString(fields, "title", 200) ?? null,
8193
+ format: "markdown",
8194
+ body,
8195
+ changeSummary: readBoundedActionString(fields, "changeSummary", 500) ?? null,
8196
+ baseRevisionId: readString(fields.baseRevisionId) ?? null,
8197
+ },
8198
+ ));
8199
+ return {
8200
+ type,
8201
+ ...(originalType && originalType !== type ? { originalType } : {}),
8202
+ issueId,
8203
+ key,
8204
+ documentId: readString(document.id),
8205
+ revisionNumber: typeof document.latestRevisionNumber === "number" ? document.latestRevisionNumber : undefined,
8206
+ };
8207
+ }
8208
+
7944
8209
  if (type === "update_parent") {
7945
8210
  assertNoPlaceholderRuntimeActionText(fields, ["comment"]);
7946
8211
  const patch = {};
@@ -8396,6 +8661,16 @@ function runtimeActionExpectedShape(actionType, actionKind) {
8396
8661
  if (actionType === "upload_artifact") {
8397
8662
  return { type: "upload_artifact", path: "relative/path.ext", title: "Artifact title", summary: "Artifact summary" };
8398
8663
  }
8664
+ if (actionType === "upsert_document") {
8665
+ return {
8666
+ type: "upsert_document",
8667
+ key: "business-brief",
8668
+ title: "2026-07-16 每日经营简报",
8669
+ body: "# 每日经营简报\n\n经营摘要。",
8670
+ format: "markdown",
8671
+ changeSummary: "生成每日经营简报",
8672
+ };
8673
+ }
8399
8674
  if (actionType === "agent_hire" || actionType === "create_agent") {
8400
8675
  return {
8401
8676
  type: actionType,
@@ -8613,6 +8888,27 @@ function piOutputUsageMetadataMissing(parsed) {
8613
8888
  return totalTokens <= 0;
8614
8889
  }
8615
8890
 
8891
+ function runtimeBusinessDisposition(runtimeActions, inferredArtifactUploads) {
8892
+ const results = Array.isArray(runtimeActions?.results)
8893
+ ? runtimeActions.results.map(asRecord).filter((result) => !isSkippedRuntimeActionResult(result))
8894
+ : [];
8895
+ const terminalUpdate = results.find((result) =>
8896
+ readString(result.type) === "update_parent" &&
8897
+ ["done", "cancelled", "blocked", "in_review"].includes(readString(result.status) ?? ""));
8898
+ const terminalStatus = readString(terminalUpdate?.status);
8899
+ if (["done", "cancelled"].includes(terminalStatus ?? "")) {
8900
+ return { state: "completed", reason: `parent_${terminalStatus}` };
8901
+ }
8902
+ if (terminalStatus === "blocked") return { state: "blocked", reason: "parent_blocked" };
8903
+ if (terminalStatus === "in_review" || results.some((result) => readString(result.type) === "create_interaction")) {
8904
+ return { state: "waiting", reason: "review_or_interaction_path_recorded" };
8905
+ }
8906
+ if (results.length > 0 || readNumber(inferredArtifactUploads?.appliedCount, 0) > 0) {
8907
+ return { state: "needs_followup", reason: "durable_progress_without_terminal_disposition" };
8908
+ }
8909
+ return { state: "missing", reason: "no_durable_action_or_live_path" };
8910
+ }
8911
+
8616
8912
  function piCompletionOutputStopped(parsed, execution) {
8617
8913
  return execution?.timedOut !== true
8618
8914
  && execution?.signal === "SIGTERM"
@@ -8920,9 +9216,11 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
8920
9216
  }
8921
9217
  let actions = orderRuntimeActionsForGovernance([...generatedUploadActions, ...rawActions]);
8922
9218
  const answeredQuestionContinuation = runtimeActionAnsweredQuestionContinuation(command);
9219
+ const acceptedConfirmationContinuation = runtimeActionAcceptedConfirmationContinuation(command);
8923
9220
  try {
8924
9221
  actions = preflightRuntimeActionBatch(actions, {
8925
9222
  answeredQuestionContinuation,
9223
+ acceptedConfirmationContinuation,
8926
9224
  allowedContentTypes,
8927
9225
  continuationScope,
8928
9226
  cwd,
@@ -8960,7 +9258,7 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
8960
9258
  createdChildIssueIdByFingerprint: new Map(),
8961
9259
  completedChildIssuesByTitle: runtimeActionCompletedChildIssueMap(command),
8962
9260
  createdReviewPathForInReview: false,
8963
- acceptedConfirmationContinuation: runtimeActionAcceptedConfirmationContinuation(command),
9261
+ acceptedConfirmationContinuation,
8964
9262
  answeredQuestionContinuation,
8965
9263
  finalCommentBodies: [],
8966
9264
  };
@@ -9071,6 +9369,72 @@ async function listExistingIssueWorkProducts(runtimeAuth, issueId) {
9071
9369
  }
9072
9370
  }
9073
9371
 
9372
+ async function verifyUploadedArtifactPostcondition(runtimeAuth, issueId, expected, fields) {
9373
+ const [workProducts, attachments] = await Promise.all([
9374
+ runtimeApiJson(runtimeAuth, "GET", `/api/issues/${encodeURIComponent(issueId)}/work-products`),
9375
+ runtimeApiJson(runtimeAuth, "GET", `/api/issues/${encodeURIComponent(issueId)}/attachments`),
9376
+ ]);
9377
+ const workProduct = Array.isArray(workProducts)
9378
+ ? workProducts.map(asRecord).find((entry) => readString(entry.id) === expected.workProductId)
9379
+ : null;
9380
+ const attachment = Array.isArray(attachments)
9381
+ ? attachments.map(asRecord).find((entry) => readString(entry.id) === expected.attachmentId)
9382
+ : null;
9383
+ const metadata = asRecord(workProduct?.metadata);
9384
+ const expectedSourceIds = Array.isArray(fields.sourceWorkProductIds)
9385
+ ? fields.sourceWorkProductIds.map(readString).filter(Boolean).sort()
9386
+ : [];
9387
+ const persistedSourceIds = Array.isArray(metadata.sourceWorkProductIds)
9388
+ ? metadata.sourceWorkProductIds.map(readString).filter(Boolean).sort()
9389
+ : [];
9390
+ const expectedScenarioKeys = Array.isArray(fields.scenarioKeys)
9391
+ ? fields.scenarioKeys.map(readString).filter(Boolean).sort()
9392
+ : [];
9393
+ const persistedScenarioKeys = Array.isArray(metadata.scenarioKeys)
9394
+ ? metadata.scenarioKeys.map(readString).filter(Boolean).sort()
9395
+ : [];
9396
+ const failures = [
9397
+ !workProduct ? "work product missing" : null,
9398
+ !attachment ? "attachment missing" : null,
9399
+ readString(metadata.attachmentId) !== expected.attachmentId ? "work product attachmentId mismatch" : null,
9400
+ readString(metadata.sha256) !== expected.sha256 ? "work product SHA mismatch" : null,
9401
+ readString(attachment?.sha256) !== expected.sha256 ? "attachment SHA mismatch" : null,
9402
+ readString(metadata.sourceWorkProductId) !== readString(fields.sourceWorkProductId) ? "sourceWorkProductId mismatch" : null,
9403
+ readString(metadata.supersedesWorkProductId) !== readString(fields.supersedesWorkProductId) ? "supersedesWorkProductId mismatch" : null,
9404
+ readString(metadata.evidenceRole) !== readString(fields.evidenceRole) ? "evidenceRole mismatch" : null,
9405
+ JSON.stringify(persistedSourceIds) !== JSON.stringify(expectedSourceIds) ? "sourceWorkProductIds mismatch" : null,
9406
+ JSON.stringify(persistedScenarioKeys) !== JSON.stringify(expectedScenarioKeys) ? "scenarioKeys mismatch" : null,
9407
+ ].filter(Boolean);
9408
+ if (failures.length > 0) {
9409
+ throw new Error(`runtime_action_postcondition_failed: artifact upload readback failed: ${failures.join("; ")}`);
9410
+ }
9411
+ }
9412
+
9413
+ async function cleanupFailedArtifactUpload(config, command, runtimeAuth, attachmentId, workProductId, cause) {
9414
+ const cleanupFailures = [];
9415
+ if (workProductId) {
9416
+ try {
9417
+ await runtimeApiJson(runtimeAuth, "DELETE", `/api/work-products/${encodeURIComponent(workProductId)}`);
9418
+ } catch (err) {
9419
+ cleanupFailures.push(`workProduct=${err instanceof Error ? err.message : String(err)}`);
9420
+ }
9421
+ }
9422
+ try {
9423
+ await runtimeApiJson(runtimeAuth, "DELETE", `/api/attachments/${encodeURIComponent(attachmentId)}`);
9424
+ } catch (err) {
9425
+ cleanupFailures.push(`attachment=${err instanceof Error ? err.message : String(err)}`);
9426
+ }
9427
+ await ingestLog(config, command, "system", "error", "Artifact upload postcondition failed", {
9428
+ attachmentId,
9429
+ workProductId: workProductId ?? null,
9430
+ cause: cause instanceof Error ? cause.message : String(cause),
9431
+ cleanupFailures,
9432
+ });
9433
+ if (cleanupFailures.length > 0) {
9434
+ throw new Error(`${cause instanceof Error ? cause.message : String(cause)}; cleanup failed: ${cleanupFailures.join("; ")}`);
9435
+ }
9436
+ }
9437
+
9074
9438
  function existingArtifactUploadMatches(artifact, existingWorkProducts) {
9075
9439
  const filename = basename(artifact.path);
9076
9440
  const byteSize = statSync(artifact.path).size;
@@ -9245,9 +9609,10 @@ async function executeRunCommand(config, command) {
9245
9609
  });
9246
9610
  }
9247
9611
  await materializeIssueCheckpoint(config, command, workspace);
9248
- let materializedAttachments = [];
9612
+ const requiredArtifactInputs = await materializeRequiredArtifactInputs(config, command, workspace);
9613
+ let materializedAttachments = requiredArtifactInputs;
9249
9614
  try {
9250
- materializedAttachments = await materializeIssueAttachments(config, command, workspace);
9615
+ materializedAttachments = [...requiredArtifactInputs, ...await materializeIssueAttachments(config, command, workspace)];
9251
9616
  } catch (err) {
9252
9617
  await ingestLog(config, command, "system", "warn", `Failed to materialize issue attachments: ${err instanceof Error ? err.message : String(err)}`, {
9253
9618
  error: err instanceof Error ? err.message : String(err),
@@ -9472,6 +9837,7 @@ async function executeRunCommand(config, command) {
9472
9837
  ? "Executor cancelled by AMaster control plane"
9473
9838
  : execution.spawnError ?? parsedErrorMessage ?? runtimeActionError ?? inferredArtifactError ?? (succeeded ? null : `Executor exited with code ${execution.exitCode ?? "unknown"}`);
9474
9839
  const artifactUploadSummary = runtimeArtifactUploadSummary(runtimeActions, inferredArtifactUploads);
9840
+ const businessDisposition = runtimeBusinessDisposition(runtimeActions, inferredArtifactUploads);
9475
9841
  const costUsage = parsedCostUsage(parsed.usage);
9476
9842
  const result = {
9477
9843
  executorKind: executor.kind,
@@ -9542,6 +9908,7 @@ async function executeRunCommand(config, command) {
9542
9908
  ...(runtimeActions ? { runtimeActions } : {}),
9543
9909
  ...(inferredArtifactUploads ? { inferredArtifactUploads } : {}),
9544
9910
  ...(artifactUploadSummary ? { artifactUploadSummary } : {}),
9911
+ businessDisposition,
9545
9912
  };
9546
9913
  const completion = await completeCommand(config, command, cancelled ? "cancelled" : succeeded ? "succeeded" : "failed", result, error ?? undefined);
9547
9914
  if (!completion) {
@@ -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.7";
8
+ const CONNECTOR_VERSION = "0.1.0-beta.9";
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.7",
3
+ "version": "0.1.0-beta.9",
4
4
  "description": "AMaster Employee runtime connector CLI and daemon",
5
5
  "license": "MIT",
6
6
  "type": "module",