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