@amaster.ai/employee-runtime-connector 0.1.0-beta.6 → 0.1.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/amaster-runtime-daemon.mjs +1029 -77
- package/dist/amaster-runtime.mjs +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
2338
|
+
const CONNECTOR_VERSION = "0.1.0-beta.8";
|
|
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(", ");
|
|
@@ -3527,8 +3539,13 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3527
3539
|
const artifactVerifierCommands = Array.isArray(options.artifactVerifierCommands)
|
|
3528
3540
|
? options.artifactVerifierCommands.map(readString).filter(Boolean)
|
|
3529
3541
|
: [];
|
|
3542
|
+
const actionValidatorCommand = readString(options.actionValidatorCommand);
|
|
3530
3543
|
return [
|
|
3531
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.",
|
|
3532
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.",
|
|
3533
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.",
|
|
3534
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.",
|
|
@@ -3537,12 +3554,17 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3537
3554
|
"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.",
|
|
3538
3555
|
"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.",
|
|
3539
3556
|
"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.",
|
|
3557
|
+
"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.",
|
|
3558
|
+
"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.",
|
|
3559
|
+
"For same-batch relationships, assign a unique clientKey to each agent_hire or create_child_task producer. Use reportsToAgentClientKey for a new agent's manager, assigneeAgentClientKey for a child owner, parentClientKey for a nested child, and blockedByClientKeys for first-class child blockers. Use blockedByIssueIds only when a new child depends on existing issue UUIDs supplied by the current task context; connector preflight checks UUID shape and the server validates same-company existence. References may point forward within the same batch; every referenced key must resolve to the correct resource kind in that batch.",
|
|
3540
3560
|
options.explicitDecompositionRequired
|
|
3541
|
-
? "This standard task explicitly requires child task decomposition. The runtime action batch must include create_child_task or one suggest_tasks interaction unless child tasks already exist. A checklist in a comment or artifact is not decomposition. Create independently executable internal preparation tasks now.
|
|
3561
|
+
? "This standard task explicitly requires child task decomposition. The runtime action batch must include create_child_task or one suggest_tasks interaction unless child tasks already exist. A checklist in a comment or artifact is not decomposition. Create independently executable internal preparation tasks now. Use backlog for recorded work that should not start now, and use suggest_tasks for work that must wait for human approval. When a pending interaction is created, keep the parent in_review rather than in_progress or done."
|
|
3542
3562
|
: "",
|
|
3543
|
-
"If child tasks must wait for a human confirmation, use one suggest_tasks interaction so AMaster creates and wakes the selected tasks only after acceptance. A batch that combines executable create_child_task actions with a pending interaction is rejected before mutation unless every such child is backlog or explicitly sets executeBeforeConfirmation true.",
|
|
3544
3563
|
options.canOrchestrateOrganization
|
|
3545
|
-
? "
|
|
3564
|
+
? "Pending-interaction batch gate: before emitting JSON, scan every agent_hire and create_child_task. Every co-batched hire must independently set executeBeforeConfirmation true; every create_child_task must independently be backlog, set executeBeforeConfirmation true, or move into suggest_tasks. blockParentUntilDone=false and a flagged assignee hire do not exempt a child. Otherwise move the hires to a later continuation and use suggest_tasks for work that must wait for acceptance. This applies to every pending interaction, including budget or business approval."
|
|
3565
|
+
: "Pending-interaction batch gate: before emitting JSON, scan every create_child_task. Every create_child_task must independently be backlog, set executeBeforeConfirmation true, or move into suggest_tasks. blockParentUntilDone=false does not exempt a child. Use suggest_tasks for work that must wait for acceptance.",
|
|
3566
|
+
options.canOrchestrateOrganization
|
|
3567
|
+
? "For AMaster organization-building requests, such as creating other roles, hiring digital employees, building the first team, or assigning work to roles, do not stop at a strategy document. Include agent_hire for each needed role and create_child_task for that role's first concrete assignment. agent_hire already creates the native pending hire approval; do not also ask whether to approve that hire in a create_interaction action in the same batch. If approval must happen before any hire record exists, create only a proposal interaction and hire in a later continuation. These organization batches must include add_comment or update_parent.comment explaining what was created, requested, assigned, or blocked."
|
|
3546
3568
|
: "",
|
|
3547
3569
|
options.canOrchestrateOrganization
|
|
3548
3570
|
? `For agent_hire, set adapterType explicitly. Default to the current executing agent adapterType${currentAgentAdapterType ? ` (${currentAgentAdapterType})` : ""} unless the task names a different approved adapter.`
|
|
@@ -3557,17 +3579,28 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3557
3579
|
"Never use write, bash, cat, printf, echo, or any other tool to emit the marker or JSON. You may prepare and validate the envelope in a temporary workspace file, but the final assistant message itself must contain the exact marker and JSON; tool output is ignored by the runtime action bridge.",
|
|
3558
3580
|
"The runtime action block must be strict JSON parseable by JSON.parse. Do not use ellipses, placeholders such as [...], comments, trailing commas, single quotes, markdown tables outside quoted strings, or raw double quotes inside string values. Escape quotes and newlines in string values, or generate the block with JSON.stringify / @amaster/runtime-sdk before including it in the final assistant message.",
|
|
3559
3581
|
"Before final output, validate the runtime action block with JSON.parse. If it would fail, fix the JSON and only then include AMASTER_RUNTIME_ACTIONS in the final assistant message.",
|
|
3582
|
+
actionValidatorCommand
|
|
3583
|
+
? `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.`
|
|
3584
|
+
: "",
|
|
3560
3585
|
`Allowed action types: ${allowedActionTypes}.`,
|
|
3586
|
+
"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.",
|
|
3561
3587
|
"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.",
|
|
3562
3588
|
"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.",
|
|
3563
|
-
"For create_interaction ask_user_questions, 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.",
|
|
3589
|
+
"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.",
|
|
3564
3590
|
"For create_interaction request_confirmation, use payload {\"version\":1,\"prompt\":\"Confirm this result?\",\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\"}. Do not use options arrays for request_confirmation.",
|
|
3565
3591
|
"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\":\"调整\"}.",
|
|
3566
3592
|
"For create_interaction suggest_tasks, use payload {\"version\":1,\"tasks\":[{\"clientKey\":\"task_key\",\"title\":\"Task title\",\"description\":\"Task description\",\"priority\":\"medium\"}]}.",
|
|
3567
3593
|
"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.",
|
|
3594
|
+
"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.",
|
|
3568
3595
|
"Do not copy placeholder names, placeholder summaries, placeholder action arrays, or placeholder paths. Every title, body, role, idempotencyKey, artifact path, and task description must be specific to the current issue and company.",
|
|
3569
3596
|
"Only include upload_artifact for a file that you actually created in the execution workspace and whose path exists relative to cwd. If no file was created, omit upload_artifact.",
|
|
3597
|
+
"upload_artifact actions do not accept required. Artifact required belongs only to top-level deliverables entries, not action objects.",
|
|
3598
|
+
"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.",
|
|
3599
|
+
"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.",
|
|
3600
|
+
"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.",
|
|
3601
|
+
"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.",
|
|
3570
3602
|
"For required deliverables, add top-level deliverables entries using {\"path\":\"relative/file\",\"required\":true}.",
|
|
3603
|
+
"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.",
|
|
3571
3604
|
artifactVerifierCommands.length > 0
|
|
3572
3605
|
? `Executable deliverable verification is enabled. Allowed verifier commands: ${artifactVerifierCommands.join(", ")}. For runnable outputs, add verification {\"command\":\"${artifactVerifierCommands[0]}\",\"args\":[\"script.js\"],\"timeoutSeconds\":30,\"expectedExitCode\":0,\"expectedOutputs\":[\"result.json\"]}. Commands are argv-only and run without a shell.`
|
|
3573
3606
|
: "Executable deliverable verification is disabled by runtime policy. Do not include verification blocks. Required deliverables are still checked for presence and upload completeness.",
|
|
@@ -3855,17 +3888,25 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3855
3888
|
const agentInstructions = renderAgentInstructionsBundle(asRecord(payload.agentInstructionsBundle));
|
|
3856
3889
|
const runtimeAuth = commandRuntimeAuth(command);
|
|
3857
3890
|
const hasRuntimeApi = Boolean(readString(runtimeAuth.apiUrl) && readString(runtimeAuth.apiKey));
|
|
3858
|
-
const
|
|
3891
|
+
const authorizationEnvelope = runtimeActionAuthorizationEnvelope(command);
|
|
3892
|
+
const canOrchestrateOrganization = canRuntimeAgentOrchestrateOrganization(context)
|
|
3893
|
+
&& authorizationEnvelope.allowedActionClasses.has("organization_mutation");
|
|
3859
3894
|
const decompositionRequirement = commandLegacyTaskDecompositionRequirement(command);
|
|
3860
3895
|
const explicitDecompositionRequired = decompositionRequirement.required
|
|
3861
3896
|
&& runtimeActionChildIssueSummaryEntries(command).length === 0;
|
|
3862
3897
|
const executingAgent = asRecord(context.paperclipExecutingAgent);
|
|
3863
3898
|
const currentAgentAdapterType = readString(executingAgent.adapterType);
|
|
3899
|
+
const validatorContentTypes = runtimeAttachmentContentTypes(command);
|
|
3900
|
+
// Built-in executors run on the connector host, so the prompt may reference these host paths.
|
|
3901
|
+
// A custom wrapper may isolate model tools; until isolation is an explicit capability, this
|
|
3902
|
+
// validator is same-host only and must fail visibly rather than fall back to a weaker check.
|
|
3903
|
+
const actionValidatorCommand = `${quoteShell(process.execPath)} ${quoteShell(resolve(process.argv[1]))} validate-actions --file .amaster-runtime-actions.json --cwd . --allowed-content-types ${quoteShell(validatorContentTypes.join(","))}`;
|
|
3864
3904
|
return [
|
|
3865
3905
|
"## AMaster Runtime Connector Task",
|
|
3866
3906
|
"",
|
|
3867
3907
|
"You are executing a task dispatched by AMaster Employee from the central control plane.",
|
|
3868
3908
|
"Work only inside the declared workspace. Make concrete progress and finish with a concise result summary.",
|
|
3909
|
+
"Do not install operating-system or user-global packages, and do not use host package managers such as brew, apt, yum, or global pip/npm installs. Use tools already available or workspace-local dependencies or virtual environments. If a required renderer or evaluator is unavailable, keep the source artifact, record the exact verification gap, and do not mutate the host.",
|
|
3869
3910
|
"",
|
|
3870
3911
|
`- command id: ${command.commandId}`,
|
|
3871
3912
|
`- run id: ${commandRunId(command) ?? "unknown"}`,
|
|
@@ -3873,7 +3914,9 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3873
3914
|
issueLine ? `- issue: ${issueLine}` : "",
|
|
3874
3915
|
`- workspace: ${cwd}`,
|
|
3875
3916
|
workspaceContext.managed ? `- source workspace: ${workspaceContext.sourceWorkspacePath}` : "",
|
|
3876
|
-
workspaceContext.managed
|
|
3917
|
+
workspaceContext.managed
|
|
3918
|
+
? "Use the execution workspace as cwd. Never recursively scan the source workspace, the user's home directory, or any path outside the execution workspace. Only inspect a specific external source path when the task explicitly requires that exact path."
|
|
3919
|
+
: "",
|
|
3877
3920
|
readString(context.wakeReason) ? `- wake reason: ${readString(context.wakeReason)}` : "",
|
|
3878
3921
|
hasRuntimeApi
|
|
3879
3922
|
? "- AMaster API env: PAPERCLIP_API_URL, PAPERCLIP_API_KEY, AMASTER_BOARD_API_KEY, PAPERCLIP_RUN_ID, PAPERCLIP_TASK_ID, PAPERCLIP_AGENT_ID, PAPERCLIP_COMPANY_ID are available to update the task, create child tasks, and report progress. Treat API keys as secrets; never print them or include them in runtime actions."
|
|
@@ -3884,6 +3927,8 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3884
3927
|
currentAgentAdapterType,
|
|
3885
3928
|
explicitDecompositionRequired,
|
|
3886
3929
|
artifactVerifierCommands: options.artifactVerifierCommands,
|
|
3930
|
+
actionValidatorCommand,
|
|
3931
|
+
authorizationEnvelope,
|
|
3887
3932
|
}) : "",
|
|
3888
3933
|
"",
|
|
3889
3934
|
agentInstructions,
|
|
@@ -5359,6 +5404,15 @@ function parseRuntimeActionEnvelopeJson(jsonText) {
|
|
|
5359
5404
|
}
|
|
5360
5405
|
}
|
|
5361
5406
|
|
|
5407
|
+
function hasMalformedRuntimeActionMarkerIntent(text) {
|
|
5408
|
+
const value = String(text ?? "");
|
|
5409
|
+
if (!value.includes("AMASTER_RUNTIME_ACTIONS")) return false;
|
|
5410
|
+
const hasNonCanonicalMarkerLine = value
|
|
5411
|
+
.split(/\r?\n/)
|
|
5412
|
+
.some((line) => line.includes("AMASTER_RUNTIME_ACTIONS") && line.trim() !== "AMASTER_RUNTIME_ACTIONS");
|
|
5413
|
+
return hasNonCanonicalMarkerLine && /["']actions["']\s*:/.test(value);
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5362
5416
|
function readBoundedActionString(record, key, maxChars) {
|
|
5363
5417
|
const value = readString(record[key]);
|
|
5364
5418
|
return value ? truncateText(value, maxChars) : null;
|
|
@@ -5722,6 +5776,97 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
5722
5776
|
return materialized;
|
|
5723
5777
|
}
|
|
5724
5778
|
|
|
5779
|
+
function safeArtifactInputSourceDir(entry, index) {
|
|
5780
|
+
const raw = readString(entry.sourceIssueIdentifier) ?? readString(entry.sourceIssueId) ?? `source-${index + 1}`;
|
|
5781
|
+
const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^\.+|\.+$/g, "").slice(0, 120);
|
|
5782
|
+
return safe && safe !== "." && safe !== ".." ? safe : `source-${index + 1}`;
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
5786
|
+
const runtimeAuth = commandRuntimeAuth(command);
|
|
5787
|
+
if (!readString(runtimeAuth.apiUrl) || !readString(runtimeAuth.apiKey)) return [];
|
|
5788
|
+
const context = asRecord(asRecord(command.payload).contextSnapshot);
|
|
5789
|
+
const issue = asRecord(context.paperclipIssue);
|
|
5790
|
+
const manifest = asRecord(issue.artifactInputManifest);
|
|
5791
|
+
if (Object.keys(manifest).length === 0) return [];
|
|
5792
|
+
if (manifest.version !== 1 || !Array.isArray(manifest.entries)) {
|
|
5793
|
+
throw new Error("artifact_input_manifest_invalid: expected version 1 entries array");
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5796
|
+
const targetRoot = join(workspace.cwd, "input-artifacts");
|
|
5797
|
+
rmSync(targetRoot, { recursive: true, force: true });
|
|
5798
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
5799
|
+
const usedPaths = new Set();
|
|
5800
|
+
const materialized = [];
|
|
5801
|
+
for (const [index, rawEntry] of manifest.entries.entries()) {
|
|
5802
|
+
const entry = asRecord(rawEntry);
|
|
5803
|
+
const workProductId = readString(entry.workProductId);
|
|
5804
|
+
const attachmentId = readString(entry.attachmentId);
|
|
5805
|
+
const sha256 = readString(entry.sha256);
|
|
5806
|
+
const contentPath = readString(entry.contentPath);
|
|
5807
|
+
const byteSize = readNumber(entry.byteSize, null);
|
|
5808
|
+
if (!workProductId || !attachmentId || !/^[a-f0-9]{64}$/i.test(sha256 ?? "") || !contentPath || byteSize === null) {
|
|
5809
|
+
throw new Error(`artifact_input_manifest_invalid: entry ${index} is incomplete`);
|
|
5810
|
+
}
|
|
5811
|
+
const expectedContentPath = `/api/attachments/${attachmentId}/content`;
|
|
5812
|
+
if (contentPath !== expectedContentPath) {
|
|
5813
|
+
throw new Error(`artifact_input_manifest_invalid: entry ${index} contentPath does not match attachmentId`);
|
|
5814
|
+
}
|
|
5815
|
+
const body = await runtimeApiBuffer(runtimeAuth, contentPath);
|
|
5816
|
+
const actualSha256 = createHash("sha256").update(body).digest("hex");
|
|
5817
|
+
if (body.byteLength !== byteSize || actualSha256 !== sha256) {
|
|
5818
|
+
throw new Error(
|
|
5819
|
+
`artifact_input_integrity_mismatch: workProductId=${workProductId} expectedBytes=${byteSize} actualBytes=${body.byteLength} expectedSha256=${sha256} actualSha256=${actualSha256}`,
|
|
5820
|
+
);
|
|
5821
|
+
}
|
|
5822
|
+
|
|
5823
|
+
const sourceDir = safeArtifactInputSourceDir(entry, index);
|
|
5824
|
+
let filename = safeMaterializedAttachmentFilename({
|
|
5825
|
+
id: attachmentId,
|
|
5826
|
+
originalFilename: readString(entry.originalFilename),
|
|
5827
|
+
}, index);
|
|
5828
|
+
let relativePath = join("input-artifacts", sourceDir, filename).split(/[\\/]+/).join("/");
|
|
5829
|
+
if (usedPaths.has(relativePath)) {
|
|
5830
|
+
const ext = extname(filename);
|
|
5831
|
+
const stem = ext ? filename.slice(0, -ext.length) : filename;
|
|
5832
|
+
filename = `${stem}-${index + 1}${ext}`;
|
|
5833
|
+
relativePath = join("input-artifacts", sourceDir, filename).split(/[\\/]+/).join("/");
|
|
5834
|
+
}
|
|
5835
|
+
usedPaths.add(relativePath);
|
|
5836
|
+
const targetPath = join(workspace.cwd, relativePath);
|
|
5837
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
5838
|
+
writeFileSync(targetPath, body);
|
|
5839
|
+
chmodSync(targetPath, 0o444);
|
|
5840
|
+
materialized.push({
|
|
5841
|
+
id: attachmentId,
|
|
5842
|
+
workProductId,
|
|
5843
|
+
sourceIssueId: readString(entry.sourceIssueId),
|
|
5844
|
+
sourceIssueIdentifier: readString(entry.sourceIssueIdentifier),
|
|
5845
|
+
name: readString(entry.originalFilename) ?? filename,
|
|
5846
|
+
path: targetPath,
|
|
5847
|
+
relativePath,
|
|
5848
|
+
contentType: readString(entry.contentType),
|
|
5849
|
+
byteSize: body.byteLength,
|
|
5850
|
+
sha256,
|
|
5851
|
+
contentPath,
|
|
5852
|
+
});
|
|
5853
|
+
}
|
|
5854
|
+
|
|
5855
|
+
const localManifest = {
|
|
5856
|
+
version: 1,
|
|
5857
|
+
entries: materialized.map(({ path: _path, relativePath, ...entry }) => ({ ...entry, path: relativePath })),
|
|
5858
|
+
};
|
|
5859
|
+
const manifestPath = join(targetRoot, "artifact-input-manifest.json");
|
|
5860
|
+
writeFileSync(manifestPath, JSON.stringify(localManifest, null, 2) + "\n");
|
|
5861
|
+
chmodSync(manifestPath, 0o444);
|
|
5862
|
+
updateWorkspaceManifest(workspaceManifestPath(workspace), { artifactInputManifest: localManifest });
|
|
5863
|
+
await ingestLog(config, command, "system", "info", `Materialized ${materialized.length} required artifact input(s) into the execution workspace`, {
|
|
5864
|
+
artifactInputCount: materialized.length,
|
|
5865
|
+
artifactInputs: localManifest.entries,
|
|
5866
|
+
});
|
|
5867
|
+
return materialized;
|
|
5868
|
+
}
|
|
5869
|
+
|
|
5725
5870
|
function issueCheckpointDir(workspace) {
|
|
5726
5871
|
return join(dirname(workspace.runDir), "checkpoint");
|
|
5727
5872
|
}
|
|
@@ -6022,6 +6167,12 @@ function runtimeChildActionFingerprint(action, issueId, parentIssueId) {
|
|
|
6022
6167
|
status: readActionStatus(action.status) ?? null,
|
|
6023
6168
|
priority: readActionPriority(action.priority) ?? null,
|
|
6024
6169
|
parentClientKey: readString(action.parentClientKey) ?? null,
|
|
6170
|
+
inputWorkProductIds: Array.isArray(action.inputWorkProductIds)
|
|
6171
|
+
? action.inputWorkProductIds.map(readString).filter(Boolean).sort()
|
|
6172
|
+
: [],
|
|
6173
|
+
blockedByClientKeys: Array.isArray(action.blockedByClientKeys)
|
|
6174
|
+
? action.blockedByClientKeys.map(readString).filter(Boolean).sort()
|
|
6175
|
+
: [],
|
|
6025
6176
|
})}`;
|
|
6026
6177
|
}
|
|
6027
6178
|
|
|
@@ -6039,6 +6190,7 @@ function runtimeAgentHireActionFingerprint(action, companyId, issueId, body) {
|
|
|
6039
6190
|
adapterType: readString(body.adapterType) ?? null,
|
|
6040
6191
|
sourceIssueId: readString(body.sourceIssueId) ?? null,
|
|
6041
6192
|
sourceIssueIds: runtimeActionIssueIdList(body.sourceIssueIds).sort(),
|
|
6193
|
+
reportsToAgentClientKey: readString(action.reportsToAgentClientKey) ?? null,
|
|
6042
6194
|
})}`;
|
|
6043
6195
|
}
|
|
6044
6196
|
|
|
@@ -6086,21 +6238,6 @@ function runtimeActionDoneNeedsExternalHuman(action, actionContext) {
|
|
|
6086
6238
|
].some((pattern) => pattern.test(normalized));
|
|
6087
6239
|
}
|
|
6088
6240
|
|
|
6089
|
-
function runtimeActionExternalHumanPatch(comment) {
|
|
6090
|
-
const owner = "人工负责人";
|
|
6091
|
-
const action = "补充或授权外部输入后再继续;涉及个人信息、付款、报名提交或审批时由人工处理";
|
|
6092
|
-
const guard = [
|
|
6093
|
-
"AMaster 已检测到本次运行仍依赖人工授权或外部输入,任务已转为 blocked / external_owner_action。本次运行不会自动发送飞书消息;请通过人工协作或已配置的飞书 CLI 通知对应负责人处理,收到明确授权后再继续。",
|
|
6094
|
-
"",
|
|
6095
|
-
`external owner: ${owner}`,
|
|
6096
|
-
`external action: ${action}`,
|
|
6097
|
-
].join("\n");
|
|
6098
|
-
return {
|
|
6099
|
-
status: "blocked",
|
|
6100
|
-
comment: comment ? `${comment}\n\n${guard}` : guard,
|
|
6101
|
-
};
|
|
6102
|
-
}
|
|
6103
|
-
|
|
6104
6241
|
function runtimeActionReviewConfirmation(action, runtimeAuth, command, actionContext, opts = {}) {
|
|
6105
6242
|
const runId = runtimeActionRunId(runtimeAuth) ?? commandRunId(command) ?? "unknown-run";
|
|
6106
6243
|
const comment = readBoundedActionString(action, "comment", 20_000)
|
|
@@ -6250,6 +6387,24 @@ function runtimeActionContinuationScope(command) {
|
|
|
6250
6387
|
return ["revision_only", "interaction_accepted", "message_only"].includes(scope ?? "") ? scope : null;
|
|
6251
6388
|
}
|
|
6252
6389
|
|
|
6390
|
+
function runtimeActionAuthorizationEnvelope(command) {
|
|
6391
|
+
const payload = asRecord(command.payload);
|
|
6392
|
+
const context = asRecord(payload.contextSnapshot);
|
|
6393
|
+
const rawEnvelope = asRecord(context.authorizationEnvelope);
|
|
6394
|
+
const allowedActionClasses = Array.isArray(rawEnvelope.allowedActionClasses)
|
|
6395
|
+
? rawEnvelope.allowedActionClasses.map(readString).filter(Boolean)
|
|
6396
|
+
: [];
|
|
6397
|
+
const effectiveAllowedActionClasses = allowedActionClasses.length > 0
|
|
6398
|
+
? allowedActionClasses
|
|
6399
|
+
: ["task_governance"];
|
|
6400
|
+
return {
|
|
6401
|
+
allowedActionClasses: new Set(effectiveAllowedActionClasses),
|
|
6402
|
+
sourceKind: readString(rawEnvelope.sourceKind) ?? "implicit_default",
|
|
6403
|
+
sourceId: readString(rawEnvelope.sourceId) ?? commandRunId(command),
|
|
6404
|
+
sourceRevision: readString(rawEnvelope.sourceRevision) ?? "missing_envelope",
|
|
6405
|
+
};
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6253
6408
|
function runtimeActionInReviewNeedsFollowUpText(text) {
|
|
6254
6409
|
const normalized = String(text ?? "")
|
|
6255
6410
|
.replace(/\s+/g, "")
|
|
@@ -6412,6 +6567,35 @@ function runtimeActionParentIssueId(action, actionContext, fallbackIssueId) {
|
|
|
6412
6567
|
return parentIssueId;
|
|
6413
6568
|
}
|
|
6414
6569
|
|
|
6570
|
+
function runtimeActionReportsToAgentId(action, actionContext) {
|
|
6571
|
+
const reportsToAgentClientKey = readString(action.reportsToAgentClientKey);
|
|
6572
|
+
if (!reportsToAgentClientKey) return readString(action.reportsTo);
|
|
6573
|
+
const reportsTo = actionContext.agentIdByClientKey.get(reportsToAgentClientKey);
|
|
6574
|
+
if (!reportsTo) {
|
|
6575
|
+
throw new Error(`agent_hire action references unknown reportsToAgentClientKey: ${reportsToAgentClientKey}`);
|
|
6576
|
+
}
|
|
6577
|
+
return reportsTo;
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
function runtimeActionBlockedByIssueIds(action, actionContext) {
|
|
6581
|
+
const explicitIssueIds = Array.isArray(action.blockedByIssueIds)
|
|
6582
|
+
? action.blockedByIssueIds.map((rawIssueId) => readString(rawIssueId)).filter(Boolean)
|
|
6583
|
+
: [];
|
|
6584
|
+
if (action.blockedByClientKeys === undefined) return [...new Set(explicitIssueIds)];
|
|
6585
|
+
const clientKeyIssueIds = action.blockedByClientKeys.map((rawClientKey, index) => {
|
|
6586
|
+
const clientKey = readString(rawClientKey);
|
|
6587
|
+
if (!clientKey) {
|
|
6588
|
+
throw runtimeActionValidationError(`create_child_task blockedByClientKeys[${index}] must be a non-empty string`);
|
|
6589
|
+
}
|
|
6590
|
+
const blockerIssueId = actionContext.issueIdByClientKey.get(clientKey);
|
|
6591
|
+
if (!blockerIssueId) {
|
|
6592
|
+
throw new Error(`create_child_task action references unknown blockedByClientKeys entry: ${clientKey}`);
|
|
6593
|
+
}
|
|
6594
|
+
return blockerIssueId;
|
|
6595
|
+
});
|
|
6596
|
+
return [...new Set([...explicitIssueIds, ...clientKeyIssueIds])];
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6415
6599
|
const RUNTIME_EXECUTOR_ADAPTER_TYPE_ALIASES = new Map([
|
|
6416
6600
|
["pi", "pi_local"],
|
|
6417
6601
|
["pi_local", "pi_local"],
|
|
@@ -6859,6 +7043,242 @@ function assertRuntimeActionValid(condition, message) {
|
|
|
6859
7043
|
if (!condition) throw runtimeActionValidationError(message);
|
|
6860
7044
|
}
|
|
6861
7045
|
|
|
7046
|
+
const RUNTIME_ACTION_COMMON_FIELDS = new Set(["type", "action", "name", "payload"]);
|
|
7047
|
+
const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
|
|
7048
|
+
["create_child_task", new Set([
|
|
7049
|
+
"title", "description", "status", "priority", "assigneeAgentId", "assigneeUserId",
|
|
7050
|
+
"assigneeAgentClientKey", "agentClientKey", "blockParentUntilDone", "executeBeforeConfirmation",
|
|
7051
|
+
"clientKey", "idempotencyKey", "parentClientKey", "blockedByIssueIds", "blockedByClientKeys", "create_child_task",
|
|
7052
|
+
"inputWorkProductIds",
|
|
7053
|
+
])],
|
|
7054
|
+
["agent_hire", new Set([
|
|
7055
|
+
"clientKey", "idempotencyKey", "key", "companyId", "role", "title", "icon", "reportsTo",
|
|
7056
|
+
"reportsToAgentClientKey", "capabilities", "desiredSkills", "adapterType", "adapterConfig",
|
|
7057
|
+
"instructionsBundle", "runtimeConfig", "defaultEnvironmentId", "budgetMonthlyCents", "permissions",
|
|
7058
|
+
"metadata", "sourceIssueId", "sourceIssueIds", "executeBeforeConfirmation", "agent_hire", "agent_hire/create_agent",
|
|
7059
|
+
])],
|
|
7060
|
+
["create_agent", new Set([
|
|
7061
|
+
"clientKey", "idempotencyKey", "key", "companyId", "role", "title", "icon", "reportsTo",
|
|
7062
|
+
"reportsToAgentClientKey", "capabilities", "desiredSkills", "adapterType", "adapterConfig",
|
|
7063
|
+
"instructionsBundle", "runtimeConfig", "defaultEnvironmentId", "budgetMonthlyCents", "permissions",
|
|
7064
|
+
"metadata", "sourceIssueId", "sourceIssueIds", "executeBeforeConfirmation", "create_agent",
|
|
7065
|
+
])],
|
|
7066
|
+
["add_comment", new Set(["body", "content", "text", "message", "add_comment"])],
|
|
7067
|
+
["upload_artifact", new Set([
|
|
7068
|
+
"path", "filePath", "artifactPath", "artifact_path", "title", "summary", "contentType",
|
|
7069
|
+
"createWorkProduct", "provider", "status", "reviewState", "isPrimary", "healthStatus",
|
|
7070
|
+
"sourceWorkProductId", "sourceWorkProductIds", "supersedesWorkProductId", "evidenceRole", "scenarioKeys",
|
|
7071
|
+
"idempotencyKey", "upload_artifact",
|
|
7072
|
+
])],
|
|
7073
|
+
["upsert_document", new Set([
|
|
7074
|
+
"key", "title", "body", "format", "changeSummary", "baseRevisionId", "upsert_document",
|
|
7075
|
+
])],
|
|
7076
|
+
["create_interaction", new Set([
|
|
7077
|
+
"kind", "continuationPolicy", "title", "summary", "idempotencyKey", "interaction", "version",
|
|
7078
|
+
"prompt", "question", "body", "content", "text", "message", "description", "acceptLabel",
|
|
7079
|
+
"requestChangesLabel", "requestChangesReasonLabel", "rejectLabel", "rejectRequiresReason",
|
|
7080
|
+
"rejectReasonLabel", "allowDeclineReason", "declineReasonPlaceholder", "detailsMarkdown",
|
|
7081
|
+
"supersedeOnUserComment", "target", "artifactRefs", "tasks", "options", "defaultSelectedOptionIds",
|
|
7082
|
+
"minSelected", "maxSelected", "questions", "submitLabel", "selectionMode", "required", "helpText",
|
|
7083
|
+
"create_interaction",
|
|
7084
|
+
])],
|
|
7085
|
+
["update_parent", new Set(["status", "comment", "force", "update_parent"])],
|
|
7086
|
+
["update_company_profile", new Set([
|
|
7087
|
+
"companyId", "tagline", "stage", "targetCustomers", "products", "currentPriority", "businessModel",
|
|
7088
|
+
"regions", "brandTone", "markAutoDerived", "idempotencyKey", "update_company_profile",
|
|
7089
|
+
])],
|
|
7090
|
+
["create_routine", new Set([
|
|
7091
|
+
"companyId", "title", "description", "cronExpression", "priority", "concurrencyPolicy", "catchUpPolicy",
|
|
7092
|
+
"assigneeAgentId", "timezone", "idempotencyKey", "create_routine",
|
|
7093
|
+
])],
|
|
7094
|
+
]);
|
|
7095
|
+
|
|
7096
|
+
function runtimeActionReceivedType(value) {
|
|
7097
|
+
if (value === null) return "null";
|
|
7098
|
+
if (Array.isArray(value)) return "array";
|
|
7099
|
+
return typeof value;
|
|
7100
|
+
}
|
|
7101
|
+
|
|
7102
|
+
function assertNoUnsupportedRuntimeActionFields(action, actionIndex) {
|
|
7103
|
+
const type = readRuntimeActionType(action);
|
|
7104
|
+
const allowed = RUNTIME_ACTION_ALLOWED_FIELDS.get(type);
|
|
7105
|
+
assertRuntimeActionValid(allowed, `unsupported AMaster runtime action type: ${type ?? "missing"}`);
|
|
7106
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7107
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
7108
|
+
if (type === "create_interaction" && field === "interactionType") continue;
|
|
7109
|
+
if (RUNTIME_ACTION_COMMON_FIELDS.has(field) || allowed.has(field)) continue;
|
|
7110
|
+
const fieldPath = `actions[${actionIndex}].${field}`;
|
|
7111
|
+
const receivedType = runtimeActionReceivedType(value);
|
|
7112
|
+
const err = runtimeActionValidationError(
|
|
7113
|
+
`${type} action has unsupported field ${field} at ${fieldPath}; received ${receivedType}`,
|
|
7114
|
+
);
|
|
7115
|
+
err.runtimeActionFieldPath = fieldPath;
|
|
7116
|
+
err.runtimeActionReceivedType = receivedType;
|
|
7117
|
+
throw err;
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
|
|
7121
|
+
const RUNTIME_ACTION_BOOLEAN_FIELDS = new Set([
|
|
7122
|
+
"blockParentUntilDone", "executeBeforeConfirmation", "createWorkProduct", "isPrimary",
|
|
7123
|
+
"force", "markAutoDerived", "rejectRequiresReason", "allowDeclineReason", "supersedeOnUserComment",
|
|
7124
|
+
"required",
|
|
7125
|
+
]);
|
|
7126
|
+
const RUNTIME_ACTION_NUMBER_FIELDS = new Set(["budgetMonthlyCents", "minSelected", "maxSelected", "version"]);
|
|
7127
|
+
const RUNTIME_ACTION_ARRAY_FIELDS = new Set([
|
|
7128
|
+
"blockedByIssueIds", "blockedByClientKeys", "desiredSkills", "sourceIssueIds", "artifactRefs", "tasks",
|
|
7129
|
+
"options", "defaultSelectedOptionIds", "questions", "sourceWorkProductIds", "scenarioKeys",
|
|
7130
|
+
"inputWorkProductIds",
|
|
7131
|
+
]);
|
|
7132
|
+
const RUNTIME_ACTION_OBJECT_FIELDS = new Set([
|
|
7133
|
+
"adapterConfig", "instructionsBundle", "runtimeConfig", "permissions", "metadata", "target",
|
|
7134
|
+
"interaction",
|
|
7135
|
+
]);
|
|
7136
|
+
|
|
7137
|
+
function assertRuntimeActionFieldTypes(action, actionIndex) {
|
|
7138
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7139
|
+
const type = readRuntimeActionType(fields) ?? "unknown";
|
|
7140
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
7141
|
+
if (value === undefined) continue;
|
|
7142
|
+
if (SINGLE_KEY_RUNTIME_ACTION_TYPES.has(field)) continue;
|
|
7143
|
+
if (type === "create_interaction" && field === "payload") continue;
|
|
7144
|
+
let expectedType = null;
|
|
7145
|
+
let valid = true;
|
|
7146
|
+
if (RUNTIME_ACTION_BOOLEAN_FIELDS.has(field)) {
|
|
7147
|
+
expectedType = "boolean";
|
|
7148
|
+
valid = typeof value === "boolean";
|
|
7149
|
+
} else if (RUNTIME_ACTION_NUMBER_FIELDS.has(field)) {
|
|
7150
|
+
expectedType = "number";
|
|
7151
|
+
valid = typeof value === "number" && Number.isFinite(value);
|
|
7152
|
+
} else if (RUNTIME_ACTION_ARRAY_FIELDS.has(field)) {
|
|
7153
|
+
expectedType = "array";
|
|
7154
|
+
valid = Array.isArray(value);
|
|
7155
|
+
} else if (field === "capabilities") {
|
|
7156
|
+
expectedType = "string or array";
|
|
7157
|
+
valid = typeof value === "string" || Array.isArray(value);
|
|
7158
|
+
} else if (RUNTIME_ACTION_OBJECT_FIELDS.has(field) || field === "payload") {
|
|
7159
|
+
expectedType = "object";
|
|
7160
|
+
valid = value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7161
|
+
} else if (!RUNTIME_ACTION_COMMON_FIELDS.has(field) && value !== null) {
|
|
7162
|
+
expectedType = "string";
|
|
7163
|
+
valid = typeof value === "string";
|
|
7164
|
+
}
|
|
7165
|
+
if (valid || !expectedType) continue;
|
|
7166
|
+
const fieldPath = `actions[${actionIndex}].${field}`;
|
|
7167
|
+
const err = runtimeActionValidationError(
|
|
7168
|
+
`${type} action field ${fieldPath} must be ${expectedType}; received ${runtimeActionReceivedType(value)}`,
|
|
7169
|
+
);
|
|
7170
|
+
err.runtimeActionFieldPath = fieldPath;
|
|
7171
|
+
err.runtimeActionReceivedType = runtimeActionReceivedType(value);
|
|
7172
|
+
throw err;
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
|
|
7176
|
+
function runtimeActionClientKeyProducerKind(type) {
|
|
7177
|
+
if (["agent_hire", "create_agent"].includes(type ?? "")) return "agent";
|
|
7178
|
+
if (type === "create_child_task") return "issue";
|
|
7179
|
+
return null;
|
|
7180
|
+
}
|
|
7181
|
+
|
|
7182
|
+
function runtimeActionClientKeyReferences(fields) {
|
|
7183
|
+
const type = readRuntimeActionType(fields);
|
|
7184
|
+
const references = [];
|
|
7185
|
+
if (["agent_hire", "create_agent"].includes(type ?? "")) {
|
|
7186
|
+
const clientKey = readString(fields.reportsToAgentClientKey);
|
|
7187
|
+
if (clientKey) references.push({ clientKey, expectedKind: "agent", field: "reportsToAgentClientKey" });
|
|
7188
|
+
}
|
|
7189
|
+
if (type === "create_child_task") {
|
|
7190
|
+
const assigneeClientKey = readString(fields.assigneeAgentClientKey) ?? readString(fields.agentClientKey);
|
|
7191
|
+
if (assigneeClientKey) references.push({ clientKey: assigneeClientKey, expectedKind: "agent", field: "assigneeAgentClientKey" });
|
|
7192
|
+
const parentClientKey = readString(fields.parentClientKey);
|
|
7193
|
+
if (parentClientKey) references.push({ clientKey: parentClientKey, expectedKind: "issue", field: "parentClientKey" });
|
|
7194
|
+
if (fields.blockedByClientKeys !== undefined) {
|
|
7195
|
+
assertRuntimeActionValid(Array.isArray(fields.blockedByClientKeys), "create_child_task blockedByClientKeys must be an array");
|
|
7196
|
+
for (const [index, value] of fields.blockedByClientKeys.entries()) {
|
|
7197
|
+
const clientKey = readString(value);
|
|
7198
|
+
assertRuntimeActionValid(clientKey, `create_child_task blockedByClientKeys[${index}] must be a non-empty string`);
|
|
7199
|
+
references.push({ clientKey, expectedKind: "issue", field: `blockedByClientKeys[${index}]` });
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7203
|
+
return references;
|
|
7204
|
+
}
|
|
7205
|
+
|
|
7206
|
+
function throwRuntimeActionGraphValidation(message, action, actionIndex, fieldPath) {
|
|
7207
|
+
const err = runtimeActionValidationError(message);
|
|
7208
|
+
err.runtimeActionType = readRuntimeActionType(action) ?? "unknown";
|
|
7209
|
+
err.runtimeActionOriginalType = readRuntimeActionOriginalType(action);
|
|
7210
|
+
err.runtimeActionFieldPath = fieldPath ?? `actions[${actionIndex}]`;
|
|
7211
|
+
throw err;
|
|
7212
|
+
}
|
|
7213
|
+
|
|
7214
|
+
function orderRuntimeActionsByClientKeyDependencies(actions) {
|
|
7215
|
+
const producers = new Map();
|
|
7216
|
+
for (const [index, action] of actions.entries()) {
|
|
7217
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7218
|
+
const type = readRuntimeActionType(fields);
|
|
7219
|
+
const kind = runtimeActionClientKeyProducerKind(type);
|
|
7220
|
+
const clientKey = readString(fields.clientKey);
|
|
7221
|
+
if (!clientKey || !kind) continue;
|
|
7222
|
+
if (producers.has(clientKey)) {
|
|
7223
|
+
throwRuntimeActionGraphValidation(
|
|
7224
|
+
`runtime action clientKey must be unique: ${clientKey}`,
|
|
7225
|
+
action,
|
|
7226
|
+
index,
|
|
7227
|
+
`actions[${index}].clientKey`,
|
|
7228
|
+
);
|
|
7229
|
+
}
|
|
7230
|
+
producers.set(clientKey, { index, kind, type });
|
|
7231
|
+
}
|
|
7232
|
+
|
|
7233
|
+
const dependenciesByIndex = actions.map(() => new Set());
|
|
7234
|
+
const dependentsByIndex = actions.map(() => new Set());
|
|
7235
|
+
for (const [index, action] of actions.entries()) {
|
|
7236
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7237
|
+
for (const reference of runtimeActionClientKeyReferences(fields)) {
|
|
7238
|
+
const producer = producers.get(reference.clientKey);
|
|
7239
|
+
if (!producer) {
|
|
7240
|
+
throwRuntimeActionGraphValidation(
|
|
7241
|
+
`${readRuntimeActionType(fields)} action references unknown ${reference.field}: ${reference.clientKey}`,
|
|
7242
|
+
action,
|
|
7243
|
+
index,
|
|
7244
|
+
`actions[${index}].${reference.field}`,
|
|
7245
|
+
);
|
|
7246
|
+
}
|
|
7247
|
+
if (producer.kind !== reference.expectedKind) {
|
|
7248
|
+
throwRuntimeActionGraphValidation(
|
|
7249
|
+
`${readRuntimeActionType(fields)} action ${reference.field} must reference a ${reference.expectedKind} clientKey: ${reference.clientKey}`,
|
|
7250
|
+
action,
|
|
7251
|
+
index,
|
|
7252
|
+
`actions[${index}].${reference.field}`,
|
|
7253
|
+
);
|
|
7254
|
+
}
|
|
7255
|
+
dependenciesByIndex[index].add(producer.index);
|
|
7256
|
+
dependentsByIndex[producer.index].add(index);
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
|
|
7260
|
+
const ready = actions.map((_, index) => index).filter((index) => dependenciesByIndex[index].size === 0);
|
|
7261
|
+
const orderedIndexes = [];
|
|
7262
|
+
while (ready.length > 0) {
|
|
7263
|
+
ready.sort((left, right) => left - right);
|
|
7264
|
+
const index = ready.shift();
|
|
7265
|
+
orderedIndexes.push(index);
|
|
7266
|
+
for (const dependentIndex of dependentsByIndex[index]) {
|
|
7267
|
+
dependenciesByIndex[dependentIndex].delete(index);
|
|
7268
|
+
if (dependenciesByIndex[dependentIndex].size === 0) ready.push(dependentIndex);
|
|
7269
|
+
}
|
|
7270
|
+
}
|
|
7271
|
+
if (orderedIndexes.length !== actions.length) {
|
|
7272
|
+
const cycleIndex = dependenciesByIndex.findIndex((dependencies) => dependencies.size > 0);
|
|
7273
|
+
throwRuntimeActionGraphValidation(
|
|
7274
|
+
"runtime action clientKey dependency graph contains a cycle",
|
|
7275
|
+
actions[cycleIndex],
|
|
7276
|
+
cycleIndex,
|
|
7277
|
+
);
|
|
7278
|
+
}
|
|
7279
|
+
return orderedIndexes.map((index) => actions[index]);
|
|
7280
|
+
}
|
|
7281
|
+
|
|
6862
7282
|
function assertRuntimeInteractionOptions(options, path) {
|
|
6863
7283
|
assertRuntimeActionValid(Array.isArray(options) && options.length > 0 && options.length <= 10, `${path} requires 1-10 options`);
|
|
6864
7284
|
const ids = new Set();
|
|
@@ -6936,6 +7356,16 @@ function willRuntimeActionCreateRootBlockingChild(action) {
|
|
|
6936
7356
|
return fields.blockParentUntilDone === true || childStatus === "blocked";
|
|
6937
7357
|
}
|
|
6938
7358
|
|
|
7359
|
+
function willRuntimeActionCreateHumanOwnedInputPath(action) {
|
|
7360
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7361
|
+
if (readRuntimeActionType(fields) !== "create_child_task") return false;
|
|
7362
|
+
const childStatus = readActionStatus(fields.status) ?? "todo";
|
|
7363
|
+
return Boolean(
|
|
7364
|
+
readString(fields.assigneeUserId) &&
|
|
7365
|
+
(fields.blockParentUntilDone === true || childStatus === "blocked"),
|
|
7366
|
+
);
|
|
7367
|
+
}
|
|
7368
|
+
|
|
6939
7369
|
function willRuntimeActionCreatePendingInteraction(action) {
|
|
6940
7370
|
const fields = runtimeActionBusinessFields(action);
|
|
6941
7371
|
if (readRuntimeActionType(fields) !== "create_interaction") return false;
|
|
@@ -6951,8 +7381,22 @@ function runtimeActionIsAmbiguousApprovalGatedChild(action) {
|
|
|
6951
7381
|
return childStatus !== "backlog";
|
|
6952
7382
|
}
|
|
6953
7383
|
|
|
6954
|
-
function
|
|
7384
|
+
function assertNoAmbiguousApprovalGatedSideEffects(actions) {
|
|
6955
7385
|
if (!actions.some(willRuntimeActionCreatePendingInteraction)) return;
|
|
7386
|
+
const ambiguousHire = actions.find((action) => {
|
|
7387
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7388
|
+
return ["agent_hire", "create_agent"].includes(readRuntimeActionType(fields) ?? "") &&
|
|
7389
|
+
fields.executeBeforeConfirmation !== true;
|
|
7390
|
+
});
|
|
7391
|
+
if (ambiguousHire) {
|
|
7392
|
+
const error = runtimeActionValidationError(
|
|
7393
|
+
"approval-gated agent hire actions require the native hire approval only, a separate proposal interaction, or executeBeforeConfirmation true",
|
|
7394
|
+
);
|
|
7395
|
+
error.runtimeActionType = readRuntimeActionType(ambiguousHire) ?? "agent_hire";
|
|
7396
|
+
error.runtimeActionOriginalType = readRuntimeActionOriginalType(ambiguousHire);
|
|
7397
|
+
error.runtimeActionReason = error.message;
|
|
7398
|
+
throw error;
|
|
7399
|
+
}
|
|
6956
7400
|
const ambiguousChild = actions.find(runtimeActionIsAmbiguousApprovalGatedChild);
|
|
6957
7401
|
if (!ambiguousChild) return;
|
|
6958
7402
|
const error = new Error(
|
|
@@ -7007,9 +7451,59 @@ function assertRuntimeActionsAllowedByContinuationScope(actions, scope) {
|
|
|
7007
7451
|
}
|
|
7008
7452
|
}
|
|
7009
7453
|
|
|
7010
|
-
function preflightRuntimeAction(action, preflightContext) {
|
|
7454
|
+
function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
7011
7455
|
const type = readRuntimeActionType(action);
|
|
7012
7456
|
const fields = runtimeActionBusinessFields(action);
|
|
7457
|
+
assertNoUnsupportedRuntimeActionFields(action, actionIndex);
|
|
7458
|
+
assertRuntimeActionFieldTypes(action, actionIndex);
|
|
7459
|
+
if (type === "create_child_task") {
|
|
7460
|
+
assertNoPlaceholderRuntimeActionText(fields, ["title", "description"]);
|
|
7461
|
+
assertRuntimeActionValid(readBoundedActionString(fields, "title", 240), "create_child_task action requires title");
|
|
7462
|
+
if (fields.status !== undefined) {
|
|
7463
|
+
assertRuntimeActionValid(readActionStatus(fields.status), `create_child_task status is unsupported: ${String(fields.status)}`);
|
|
7464
|
+
}
|
|
7465
|
+
if (fields.priority !== undefined) {
|
|
7466
|
+
assertRuntimeActionValid(readActionPriority(fields.priority), `create_child_task priority is unsupported: ${String(fields.priority)}`);
|
|
7467
|
+
}
|
|
7468
|
+
assertRuntimeActionValid(
|
|
7469
|
+
!readString(fields.assigneeAgentId) || (!readString(fields.assigneeAgentClientKey) && !readString(fields.agentClientKey)),
|
|
7470
|
+
"create_child_task action cannot combine assigneeAgentId with assigneeAgentClientKey/agentClientKey",
|
|
7471
|
+
);
|
|
7472
|
+
if (fields.blockedByIssueIds !== undefined) {
|
|
7473
|
+
for (const [index, rawIssueId] of fields.blockedByIssueIds.entries()) {
|
|
7474
|
+
const issueId = readString(rawIssueId);
|
|
7475
|
+
assertRuntimeActionValid(
|
|
7476
|
+
issueId && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(issueId),
|
|
7477
|
+
`create_child_task blockedByIssueIds[${index}] must be a UUID`,
|
|
7478
|
+
);
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
if (fields.inputWorkProductIds !== undefined) {
|
|
7482
|
+
const seen = new Set();
|
|
7483
|
+
for (const [index, rawWorkProductId] of fields.inputWorkProductIds.entries()) {
|
|
7484
|
+
const workProductId = readString(rawWorkProductId);
|
|
7485
|
+
assertRuntimeActionValid(
|
|
7486
|
+
workProductId && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(workProductId),
|
|
7487
|
+
`create_child_task inputWorkProductIds[${index}] must be a UUID`,
|
|
7488
|
+
);
|
|
7489
|
+
assertRuntimeActionValid(
|
|
7490
|
+
!seen.has(workProductId),
|
|
7491
|
+
`create_child_task inputWorkProductIds[${index}] duplicates ${workProductId}`,
|
|
7492
|
+
);
|
|
7493
|
+
seen.add(workProductId);
|
|
7494
|
+
}
|
|
7495
|
+
}
|
|
7496
|
+
return;
|
|
7497
|
+
}
|
|
7498
|
+
if (["agent_hire", "create_agent"].includes(type ?? "")) {
|
|
7499
|
+
assertNoPlaceholderRuntimeActionText(fields, ["name", "role", "title", "instructionsBundle"]);
|
|
7500
|
+
assertRuntimeActionValid(readBoundedActionString(fields, "name", 240), `${type} action requires name`);
|
|
7501
|
+
assertRuntimeActionValid(
|
|
7502
|
+
!readString(fields.reportsTo) || !readString(fields.reportsToAgentClientKey),
|
|
7503
|
+
`${type} action cannot combine reportsTo with reportsToAgentClientKey`,
|
|
7504
|
+
);
|
|
7505
|
+
return;
|
|
7506
|
+
}
|
|
7013
7507
|
if (type === "add_comment") {
|
|
7014
7508
|
assertNoPlaceholderRuntimeActionText(fields, ["body", "content", "text", "message"]);
|
|
7015
7509
|
const body = readBoundedActionString(fields, "body", 20_000)
|
|
@@ -7020,7 +7514,52 @@ function preflightRuntimeAction(action, preflightContext) {
|
|
|
7020
7514
|
return;
|
|
7021
7515
|
}
|
|
7022
7516
|
if (type === "upload_artifact") {
|
|
7517
|
+
assertNoPlaceholderRuntimeActionText(fields, ["path", "filePath", "artifactPath", "artifact_path", "title", "summary"]);
|
|
7518
|
+
assertRuntimeActionValid(readRuntimeArtifactPath(fields), "upload_artifact action requires path");
|
|
7519
|
+
const pathFields = ["path", "filePath", "artifactPath", "artifact_path"].filter((field) => readString(fields[field]));
|
|
7520
|
+
assertRuntimeActionValid(pathFields.length === 1, "upload_artifact action requires exactly one path field");
|
|
7521
|
+
const sourceWorkProductId = readString(fields.sourceWorkProductId);
|
|
7522
|
+
const sourceWorkProductIds = Array.isArray(fields.sourceWorkProductIds)
|
|
7523
|
+
? fields.sourceWorkProductIds.map(readString).filter(Boolean)
|
|
7524
|
+
: [];
|
|
7525
|
+
const supersedesWorkProductId = readString(fields.supersedesWorkProductId);
|
|
7526
|
+
assertRuntimeActionValid(
|
|
7527
|
+
Boolean(sourceWorkProductId) === Boolean(supersedesWorkProductId),
|
|
7528
|
+
"upload_artifact parent replacement requires both sourceWorkProductId and supersedesWorkProductId",
|
|
7529
|
+
);
|
|
7530
|
+
const evidenceRole = readString(fields.evidenceRole);
|
|
7531
|
+
assertRuntimeActionValid(
|
|
7532
|
+
!evidenceRole || ["source", "derived", "review"].includes(evidenceRole),
|
|
7533
|
+
`upload_artifact evidenceRole is unsupported: ${String(fields.evidenceRole)}`,
|
|
7534
|
+
);
|
|
7535
|
+
assertRuntimeActionValid(
|
|
7536
|
+
!["derived", "review"].includes(evidenceRole ?? "") || sourceWorkProductIds.length > 0 || Boolean(sourceWorkProductId),
|
|
7537
|
+
`upload_artifact evidenceRole=${evidenceRole} requires sourceWorkProductIds`,
|
|
7538
|
+
);
|
|
7539
|
+
if (fields.sourceWorkProductIds !== undefined) {
|
|
7540
|
+
assertRuntimeActionValid(Array.isArray(fields.sourceWorkProductIds), "upload_artifact sourceWorkProductIds must be an array");
|
|
7541
|
+
assertRuntimeActionValid(sourceWorkProductIds.length === fields.sourceWorkProductIds.length, "upload_artifact sourceWorkProductIds must contain non-empty strings");
|
|
7542
|
+
}
|
|
7543
|
+
if (fields.scenarioKeys !== undefined) {
|
|
7544
|
+
assertRuntimeActionValid(Array.isArray(fields.scenarioKeys), "upload_artifact scenarioKeys must be an array");
|
|
7545
|
+
assertRuntimeActionValid(fields.scenarioKeys.map(readString).filter(Boolean).length === fields.scenarioKeys.length, "upload_artifact scenarioKeys must contain non-empty strings");
|
|
7546
|
+
}
|
|
7023
7547
|
assertRuntimeArtifactContentType(fields, preflightContext.allowedContentTypes);
|
|
7548
|
+
resolveWorkspaceArtifactPath(fields, { cwd: preflightContext.cwd });
|
|
7549
|
+
return;
|
|
7550
|
+
}
|
|
7551
|
+
if (type === "upsert_document") {
|
|
7552
|
+
assertNoPlaceholderRuntimeActionText(fields, ["key", "title", "body", "changeSummary"]);
|
|
7553
|
+
const key = readBoundedActionString(fields, "key", 64);
|
|
7554
|
+
const body = readBoundedActionString(fields, "body", 524_288);
|
|
7555
|
+
assertRuntimeActionValid(key && /^[a-z0-9][a-z0-9_-]*$/.test(key), "upsert_document key must use lowercase letters, numbers, _ or -");
|
|
7556
|
+
assertRuntimeActionValid(body, "upsert_document action requires body");
|
|
7557
|
+
assertRuntimeActionValid(fields.format === "markdown", "upsert_document format must be markdown");
|
|
7558
|
+
const baseRevisionId = readString(fields.baseRevisionId);
|
|
7559
|
+
assertRuntimeActionValid(
|
|
7560
|
+
!baseRevisionId || /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(baseRevisionId),
|
|
7561
|
+
"upsert_document baseRevisionId must be a UUID",
|
|
7562
|
+
);
|
|
7024
7563
|
return;
|
|
7025
7564
|
}
|
|
7026
7565
|
if (type === "create_interaction") {
|
|
@@ -7031,6 +7570,10 @@ function preflightRuntimeAction(action, preflightContext) {
|
|
|
7031
7570
|
["request_confirmation", "request_checkbox_confirmation", "ask_user_questions", "suggest_tasks"].includes(kind),
|
|
7032
7571
|
`create_interaction action does not support kind: ${kind}`,
|
|
7033
7572
|
);
|
|
7573
|
+
assertRuntimeActionValid(
|
|
7574
|
+
kind !== "ask_user_questions" || readString(fields.continuationPolicy) !== "wake_assignee_on_accept",
|
|
7575
|
+
"ask_user_questions continuationPolicy cannot be wake_assignee_on_accept; use wake_assignee so answering the questions resumes the assignee",
|
|
7576
|
+
);
|
|
7034
7577
|
const payload = normalizeCreateInteractionPayload(fields, kind);
|
|
7035
7578
|
assertRuntimeActionValid(
|
|
7036
7579
|
Object.keys(payload).length > 0,
|
|
@@ -7045,6 +7588,20 @@ function preflightRuntimeAction(action, preflightContext) {
|
|
|
7045
7588
|
const status = readActionStatus(fields.status);
|
|
7046
7589
|
const comment = readBoundedActionString(fields, "comment", 20_000);
|
|
7047
7590
|
assertRuntimeActionValid(status || comment, "update_parent action requires status or comment");
|
|
7591
|
+
if (fields.status !== undefined) {
|
|
7592
|
+
assertRuntimeActionValid(status, `update_parent status is unsupported: ${String(fields.status)}`);
|
|
7593
|
+
}
|
|
7594
|
+
if (
|
|
7595
|
+
status === "done" &&
|
|
7596
|
+
runtimeActionDoneNeedsExternalHuman(fields, preflightContext) &&
|
|
7597
|
+
!preflightContext.createdHumanOwnedInputPath &&
|
|
7598
|
+
fields.force !== true
|
|
7599
|
+
) {
|
|
7600
|
+
assertRuntimeActionValid(
|
|
7601
|
+
false,
|
|
7602
|
+
"external_input_disposition_required: external input cannot be represented only by a free-text owner; create a human-owned blocking child task or another first-class external dependency path",
|
|
7603
|
+
);
|
|
7604
|
+
}
|
|
7048
7605
|
if (
|
|
7049
7606
|
status === "in_review" &&
|
|
7050
7607
|
!preflightContext.createdReviewPathForInReview &&
|
|
@@ -7057,22 +7614,43 @@ function preflightRuntimeAction(action, preflightContext) {
|
|
|
7057
7614
|
) {
|
|
7058
7615
|
preflightContext.createdReviewPathForInReview = true;
|
|
7059
7616
|
}
|
|
7617
|
+
return;
|
|
7618
|
+
}
|
|
7619
|
+
if (type === "update_company_profile") {
|
|
7620
|
+
const profileFields = ["tagline", "stage", "targetCustomers", "products", "currentPriority", "businessModel", "regions", "brandTone"];
|
|
7621
|
+
assertRuntimeActionValid(
|
|
7622
|
+
profileFields.some((field) => fields[field] === null || readBoundedActionString(fields, field, 2000)),
|
|
7623
|
+
"update_company_profile action requires at least one profile field",
|
|
7624
|
+
);
|
|
7625
|
+
return;
|
|
7626
|
+
}
|
|
7627
|
+
if (type === "create_routine") {
|
|
7628
|
+
assertRuntimeActionValid(readBoundedActionString(fields, "title", 240), "create_routine action requires title");
|
|
7629
|
+
assertRuntimeActionValid(readBoundedActionString(fields, "cronExpression", 120), "create_routine action requires cronExpression");
|
|
7630
|
+
if (fields.priority !== undefined) {
|
|
7631
|
+
assertRuntimeActionValid(readActionPriority(fields.priority), `create_routine priority is unsupported: ${String(fields.priority)}`);
|
|
7632
|
+
}
|
|
7060
7633
|
}
|
|
7061
7634
|
}
|
|
7062
7635
|
|
|
7063
7636
|
function preflightRuntimeActionBatch(actions, options = {}) {
|
|
7064
|
-
|
|
7065
|
-
|
|
7637
|
+
const actionIndexByObject = new Map(actions.map((action, index) => [action, index]));
|
|
7638
|
+
const orderedActions = orderRuntimeActionsByClientKeyDependencies(actions);
|
|
7639
|
+
assertRuntimeActionsAllowedByContinuationScope(orderedActions, options.continuationScope);
|
|
7640
|
+
assertNoAmbiguousApprovalGatedSideEffects(orderedActions);
|
|
7066
7641
|
const preflightContext = {
|
|
7067
7642
|
createdReviewPathForInReview: false,
|
|
7068
7643
|
createdRootBlockingChildForInReview: false,
|
|
7644
|
+
createdHumanOwnedInputPath: false,
|
|
7069
7645
|
answeredQuestionContinuation: options.answeredQuestionContinuation === true,
|
|
7070
7646
|
allowedContentTypes: Array.isArray(options.allowedContentTypes) ? options.allowedContentTypes : [],
|
|
7647
|
+
cwd: readString(options.cwd),
|
|
7071
7648
|
finalCommentBodies: [],
|
|
7072
7649
|
};
|
|
7073
|
-
for (const action of
|
|
7650
|
+
for (const action of orderedActions) {
|
|
7651
|
+
const actionIndex = actionIndexByObject.get(action);
|
|
7074
7652
|
try {
|
|
7075
|
-
preflightRuntimeAction(action, preflightContext);
|
|
7653
|
+
preflightRuntimeAction(action, preflightContext, actionIndex);
|
|
7076
7654
|
const fields = runtimeActionBusinessFields(action);
|
|
7077
7655
|
if (readRuntimeActionType(fields) === "add_comment") {
|
|
7078
7656
|
const body = readBoundedActionString(fields, "body", 20_000)
|
|
@@ -7084,6 +7662,9 @@ function preflightRuntimeActionBatch(actions, options = {}) {
|
|
|
7084
7662
|
if (willRuntimeActionCreateRootBlockingChild(action)) {
|
|
7085
7663
|
preflightContext.createdRootBlockingChildForInReview = true;
|
|
7086
7664
|
}
|
|
7665
|
+
if (willRuntimeActionCreateHumanOwnedInputPath(action)) {
|
|
7666
|
+
preflightContext.createdHumanOwnedInputPath = true;
|
|
7667
|
+
}
|
|
7087
7668
|
if (willRuntimeActionCreateReviewPath(action)) {
|
|
7088
7669
|
preflightContext.createdReviewPathForInReview = true;
|
|
7089
7670
|
}
|
|
@@ -7100,6 +7681,40 @@ function preflightRuntimeActionBatch(actions, options = {}) {
|
|
|
7100
7681
|
throw err;
|
|
7101
7682
|
}
|
|
7102
7683
|
}
|
|
7684
|
+
return orderedActions;
|
|
7685
|
+
}
|
|
7686
|
+
|
|
7687
|
+
function runtimeActionAuthorizationClass(action) {
|
|
7688
|
+
// Keep these high-risk route mappings aligned with server-side checks in
|
|
7689
|
+
// routes/agents.ts (organization_mutation) and routes/issues.ts
|
|
7690
|
+
// (executable_child). Unknown action types intentionally remain governance.
|
|
7691
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7692
|
+
const type = readRuntimeActionType(fields);
|
|
7693
|
+
if (["agent_hire", "create_agent"].includes(type ?? "")) return "organization_mutation";
|
|
7694
|
+
if (
|
|
7695
|
+
type === "create_child_task" &&
|
|
7696
|
+
["todo", "in_progress"].includes(readActionStatus(fields.status) ?? "todo")
|
|
7697
|
+
) return "executable_child";
|
|
7698
|
+
return "task_governance";
|
|
7699
|
+
}
|
|
7700
|
+
|
|
7701
|
+
function assertRuntimeActionsAllowedByAuthorizationEnvelope(actions, envelope) {
|
|
7702
|
+
for (const [actionIndex, action] of actions.entries()) {
|
|
7703
|
+
const requiredAuthorization = runtimeActionAuthorizationClass(action);
|
|
7704
|
+
if (envelope.allowedActionClasses.has(requiredAuthorization)) continue;
|
|
7705
|
+
const type = readRuntimeActionType(runtimeActionBusinessFields(action)) ?? "unknown";
|
|
7706
|
+
const err = runtimeActionValidationError(
|
|
7707
|
+
`${type} action at index ${actionIndex} requires authorization class ${requiredAuthorization}; ` +
|
|
7708
|
+
`source ${envelope.sourceKind ?? "unknown"}:${envelope.sourceId ?? "unknown"} revision ${envelope.sourceRevision ?? "unknown"}`,
|
|
7709
|
+
);
|
|
7710
|
+
err.runtimeActionType = type;
|
|
7711
|
+
err.runtimeActionFieldPath = `actions[${actionIndex}]`;
|
|
7712
|
+
err.runtimeActionRequiredAuthorization = requiredAuthorization;
|
|
7713
|
+
err.runtimeActionAuthorizationSourceKind = envelope.sourceKind;
|
|
7714
|
+
err.runtimeActionAuthorizationSourceId = envelope.sourceId;
|
|
7715
|
+
err.runtimeActionAuthorizationSourceRevision = envelope.sourceRevision;
|
|
7716
|
+
throw err;
|
|
7717
|
+
}
|
|
7103
7718
|
}
|
|
7104
7719
|
|
|
7105
7720
|
function normalizeRuntimeActionPlaceholderText(value) {
|
|
@@ -7130,6 +7745,7 @@ const SINGLE_KEY_RUNTIME_ACTION_TYPES = new Set([
|
|
|
7130
7745
|
"create_child_task",
|
|
7131
7746
|
"create_interaction",
|
|
7132
7747
|
"create_routine",
|
|
7748
|
+
"upsert_document",
|
|
7133
7749
|
"update_company_profile",
|
|
7134
7750
|
"update_parent",
|
|
7135
7751
|
"upload_artifact",
|
|
@@ -7202,6 +7818,10 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7202
7818
|
priority: readActionPriority(fields.priority) ?? "medium",
|
|
7203
7819
|
assigneeAgentId: runtimeChildAssigneeAgentId(fields, runtimeAuth, command, actionContext),
|
|
7204
7820
|
assigneeUserId: readString(fields.assigneeUserId),
|
|
7821
|
+
blockedByIssueIds: runtimeActionBlockedByIssueIds(fields, actionContext),
|
|
7822
|
+
...(Array.isArray(fields.inputWorkProductIds)
|
|
7823
|
+
? { inputWorkProductIds: fields.inputWorkProductIds.map(readString).filter(Boolean) }
|
|
7824
|
+
: {}),
|
|
7205
7825
|
blockParentUntilDone,
|
|
7206
7826
|
originKind: "amaster_runtime_child_task",
|
|
7207
7827
|
originId: issueId,
|
|
@@ -7212,6 +7832,23 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7212
7832
|
idempotencyKey: originFingerprint,
|
|
7213
7833
|
});
|
|
7214
7834
|
const createdIssueId = readString(asRecord(created).id);
|
|
7835
|
+
const persistedAssigneeAgentId = readString(asRecord(created).assigneeAgentId);
|
|
7836
|
+
if (body.assigneeAgentId && persistedAssigneeAgentId !== body.assigneeAgentId) {
|
|
7837
|
+
throw new Error(
|
|
7838
|
+
`runtime_action_postcondition_failed: child assignee mismatch; expected ${body.assigneeAgentId}, received ${persistedAssigneeAgentId ?? "missing"}`,
|
|
7839
|
+
);
|
|
7840
|
+
}
|
|
7841
|
+
if (body.blockedByIssueIds.length > 0) {
|
|
7842
|
+
const persistedBlockerIds = Array.isArray(asRecord(created).blockedBy)
|
|
7843
|
+
? asRecord(created).blockedBy.map((entry) => readString(asRecord(entry).id)).filter(Boolean)
|
|
7844
|
+
: [];
|
|
7845
|
+
const missingBlockerIds = body.blockedByIssueIds.filter((blockerId) => !persistedBlockerIds.includes(blockerId));
|
|
7846
|
+
if (missingBlockerIds.length > 0) {
|
|
7847
|
+
throw new Error(
|
|
7848
|
+
`runtime_action_postcondition_failed: child blockers were not persisted: ${missingBlockerIds.join(", ")}`,
|
|
7849
|
+
);
|
|
7850
|
+
}
|
|
7851
|
+
}
|
|
7215
7852
|
const clientKey = runtimeActionResultKey(fields);
|
|
7216
7853
|
if (createdIssueId) actionContext.createdChildIssueIdByFingerprint.set(duplicateKey, createdIssueId);
|
|
7217
7854
|
if (createdIssueId && clientKey) actionContext.issueIdByClientKey.set(clientKey, createdIssueId);
|
|
@@ -7251,6 +7888,8 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7251
7888
|
adapterType: runtimeActionCurrentAgentAdapterType(command),
|
|
7252
7889
|
runtimeConfig: runtimeActionCurrentAgentRuntimeConnector(command),
|
|
7253
7890
|
});
|
|
7891
|
+
const reportsTo = runtimeActionReportsToAgentId(fields, actionContext);
|
|
7892
|
+
if (reportsTo) body.reportsTo = reportsTo;
|
|
7254
7893
|
const idempotencyKey = runtimeAgentHireActionFingerprint(fields, companyId, issueId, body);
|
|
7255
7894
|
body.idempotencyKey = idempotencyKey;
|
|
7256
7895
|
const created = asRecord(await runtimeApiJson(
|
|
@@ -7264,6 +7903,11 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7264
7903
|
const approval = asRecord(created.approval);
|
|
7265
7904
|
const clientKey = runtimeActionResultKey(fields);
|
|
7266
7905
|
const agentId = readString(agent.id);
|
|
7906
|
+
if (reportsTo && readString(agent.reportsTo) !== reportsTo) {
|
|
7907
|
+
throw new Error(
|
|
7908
|
+
`runtime_action_postcondition_failed: agent reportsTo mismatch; expected ${reportsTo}, received ${readString(agent.reportsTo) ?? "missing"}`,
|
|
7909
|
+
);
|
|
7910
|
+
}
|
|
7267
7911
|
if (agentId && clientKey) actionContext.agentIdByClientKey.set(clientKey, agentId);
|
|
7268
7912
|
return {
|
|
7269
7913
|
type,
|
|
@@ -7309,25 +7953,6 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7309
7953
|
{ key: "idempotencyKey", placeholders: ["artifact-key", "artifact key", "upload-artifact-key", "upload artifact key"] },
|
|
7310
7954
|
]);
|
|
7311
7955
|
const rawPath = readRuntimeArtifactPath(fields);
|
|
7312
|
-
if (!rawPath) {
|
|
7313
|
-
const title = readBoundedActionString(fields, "title", 240) ?? "运行产物";
|
|
7314
|
-
const summary = readBoundedActionString(fields, "summary", 20_000);
|
|
7315
|
-
const body = [
|
|
7316
|
-
`AMaster 未上传产物 \`${title}\`,因为 runtime action 没有提供工作区内文件路径。`,
|
|
7317
|
-
summary ? `\n${summary}` : "",
|
|
7318
|
-
"\n请在后续运行中先把文件写入执行工作区,再用 upload_artifact.path 引用该相对路径。",
|
|
7319
|
-
].join("");
|
|
7320
|
-
const created = await runtimeApiJson(runtimeAuth, "POST", `/api/issues/${encodeURIComponent(issueId)}/comments`, { body });
|
|
7321
|
-
actionContext.finalCommentBodies.push(body);
|
|
7322
|
-
return {
|
|
7323
|
-
type,
|
|
7324
|
-
...(originalType && originalType !== type ? { originalType } : {}),
|
|
7325
|
-
status: "skipped",
|
|
7326
|
-
reason: "missing_path",
|
|
7327
|
-
title,
|
|
7328
|
-
commentId: readString(asRecord(created).id),
|
|
7329
|
-
};
|
|
7330
|
-
}
|
|
7331
7956
|
const payload = asRecord(command.payload);
|
|
7332
7957
|
const companyId = readString(payload.companyId) ?? readString(runtimeAuth.companyId);
|
|
7333
7958
|
if (!companyId) throw new Error("upload_artifact action requires companyId");
|
|
@@ -7349,15 +7974,25 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7349
7974
|
const title = readBoundedActionString(fields, "title", 240) ?? filename;
|
|
7350
7975
|
if (fields.createWorkProduct !== false) {
|
|
7351
7976
|
const existingWorkProducts = await listExistingIssueWorkProducts(runtimeAuth, issueId);
|
|
7352
|
-
const existingWorkProduct = existingExplicitArtifactUploadMatch(filePath, title, existingWorkProducts);
|
|
7977
|
+
const existingWorkProduct = existingExplicitArtifactUploadMatch(filePath, title, existingWorkProducts, fields);
|
|
7353
7978
|
if (existingWorkProduct) {
|
|
7354
7979
|
const metadata = asRecord(existingWorkProduct.metadata);
|
|
7980
|
+
const existingWorkProductId = readString(existingWorkProduct.id);
|
|
7981
|
+
const existingAttachmentId = readString(metadata.attachmentId);
|
|
7982
|
+
if (!existingWorkProductId || !existingAttachmentId) {
|
|
7983
|
+
throw new Error("runtime_action_postcondition_failed: reused artifact is missing work product or attachment identity");
|
|
7984
|
+
}
|
|
7985
|
+
await verifyUploadedArtifactPostcondition(runtimeAuth, issueId, {
|
|
7986
|
+
attachmentId: existingAttachmentId,
|
|
7987
|
+
workProductId: existingWorkProductId,
|
|
7988
|
+
sha256,
|
|
7989
|
+
}, fields);
|
|
7355
7990
|
return {
|
|
7356
7991
|
type,
|
|
7357
7992
|
...(originalType && originalType !== type ? { originalType } : {}),
|
|
7358
7993
|
path: filePath,
|
|
7359
|
-
attachmentId:
|
|
7360
|
-
workProductId:
|
|
7994
|
+
attachmentId: existingAttachmentId,
|
|
7995
|
+
workProductId: existingWorkProductId,
|
|
7361
7996
|
sha256: readString(metadata.sha256) ?? sha256,
|
|
7362
7997
|
byteSize: readNumber(metadata.byteSize, fileBytes.byteLength),
|
|
7363
7998
|
originalFilename: readString(metadata.originalFilename) ?? filename,
|
|
@@ -7379,11 +8014,12 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7379
8014
|
if (!attachmentId) throw new Error("upload_artifact attachment response did not include id");
|
|
7380
8015
|
|
|
7381
8016
|
let workProduct = null;
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
8017
|
+
try {
|
|
8018
|
+
if (fields.createWorkProduct !== false) {
|
|
8019
|
+
const contentPath = readString(attachment.contentPath) ?? `/api/attachments/${attachmentId}/content`;
|
|
8020
|
+
const openPath = readString(attachment.openPath) ?? contentPath;
|
|
8021
|
+
const downloadPath = readString(attachment.downloadPath) ?? `${contentPath}?download=1`;
|
|
8022
|
+
workProduct = asRecord(await runtimeApiJson(runtimeAuth, "POST", `/api/issues/${encodeURIComponent(issueId)}/work-products`, {
|
|
7387
8023
|
type: "artifact",
|
|
7388
8024
|
provider: readString(fields.provider) ?? "paperclip",
|
|
7389
8025
|
title,
|
|
@@ -7402,8 +8038,26 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7402
8038
|
openPath,
|
|
7403
8039
|
downloadPath,
|
|
7404
8040
|
originalFilename: readString(attachment.originalFilename) ?? filename,
|
|
8041
|
+
...(readString(fields.sourceWorkProductId) ? { sourceWorkProductId: readString(fields.sourceWorkProductId) } : {}),
|
|
8042
|
+
...(Array.isArray(fields.sourceWorkProductIds) ? { sourceWorkProductIds: fields.sourceWorkProductIds.map(readString).filter(Boolean) } : {}),
|
|
8043
|
+
...(readString(fields.supersedesWorkProductId) ? { supersedesWorkProductId: readString(fields.supersedesWorkProductId) } : {}),
|
|
8044
|
+
...(readString(fields.evidenceRole) ? { evidenceRole: readString(fields.evidenceRole) } : {}),
|
|
8045
|
+
...(Array.isArray(fields.scenarioKeys) ? { scenarioKeys: fields.scenarioKeys.map(readString).filter(Boolean) } : {}),
|
|
7405
8046
|
},
|
|
7406
|
-
|
|
8047
|
+
}));
|
|
8048
|
+
const workProductId = readString(workProduct.id);
|
|
8049
|
+
if (!workProductId) {
|
|
8050
|
+
throw new Error("runtime_action_postcondition_failed: artifact work product response did not include id");
|
|
8051
|
+
}
|
|
8052
|
+
await verifyUploadedArtifactPostcondition(runtimeAuth, issueId, {
|
|
8053
|
+
attachmentId,
|
|
8054
|
+
workProductId,
|
|
8055
|
+
sha256: readString(attachment.sha256) ?? sha256,
|
|
8056
|
+
}, fields);
|
|
8057
|
+
}
|
|
8058
|
+
} catch (err) {
|
|
8059
|
+
await cleanupFailedArtifactUpload(config, command, runtimeAuth, attachmentId, readString(workProduct?.id), err);
|
|
8060
|
+
throw err;
|
|
7407
8061
|
}
|
|
7408
8062
|
|
|
7409
8063
|
return {
|
|
@@ -7461,6 +8115,37 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7461
8115
|
};
|
|
7462
8116
|
}
|
|
7463
8117
|
|
|
8118
|
+
if (type === "upsert_document") {
|
|
8119
|
+
assertNoPlaceholderRuntimeActionText(fields, ["key", "title", "body", "changeSummary"]);
|
|
8120
|
+
const key = readBoundedActionString(fields, "key", 64);
|
|
8121
|
+
if (!key || !/^[a-z0-9][a-z0-9_-]*$/.test(key)) {
|
|
8122
|
+
throw new Error("upsert_document key must use lowercase letters, numbers, _ or -");
|
|
8123
|
+
}
|
|
8124
|
+
const body = readBoundedActionString(fields, "body", 524_288);
|
|
8125
|
+
if (!body) throw new Error("upsert_document action requires body");
|
|
8126
|
+
if (fields.format !== "markdown") throw new Error("upsert_document format must be markdown");
|
|
8127
|
+
const document = asRecord(await runtimeApiJson(
|
|
8128
|
+
runtimeAuth,
|
|
8129
|
+
"PUT",
|
|
8130
|
+
`/api/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}`,
|
|
8131
|
+
{
|
|
8132
|
+
title: readBoundedActionString(fields, "title", 200) ?? null,
|
|
8133
|
+
format: "markdown",
|
|
8134
|
+
body,
|
|
8135
|
+
changeSummary: readBoundedActionString(fields, "changeSummary", 500) ?? null,
|
|
8136
|
+
baseRevisionId: readString(fields.baseRevisionId) ?? null,
|
|
8137
|
+
},
|
|
8138
|
+
));
|
|
8139
|
+
return {
|
|
8140
|
+
type,
|
|
8141
|
+
...(originalType && originalType !== type ? { originalType } : {}),
|
|
8142
|
+
issueId,
|
|
8143
|
+
key,
|
|
8144
|
+
documentId: readString(document.id),
|
|
8145
|
+
revisionNumber: typeof document.latestRevisionNumber === "number" ? document.latestRevisionNumber : undefined,
|
|
8146
|
+
};
|
|
8147
|
+
}
|
|
8148
|
+
|
|
7464
8149
|
if (type === "update_parent") {
|
|
7465
8150
|
assertNoPlaceholderRuntimeActionText(fields, ["comment"]);
|
|
7466
8151
|
const patch = {};
|
|
@@ -7476,18 +8161,6 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7476
8161
|
if (Object.keys(patch).length === 0) {
|
|
7477
8162
|
throw new Error("update_parent action requires status or comment");
|
|
7478
8163
|
}
|
|
7479
|
-
if (status === "done" && runtimeActionDoneNeedsExternalHuman(fields, actionContext) && fields.force !== true) {
|
|
7480
|
-
Object.assign(patch, runtimeActionExternalHumanPatch(comment));
|
|
7481
|
-
const updated = await runtimeApiJson(runtimeAuth, "PATCH", `/api/issues/${encodeURIComponent(issueId)}`, patch);
|
|
7482
|
-
return {
|
|
7483
|
-
type,
|
|
7484
|
-
...(originalType && originalType !== type ? { originalType } : {}),
|
|
7485
|
-
issueId: readString(asRecord(updated).id),
|
|
7486
|
-
status: readString(asRecord(updated).status) ?? "blocked",
|
|
7487
|
-
coercedFromStatus: "done",
|
|
7488
|
-
externalOwnerAction: true,
|
|
7489
|
-
};
|
|
7490
|
-
}
|
|
7491
8164
|
if (status === "done" && actionContext.createdReviewPathForInReview && fields.force !== true) {
|
|
7492
8165
|
patch.status = "in_review";
|
|
7493
8166
|
patch.comment = comment
|
|
@@ -7743,6 +8416,48 @@ function runtimeActionFailureSummary(action, err) {
|
|
|
7743
8416
|
};
|
|
7744
8417
|
}
|
|
7745
8418
|
|
|
8419
|
+
function runtimeActionLedgerResourceIds(result) {
|
|
8420
|
+
const record = asRecord(result);
|
|
8421
|
+
const resourceIds = {};
|
|
8422
|
+
for (const key of ["issueId", "parentIssueId", "agentId", "approvalId", "commentId", "interactionId", "attachmentId", "workProductId", "routineId", "profileId"]) {
|
|
8423
|
+
const value = readString(record[key]);
|
|
8424
|
+
if (value) resourceIds[key] = value;
|
|
8425
|
+
}
|
|
8426
|
+
return resourceIds;
|
|
8427
|
+
}
|
|
8428
|
+
|
|
8429
|
+
function runtimeActionLedgerPostconditionStatus(action, status, error) {
|
|
8430
|
+
if (status === "failed") {
|
|
8431
|
+
return /runtime_action_postcondition_failed/i.test(String(error instanceof Error ? error.message : error ?? ""))
|
|
8432
|
+
? "failed"
|
|
8433
|
+
: "not_run";
|
|
8434
|
+
}
|
|
8435
|
+
if (status !== "applied" && status !== "reused") return "not_run";
|
|
8436
|
+
return ["create_child_task", "agent_hire", "create_agent", "upload_artifact"].includes(
|
|
8437
|
+
readRuntimeActionType(action) ?? "",
|
|
8438
|
+
)
|
|
8439
|
+
? "passed"
|
|
8440
|
+
: "not_applicable";
|
|
8441
|
+
}
|
|
8442
|
+
|
|
8443
|
+
function runtimeActionLedgerEntry(action, actionIndex, status, result = null, error = null) {
|
|
8444
|
+
const fields = runtimeActionBusinessFields(action);
|
|
8445
|
+
const resultRecord = asRecord(result);
|
|
8446
|
+
const clientKey = readString(fields.clientKey);
|
|
8447
|
+
const resourceIds = runtimeActionLedgerResourceIds(resultRecord);
|
|
8448
|
+
return {
|
|
8449
|
+
actionIndex,
|
|
8450
|
+
actionType: readRuntimeActionType(fields) ?? "unknown",
|
|
8451
|
+
actionKey: clientKey ?? `index:${actionIndex}`,
|
|
8452
|
+
...(clientKey ? { clientKey } : {}),
|
|
8453
|
+
status,
|
|
8454
|
+
reusedExisting: resultRecord.reusedExisting === true,
|
|
8455
|
+
postconditionStatus: runtimeActionLedgerPostconditionStatus(fields, status, error),
|
|
8456
|
+
...(Object.keys(resourceIds).length > 0 ? { resourceIds } : {}),
|
|
8457
|
+
...(error ? { error: truncateText(error instanceof Error ? error.message : String(error), 1000) } : {}),
|
|
8458
|
+
};
|
|
8459
|
+
}
|
|
8460
|
+
|
|
7746
8461
|
function isSkippedRuntimeActionResult(result) {
|
|
7747
8462
|
return readString(asRecord(result).status) === "skipped";
|
|
7748
8463
|
}
|
|
@@ -7886,6 +8601,16 @@ function runtimeActionExpectedShape(actionType, actionKind) {
|
|
|
7886
8601
|
if (actionType === "upload_artifact") {
|
|
7887
8602
|
return { type: "upload_artifact", path: "relative/path.ext", title: "Artifact title", summary: "Artifact summary" };
|
|
7888
8603
|
}
|
|
8604
|
+
if (actionType === "upsert_document") {
|
|
8605
|
+
return {
|
|
8606
|
+
type: "upsert_document",
|
|
8607
|
+
key: "business-brief",
|
|
8608
|
+
title: "2026-07-16 每日经营简报",
|
|
8609
|
+
body: "# 每日经营简报\n\n经营摘要。",
|
|
8610
|
+
format: "markdown",
|
|
8611
|
+
changeSummary: "生成每日经营简报",
|
|
8612
|
+
};
|
|
8613
|
+
}
|
|
7889
8614
|
if (actionType === "agent_hire" || actionType === "create_agent") {
|
|
7890
8615
|
return {
|
|
7891
8616
|
type: actionType,
|
|
@@ -7923,6 +8648,12 @@ function runtimeActionDiagnostics(err, options = {}) {
|
|
|
7923
8648
|
? { decompositionMatch: err.runtimeActionDecompositionMatch }
|
|
7924
8649
|
: {}),
|
|
7925
8650
|
...(err?.runtimeActionInferenceTrace ? { inferenceTrace: err.runtimeActionInferenceTrace } : {}),
|
|
8651
|
+
...(readString(err?.runtimeActionFieldPath) ? { fieldPath: readString(err.runtimeActionFieldPath) } : {}),
|
|
8652
|
+
...(readString(err?.runtimeActionReceivedType) ? { receivedType: readString(err.runtimeActionReceivedType) } : {}),
|
|
8653
|
+
...(readString(err?.runtimeActionRequiredAuthorization) ? { requiredAuthorization: readString(err.runtimeActionRequiredAuthorization) } : {}),
|
|
8654
|
+
...(readString(err?.runtimeActionAuthorizationSourceKind) ? { authorizationSourceKind: readString(err.runtimeActionAuthorizationSourceKind) } : {}),
|
|
8655
|
+
...(readString(err?.runtimeActionAuthorizationSourceId) ? { authorizationSourceId: readString(err.runtimeActionAuthorizationSourceId) } : {}),
|
|
8656
|
+
...(readString(err?.runtimeActionAuthorizationSourceRevision) ? { authorizationSourceRevision: readString(err.runtimeActionAuthorizationSourceRevision) } : {}),
|
|
7926
8657
|
...(Array.isArray(err?.runtimeActionAllowedContentTypes)
|
|
7927
8658
|
? { allowedContentTypes: err.runtimeActionAllowedContentTypes }
|
|
7928
8659
|
: {}),
|
|
@@ -7932,6 +8663,7 @@ function runtimeActionDiagnostics(err, options = {}) {
|
|
|
7932
8663
|
...(err?.runtimeActionVerificationResult
|
|
7933
8664
|
? { verificationResult: err.runtimeActionVerificationResult }
|
|
7934
8665
|
: {}),
|
|
8666
|
+
...(err?.runtimeActionLedger ? { actionLedger: err.runtimeActionLedger } : {}),
|
|
7935
8667
|
...(runtimeActionExpectedShape(actionType, actionKind) ? { expectedShape: runtimeActionExpectedShape(actionType, actionKind) } : {}),
|
|
7936
8668
|
...(typeof err?.httpStatus === "number" ? { backendStatus: err.httpStatus } : {}),
|
|
7937
8669
|
...(backendErrors.length > 0 ? { backendErrors } : {}),
|
|
@@ -7968,7 +8700,7 @@ function classifyRuntimeActionFailure(err) {
|
|
|
7968
8700
|
}
|
|
7969
8701
|
if (actionType === "output_channel") {
|
|
7970
8702
|
return {
|
|
7971
|
-
errorCode: "runtime_action_validation_failed",
|
|
8703
|
+
errorCode: readString(err?.runtimeActionErrorCode) ?? "runtime_action_validation_failed",
|
|
7972
8704
|
errorFamily: "output_contract",
|
|
7973
8705
|
...details,
|
|
7974
8706
|
...(diagnostics ? { runtimeActionDiagnostics: diagnostics } : {}),
|
|
@@ -7985,7 +8717,10 @@ function classifyRuntimeActionFailure(err) {
|
|
|
7985
8717
|
...(diagnostics ? { runtimeActionDiagnostics: diagnostics } : {}),
|
|
7986
8718
|
};
|
|
7987
8719
|
}
|
|
7988
|
-
if (
|
|
8720
|
+
if (
|
|
8721
|
+
actionType === "upload_artifact" &&
|
|
8722
|
+
/unsupported artifact content type|requires path|file does not exist|path must be inside/i.test(haystack)
|
|
8723
|
+
) {
|
|
7989
8724
|
return {
|
|
7990
8725
|
errorCode: "runtime_action_validation_failed",
|
|
7991
8726
|
errorFamily: "output_contract",
|
|
@@ -8009,6 +8744,31 @@ function classifyRuntimeActionFailure(err) {
|
|
|
8009
8744
|
...(diagnostics ? { runtimeActionDiagnostics: diagnostics } : {}),
|
|
8010
8745
|
};
|
|
8011
8746
|
}
|
|
8747
|
+
if (/runtime_action_postcondition_failed/i.test(haystack)) {
|
|
8748
|
+
return {
|
|
8749
|
+
errorCode: "runtime_action_postcondition_failed",
|
|
8750
|
+
errorFamily: "output_contract",
|
|
8751
|
+
...details,
|
|
8752
|
+
...(diagnostics ? { runtimeActionDiagnostics: diagnostics } : {}),
|
|
8753
|
+
};
|
|
8754
|
+
}
|
|
8755
|
+
if (err?.httpStatus === 401) {
|
|
8756
|
+
const authenticationDiagnostics = runtimeActionDiagnostics(err, { retryable: true });
|
|
8757
|
+
return {
|
|
8758
|
+
errorCode: "runtime_action_authentication_failed",
|
|
8759
|
+
errorFamily: "authentication",
|
|
8760
|
+
...details,
|
|
8761
|
+
...(authenticationDiagnostics ? { runtimeActionDiagnostics: authenticationDiagnostics } : {}),
|
|
8762
|
+
};
|
|
8763
|
+
}
|
|
8764
|
+
if (err?.httpStatus === 403) {
|
|
8765
|
+
return {
|
|
8766
|
+
errorCode: "runtime_action_permission_denied",
|
|
8767
|
+
errorFamily: "permission",
|
|
8768
|
+
...details,
|
|
8769
|
+
...(diagnostics ? { runtimeActionDiagnostics: diagnostics } : {}),
|
|
8770
|
+
};
|
|
8771
|
+
}
|
|
8012
8772
|
if (/approval-gated executable child tasks/i.test(haystack)) {
|
|
8013
8773
|
return {
|
|
8014
8774
|
errorCode: "runtime_action_validation_failed",
|
|
@@ -8068,6 +8828,27 @@ function piOutputUsageMetadataMissing(parsed) {
|
|
|
8068
8828
|
return totalTokens <= 0;
|
|
8069
8829
|
}
|
|
8070
8830
|
|
|
8831
|
+
function runtimeBusinessDisposition(runtimeActions, inferredArtifactUploads) {
|
|
8832
|
+
const results = Array.isArray(runtimeActions?.results)
|
|
8833
|
+
? runtimeActions.results.map(asRecord).filter((result) => !isSkippedRuntimeActionResult(result))
|
|
8834
|
+
: [];
|
|
8835
|
+
const terminalUpdate = results.find((result) =>
|
|
8836
|
+
readString(result.type) === "update_parent" &&
|
|
8837
|
+
["done", "cancelled", "blocked", "in_review"].includes(readString(result.status) ?? ""));
|
|
8838
|
+
const terminalStatus = readString(terminalUpdate?.status);
|
|
8839
|
+
if (["done", "cancelled"].includes(terminalStatus ?? "")) {
|
|
8840
|
+
return { state: "completed", reason: `parent_${terminalStatus}` };
|
|
8841
|
+
}
|
|
8842
|
+
if (terminalStatus === "blocked") return { state: "blocked", reason: "parent_blocked" };
|
|
8843
|
+
if (terminalStatus === "in_review" || results.some((result) => readString(result.type) === "create_interaction")) {
|
|
8844
|
+
return { state: "waiting", reason: "review_or_interaction_path_recorded" };
|
|
8845
|
+
}
|
|
8846
|
+
if (results.length > 0 || readNumber(inferredArtifactUploads?.appliedCount, 0) > 0) {
|
|
8847
|
+
return { state: "needs_followup", reason: "durable_progress_without_terminal_disposition" };
|
|
8848
|
+
}
|
|
8849
|
+
return { state: "missing", reason: "no_durable_action_or_live_path" };
|
|
8850
|
+
}
|
|
8851
|
+
|
|
8071
8852
|
function piCompletionOutputStopped(parsed, execution) {
|
|
8072
8853
|
return execution?.timedOut !== true
|
|
8073
8854
|
&& execution?.signal === "SIGTERM"
|
|
@@ -8313,9 +9094,19 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8313
9094
|
err.runtimeActionReasonHint = "assistant_message_required";
|
|
8314
9095
|
throw err;
|
|
8315
9096
|
}
|
|
9097
|
+
if (!envelope && hasMalformedRuntimeActionMarkerIntent(actionText)) {
|
|
9098
|
+
const err = runtimeActionValidationError(
|
|
9099
|
+
"AMASTER_RUNTIME_ACTIONS action intent used a non-canonical marker; the marker must be an unformatted bare line",
|
|
9100
|
+
);
|
|
9101
|
+
err.runtimeActionType = "output_channel";
|
|
9102
|
+
err.runtimeActionReasonHint = "bare_marker_line_required";
|
|
9103
|
+
err.runtimeActionErrorCode = "runtime_action_output_marker_invalid";
|
|
9104
|
+
throw err;
|
|
9105
|
+
}
|
|
8316
9106
|
if (!envelope) return null;
|
|
8317
9107
|
const rawActions = Array.isArray(envelope.actions) ? envelope.actions.map(asRecord) : [];
|
|
8318
9108
|
const continuationScope = runtimeActionContinuationScope(command);
|
|
9109
|
+
const authorizationEnvelope = runtimeActionAuthorizationEnvelope(command);
|
|
8319
9110
|
assertRuntimeActionsAllowedByContinuationScope(rawActions, continuationScope);
|
|
8320
9111
|
assertExplicitTaskDecomposition(command, rawActions);
|
|
8321
9112
|
if (
|
|
@@ -8328,6 +9119,10 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8328
9119
|
const runtimeAuth = runtimeActionAuthForCommand(command);
|
|
8329
9120
|
const issueId = readString(runtimeAuth.issueId) ?? commandIssueId(command);
|
|
8330
9121
|
if (!issueId) throw new Error("AMaster runtime action bridge requires a current issue id");
|
|
9122
|
+
const batchFingerprint = `runtime-action-batch:${stableRuntimeActionHash({
|
|
9123
|
+
runId: runtimeActionRunId(runtimeAuth) ?? commandRunId(command),
|
|
9124
|
+
envelope,
|
|
9125
|
+
})}`;
|
|
8331
9126
|
const allowedContentTypes = runtimeAttachmentContentTypes(command);
|
|
8332
9127
|
let deliverableManifest;
|
|
8333
9128
|
try {
|
|
@@ -8359,20 +9154,39 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8359
9154
|
attachRuntimeCheckpointCandidates(err, envelope, cwd);
|
|
8360
9155
|
throw err;
|
|
8361
9156
|
}
|
|
8362
|
-
|
|
9157
|
+
let actions = orderRuntimeActionsForGovernance([...generatedUploadActions, ...rawActions]);
|
|
8363
9158
|
const answeredQuestionContinuation = runtimeActionAnsweredQuestionContinuation(command);
|
|
8364
9159
|
try {
|
|
8365
|
-
preflightRuntimeActionBatch(actions, {
|
|
9160
|
+
actions = preflightRuntimeActionBatch(actions, {
|
|
8366
9161
|
answeredQuestionContinuation,
|
|
8367
9162
|
allowedContentTypes,
|
|
8368
9163
|
continuationScope,
|
|
9164
|
+
cwd,
|
|
8369
9165
|
});
|
|
9166
|
+
assertRuntimeActionsAllowedByAuthorizationEnvelope(actions, authorizationEnvelope);
|
|
8370
9167
|
} catch (err) {
|
|
9168
|
+
if (err && typeof err === "object" && !err.runtimeActionLedger) {
|
|
9169
|
+
const fieldPath = readString(err.runtimeActionFieldPath);
|
|
9170
|
+
const failedIndex = Number.parseInt(fieldPath?.match(/^actions\[(\d+)]/)?.[1] ?? "0", 10);
|
|
9171
|
+
err.runtimeActionLedger = {
|
|
9172
|
+
batchFingerprint,
|
|
9173
|
+
traceId: batchFingerprint,
|
|
9174
|
+
status: "validation_failed",
|
|
9175
|
+
actions: actions.map((action, index) => runtimeActionLedgerEntry(
|
|
9176
|
+
action,
|
|
9177
|
+
index,
|
|
9178
|
+
index === failedIndex ? "failed" : "pending",
|
|
9179
|
+
null,
|
|
9180
|
+
index === failedIndex ? err : null,
|
|
9181
|
+
)),
|
|
9182
|
+
};
|
|
9183
|
+
}
|
|
8371
9184
|
attachRuntimeCheckpointCandidates(err, envelope, cwd);
|
|
8372
9185
|
throw err;
|
|
8373
9186
|
}
|
|
8374
9187
|
const results = [];
|
|
8375
9188
|
const skipped = [];
|
|
9189
|
+
const actionLedgerEntries = [];
|
|
8376
9190
|
const actionContext = {
|
|
8377
9191
|
rootIssueId: issueId,
|
|
8378
9192
|
cwd,
|
|
@@ -8386,10 +9200,16 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8386
9200
|
answeredQuestionContinuation,
|
|
8387
9201
|
finalCommentBodies: [],
|
|
8388
9202
|
};
|
|
8389
|
-
for (const action of actions) {
|
|
9203
|
+
for (const [actionIndex, action] of actions.entries()) {
|
|
8390
9204
|
try {
|
|
8391
9205
|
const result = await applyRuntimeAction(config, command, runtimeAuth, issueId, action, actionContext);
|
|
8392
9206
|
results.push(result);
|
|
9207
|
+
actionLedgerEntries.push(runtimeActionLedgerEntry(
|
|
9208
|
+
action,
|
|
9209
|
+
actionIndex,
|
|
9210
|
+
asRecord(result).reusedExisting === true ? "reused" : "applied",
|
|
9211
|
+
result,
|
|
9212
|
+
));
|
|
8393
9213
|
if (isSkippedRuntimeActionResult(result)) {
|
|
8394
9214
|
skipped.push(result);
|
|
8395
9215
|
}
|
|
@@ -8418,6 +9238,17 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8418
9238
|
err.runtimeActionOriginalType = readRuntimeActionOriginalType(action);
|
|
8419
9239
|
err.runtimeActionKind = readString(action.kind) ?? readString(asRecord(action.payload).kind);
|
|
8420
9240
|
err.runtimeActionReason = truncateText(err instanceof Error ? err.message : String(err), 1000);
|
|
9241
|
+
err.runtimeActionLedger = {
|
|
9242
|
+
batchFingerprint,
|
|
9243
|
+
traceId: batchFingerprint,
|
|
9244
|
+
status: "partial_failed",
|
|
9245
|
+
actions: [
|
|
9246
|
+
...actionLedgerEntries,
|
|
9247
|
+
runtimeActionLedgerEntry(action, actionIndex, "failed", null, err),
|
|
9248
|
+
...actions.slice(actionIndex + 1).map((pendingAction, offset) =>
|
|
9249
|
+
runtimeActionLedgerEntry(pendingAction, actionIndex + offset + 1, "pending")),
|
|
9250
|
+
],
|
|
9251
|
+
};
|
|
8421
9252
|
}
|
|
8422
9253
|
attachRuntimeCheckpointCandidates(err, envelope, cwd);
|
|
8423
9254
|
throw err;
|
|
@@ -8425,6 +9256,7 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8425
9256
|
const skippedResult = runtimeActionFailureSummary(action, err);
|
|
8426
9257
|
skipped.push(skippedResult);
|
|
8427
9258
|
results.push(skippedResult);
|
|
9259
|
+
actionLedgerEntries.push(runtimeActionLedgerEntry(action, actionIndex, "skipped", skippedResult, err));
|
|
8428
9260
|
await ingestLog(config, command, "system", "warn", `Skipped AMaster runtime action: ${skippedResult.reason}`, {
|
|
8429
9261
|
runtimeAction: skippedResult,
|
|
8430
9262
|
});
|
|
@@ -8432,8 +9264,19 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
8432
9264
|
}
|
|
8433
9265
|
return {
|
|
8434
9266
|
version: readNumber(envelope.version, 1),
|
|
9267
|
+
markerFound: true,
|
|
9268
|
+
jsonParsed: true,
|
|
9269
|
+
actionsValidated: true,
|
|
9270
|
+
postconditionsPassed: true,
|
|
9271
|
+
actionLedger: {
|
|
9272
|
+
batchFingerprint,
|
|
9273
|
+
traceId: batchFingerprint,
|
|
9274
|
+
status: "completed",
|
|
9275
|
+
actions: actionLedgerEntries,
|
|
9276
|
+
},
|
|
8435
9277
|
requestedCount: actions.length,
|
|
8436
9278
|
appliedCount: results.length - skipped.length,
|
|
9279
|
+
actionsApplied: results.length - skipped.length,
|
|
8437
9280
|
skippedCount: skipped.length,
|
|
8438
9281
|
results,
|
|
8439
9282
|
deliverableManifest: {
|
|
@@ -8464,6 +9307,72 @@ async function listExistingIssueWorkProducts(runtimeAuth, issueId) {
|
|
|
8464
9307
|
}
|
|
8465
9308
|
}
|
|
8466
9309
|
|
|
9310
|
+
async function verifyUploadedArtifactPostcondition(runtimeAuth, issueId, expected, fields) {
|
|
9311
|
+
const [workProducts, attachments] = await Promise.all([
|
|
9312
|
+
runtimeApiJson(runtimeAuth, "GET", `/api/issues/${encodeURIComponent(issueId)}/work-products`),
|
|
9313
|
+
runtimeApiJson(runtimeAuth, "GET", `/api/issues/${encodeURIComponent(issueId)}/attachments`),
|
|
9314
|
+
]);
|
|
9315
|
+
const workProduct = Array.isArray(workProducts)
|
|
9316
|
+
? workProducts.map(asRecord).find((entry) => readString(entry.id) === expected.workProductId)
|
|
9317
|
+
: null;
|
|
9318
|
+
const attachment = Array.isArray(attachments)
|
|
9319
|
+
? attachments.map(asRecord).find((entry) => readString(entry.id) === expected.attachmentId)
|
|
9320
|
+
: null;
|
|
9321
|
+
const metadata = asRecord(workProduct?.metadata);
|
|
9322
|
+
const expectedSourceIds = Array.isArray(fields.sourceWorkProductIds)
|
|
9323
|
+
? fields.sourceWorkProductIds.map(readString).filter(Boolean).sort()
|
|
9324
|
+
: [];
|
|
9325
|
+
const persistedSourceIds = Array.isArray(metadata.sourceWorkProductIds)
|
|
9326
|
+
? metadata.sourceWorkProductIds.map(readString).filter(Boolean).sort()
|
|
9327
|
+
: [];
|
|
9328
|
+
const expectedScenarioKeys = Array.isArray(fields.scenarioKeys)
|
|
9329
|
+
? fields.scenarioKeys.map(readString).filter(Boolean).sort()
|
|
9330
|
+
: [];
|
|
9331
|
+
const persistedScenarioKeys = Array.isArray(metadata.scenarioKeys)
|
|
9332
|
+
? metadata.scenarioKeys.map(readString).filter(Boolean).sort()
|
|
9333
|
+
: [];
|
|
9334
|
+
const failures = [
|
|
9335
|
+
!workProduct ? "work product missing" : null,
|
|
9336
|
+
!attachment ? "attachment missing" : null,
|
|
9337
|
+
readString(metadata.attachmentId) !== expected.attachmentId ? "work product attachmentId mismatch" : null,
|
|
9338
|
+
readString(metadata.sha256) !== expected.sha256 ? "work product SHA mismatch" : null,
|
|
9339
|
+
readString(attachment?.sha256) !== expected.sha256 ? "attachment SHA mismatch" : null,
|
|
9340
|
+
readString(metadata.sourceWorkProductId) !== readString(fields.sourceWorkProductId) ? "sourceWorkProductId mismatch" : null,
|
|
9341
|
+
readString(metadata.supersedesWorkProductId) !== readString(fields.supersedesWorkProductId) ? "supersedesWorkProductId mismatch" : null,
|
|
9342
|
+
readString(metadata.evidenceRole) !== readString(fields.evidenceRole) ? "evidenceRole mismatch" : null,
|
|
9343
|
+
JSON.stringify(persistedSourceIds) !== JSON.stringify(expectedSourceIds) ? "sourceWorkProductIds mismatch" : null,
|
|
9344
|
+
JSON.stringify(persistedScenarioKeys) !== JSON.stringify(expectedScenarioKeys) ? "scenarioKeys mismatch" : null,
|
|
9345
|
+
].filter(Boolean);
|
|
9346
|
+
if (failures.length > 0) {
|
|
9347
|
+
throw new Error(`runtime_action_postcondition_failed: artifact upload readback failed: ${failures.join("; ")}`);
|
|
9348
|
+
}
|
|
9349
|
+
}
|
|
9350
|
+
|
|
9351
|
+
async function cleanupFailedArtifactUpload(config, command, runtimeAuth, attachmentId, workProductId, cause) {
|
|
9352
|
+
const cleanupFailures = [];
|
|
9353
|
+
if (workProductId) {
|
|
9354
|
+
try {
|
|
9355
|
+
await runtimeApiJson(runtimeAuth, "DELETE", `/api/work-products/${encodeURIComponent(workProductId)}`);
|
|
9356
|
+
} catch (err) {
|
|
9357
|
+
cleanupFailures.push(`workProduct=${err instanceof Error ? err.message : String(err)}`);
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
try {
|
|
9361
|
+
await runtimeApiJson(runtimeAuth, "DELETE", `/api/attachments/${encodeURIComponent(attachmentId)}`);
|
|
9362
|
+
} catch (err) {
|
|
9363
|
+
cleanupFailures.push(`attachment=${err instanceof Error ? err.message : String(err)}`);
|
|
9364
|
+
}
|
|
9365
|
+
await ingestLog(config, command, "system", "error", "Artifact upload postcondition failed", {
|
|
9366
|
+
attachmentId,
|
|
9367
|
+
workProductId: workProductId ?? null,
|
|
9368
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
9369
|
+
cleanupFailures,
|
|
9370
|
+
});
|
|
9371
|
+
if (cleanupFailures.length > 0) {
|
|
9372
|
+
throw new Error(`${cause instanceof Error ? cause.message : String(cause)}; cleanup failed: ${cleanupFailures.join("; ")}`);
|
|
9373
|
+
}
|
|
9374
|
+
}
|
|
9375
|
+
|
|
8467
9376
|
function existingArtifactUploadMatches(artifact, existingWorkProducts) {
|
|
8468
9377
|
const filename = basename(artifact.path);
|
|
8469
9378
|
const byteSize = statSync(artifact.path).size;
|
|
@@ -8489,7 +9398,7 @@ function existingArtifactUploadMatches(artifact, existingWorkProducts) {
|
|
|
8489
9398
|
});
|
|
8490
9399
|
}
|
|
8491
9400
|
|
|
8492
|
-
function existingExplicitArtifactUploadMatch(filePath, title, existingWorkProducts) {
|
|
9401
|
+
function existingExplicitArtifactUploadMatch(filePath, title, existingWorkProducts, fields = {}) {
|
|
8493
9402
|
const filename = basename(filePath);
|
|
8494
9403
|
const byteSize = statSync(filePath).size;
|
|
8495
9404
|
const sha256 = hashFileSha256(filePath);
|
|
@@ -8500,6 +9409,22 @@ function existingExplicitArtifactUploadMatch(filePath, title, existingWorkProduc
|
|
|
8500
9409
|
if (readString(workProduct.provider) !== "paperclip") return false;
|
|
8501
9410
|
if (readString(workProduct.status) === "archived") return false;
|
|
8502
9411
|
const metadata = asRecord(workProduct.metadata);
|
|
9412
|
+
const sourceWorkProductId = readString(fields.sourceWorkProductId);
|
|
9413
|
+
const sourceWorkProductIds = Array.isArray(fields.sourceWorkProductIds)
|
|
9414
|
+
? fields.sourceWorkProductIds.map(readString).filter(Boolean).sort()
|
|
9415
|
+
: [];
|
|
9416
|
+
const supersedesWorkProductId = readString(fields.supersedesWorkProductId);
|
|
9417
|
+
const evidenceRole = readString(fields.evidenceRole);
|
|
9418
|
+
const scenarioKeys = Array.isArray(fields.scenarioKeys) ? fields.scenarioKeys.map(readString).filter(Boolean).sort() : [];
|
|
9419
|
+
if (sourceWorkProductId || supersedesWorkProductId || sourceWorkProductIds.length > 0 || evidenceRole || scenarioKeys.length > 0) {
|
|
9420
|
+
if (readString(metadata.sourceWorkProductId) !== sourceWorkProductId) return false;
|
|
9421
|
+
if (readString(metadata.supersedesWorkProductId) !== supersedesWorkProductId) return false;
|
|
9422
|
+
const existingSourceIds = Array.isArray(metadata.sourceWorkProductIds) ? metadata.sourceWorkProductIds.map(readString).filter(Boolean).sort() : [];
|
|
9423
|
+
const existingScenarioKeys = Array.isArray(metadata.scenarioKeys) ? metadata.scenarioKeys.map(readString).filter(Boolean).sort() : [];
|
|
9424
|
+
if (JSON.stringify(existingSourceIds) !== JSON.stringify(sourceWorkProductIds)) return false;
|
|
9425
|
+
if (readString(metadata.evidenceRole) !== evidenceRole) return false;
|
|
9426
|
+
if (JSON.stringify(existingScenarioKeys) !== JSON.stringify(scenarioKeys)) return false;
|
|
9427
|
+
}
|
|
8503
9428
|
const existingSha = readString(metadata.sha256) ?? readString(workProduct.sha256);
|
|
8504
9429
|
if (existingSha) return existingSha === sha256;
|
|
8505
9430
|
|
|
@@ -8622,9 +9547,10 @@ async function executeRunCommand(config, command) {
|
|
|
8622
9547
|
});
|
|
8623
9548
|
}
|
|
8624
9549
|
await materializeIssueCheckpoint(config, command, workspace);
|
|
8625
|
-
|
|
9550
|
+
const requiredArtifactInputs = await materializeRequiredArtifactInputs(config, command, workspace);
|
|
9551
|
+
let materializedAttachments = requiredArtifactInputs;
|
|
8626
9552
|
try {
|
|
8627
|
-
materializedAttachments = await materializeIssueAttachments(config, command, workspace);
|
|
9553
|
+
materializedAttachments = [...requiredArtifactInputs, ...await materializeIssueAttachments(config, command, workspace)];
|
|
8628
9554
|
} catch (err) {
|
|
8629
9555
|
await ingestLog(config, command, "system", "warn", `Failed to materialize issue attachments: ${err instanceof Error ? err.message : String(err)}`, {
|
|
8630
9556
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -8849,6 +9775,7 @@ async function executeRunCommand(config, command) {
|
|
|
8849
9775
|
? "Executor cancelled by AMaster control plane"
|
|
8850
9776
|
: execution.spawnError ?? parsedErrorMessage ?? runtimeActionError ?? inferredArtifactError ?? (succeeded ? null : `Executor exited with code ${execution.exitCode ?? "unknown"}`);
|
|
8851
9777
|
const artifactUploadSummary = runtimeArtifactUploadSummary(runtimeActions, inferredArtifactUploads);
|
|
9778
|
+
const businessDisposition = runtimeBusinessDisposition(runtimeActions, inferredArtifactUploads);
|
|
8852
9779
|
const costUsage = parsedCostUsage(parsed.usage);
|
|
8853
9780
|
const result = {
|
|
8854
9781
|
executorKind: executor.kind,
|
|
@@ -8919,6 +9846,7 @@ async function executeRunCommand(config, command) {
|
|
|
8919
9846
|
...(runtimeActions ? { runtimeActions } : {}),
|
|
8920
9847
|
...(inferredArtifactUploads ? { inferredArtifactUploads } : {}),
|
|
8921
9848
|
...(artifactUploadSummary ? { artifactUploadSummary } : {}),
|
|
9849
|
+
businessDisposition,
|
|
8922
9850
|
};
|
|
8923
9851
|
const completion = await completeCommand(config, command, cancelled ? "cancelled" : succeeded ? "succeeded" : "failed", result, error ?? undefined);
|
|
8924
9852
|
if (!completion) {
|
|
@@ -9113,6 +10041,26 @@ function statusSummary(config) {
|
|
|
9113
10041
|
}), null, 2)}\n`);
|
|
9114
10042
|
}
|
|
9115
10043
|
|
|
10044
|
+
function validateRuntimeActionsFile(config, flags) {
|
|
10045
|
+
const rawFile = readString(flags.file);
|
|
10046
|
+
if (!rawFile) throw new Error("--file is required");
|
|
10047
|
+
const cwd = resolve(readString(flags.cwd) ?? process.cwd());
|
|
10048
|
+
const file = resolve(cwd, rawFile);
|
|
10049
|
+
const allowedContentTypes = splitList(flags["allowed-content-types"]);
|
|
10050
|
+
const envelope = JSON.parse(readFileSync(file, "utf8"));
|
|
10051
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
10052
|
+
throw runtimeActionValidationError("runtime action envelope must be a JSON object");
|
|
10053
|
+
}
|
|
10054
|
+
assertRuntimeActionValid(envelope.version === 1, "runtime action envelope version must be 1");
|
|
10055
|
+
assertRuntimeActionValid(Array.isArray(envelope.actions), "runtime action envelope actions must be an array");
|
|
10056
|
+
assertRuntimeActionValid(envelope.actions.length <= 20, "runtime action envelope actions exceed the limit of 20");
|
|
10057
|
+
|
|
10058
|
+
preflightRuntimeDeliverables(config, envelope, cwd, allowedContentTypes);
|
|
10059
|
+
const actions = orderRuntimeActionsForGovernance(envelope.actions.map(asRecord));
|
|
10060
|
+
preflightRuntimeActionBatch(actions, { cwd, allowedContentTypes });
|
|
10061
|
+
process.stdout.write(`${JSON.stringify({ valid: true, actionCount: actions.length }, null, 2)}\n`);
|
|
10062
|
+
}
|
|
10063
|
+
|
|
9116
10064
|
async function runLoop(config) {
|
|
9117
10065
|
config = await ensureRegistered(config);
|
|
9118
10066
|
let startupOrphanCheckPending = true;
|
|
@@ -9146,6 +10094,7 @@ Commands:
|
|
|
9146
10094
|
run-once Register if needed, heartbeat, poll once, execute leased commands, then exit.
|
|
9147
10095
|
workspace-gc-dry-run Print managed runtime workdir GC candidates without deleting anything.
|
|
9148
10096
|
status-summary Print safe local daemon storage/outbox summary JSON.
|
|
10097
|
+
validate-actions Validate a JSON action envelope with --file, --cwd, and --allowed-content-types.
|
|
9149
10098
|
start | run Register if needed, then heartbeat and poll in a foreground loop.
|
|
9150
10099
|
|
|
9151
10100
|
Key env:
|
|
@@ -9197,6 +10146,9 @@ async function main() {
|
|
|
9197
10146
|
case "status-summary":
|
|
9198
10147
|
statusSummary(config);
|
|
9199
10148
|
break;
|
|
10149
|
+
case "validate-actions":
|
|
10150
|
+
validateRuntimeActionsFile(config, flags);
|
|
10151
|
+
break;
|
|
9200
10152
|
case "start":
|
|
9201
10153
|
case "run":
|
|
9202
10154
|
await runLoop(config);
|