@amaster.ai/employee-runtime-connector 0.1.0-beta.7 → 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 +320 -15
- 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(", ");
|
|
@@ -3530,6 +3542,10 @@ 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.",
|
|
@@ -3567,6 +3583,7 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3567
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.`
|
|
3568
3584
|
: "",
|
|
3569
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.",
|
|
3570
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.",
|
|
3571
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.",
|
|
3572
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.",
|
|
@@ -3580,6 +3597,7 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3580
3597
|
"upload_artifact actions do not accept required. Artifact required belongs only to top-level deliverables entries, not action objects.",
|
|
3581
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.",
|
|
3582
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.",
|
|
3583
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.",
|
|
3584
3602
|
"For required deliverables, add top-level deliverables entries using {\"path\":\"relative/file\",\"required\":true}.",
|
|
3585
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.",
|
|
@@ -3870,7 +3888,9 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3870
3888
|
const agentInstructions = renderAgentInstructionsBundle(asRecord(payload.agentInstructionsBundle));
|
|
3871
3889
|
const runtimeAuth = commandRuntimeAuth(command);
|
|
3872
3890
|
const hasRuntimeApi = Boolean(readString(runtimeAuth.apiUrl) && readString(runtimeAuth.apiKey));
|
|
3873
|
-
const
|
|
3891
|
+
const authorizationEnvelope = runtimeActionAuthorizationEnvelope(command);
|
|
3892
|
+
const canOrchestrateOrganization = canRuntimeAgentOrchestrateOrganization(context)
|
|
3893
|
+
&& authorizationEnvelope.allowedActionClasses.has("organization_mutation");
|
|
3874
3894
|
const decompositionRequirement = commandLegacyTaskDecompositionRequirement(command);
|
|
3875
3895
|
const explicitDecompositionRequired = decompositionRequirement.required
|
|
3876
3896
|
&& runtimeActionChildIssueSummaryEntries(command).length === 0;
|
|
@@ -3908,6 +3928,7 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3908
3928
|
explicitDecompositionRequired,
|
|
3909
3929
|
artifactVerifierCommands: options.artifactVerifierCommands,
|
|
3910
3930
|
actionValidatorCommand,
|
|
3931
|
+
authorizationEnvelope,
|
|
3911
3932
|
}) : "",
|
|
3912
3933
|
"",
|
|
3913
3934
|
agentInstructions,
|
|
@@ -5755,6 +5776,97 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
5755
5776
|
return materialized;
|
|
5756
5777
|
}
|
|
5757
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
|
+
|
|
5758
5870
|
function issueCheckpointDir(workspace) {
|
|
5759
5871
|
return join(dirname(workspace.runDir), "checkpoint");
|
|
5760
5872
|
}
|
|
@@ -6055,6 +6167,9 @@ function runtimeChildActionFingerprint(action, issueId, parentIssueId) {
|
|
|
6055
6167
|
status: readActionStatus(action.status) ?? null,
|
|
6056
6168
|
priority: readActionPriority(action.priority) ?? null,
|
|
6057
6169
|
parentClientKey: readString(action.parentClientKey) ?? null,
|
|
6170
|
+
inputWorkProductIds: Array.isArray(action.inputWorkProductIds)
|
|
6171
|
+
? action.inputWorkProductIds.map(readString).filter(Boolean).sort()
|
|
6172
|
+
: [],
|
|
6058
6173
|
blockedByClientKeys: Array.isArray(action.blockedByClientKeys)
|
|
6059
6174
|
? action.blockedByClientKeys.map(readString).filter(Boolean).sort()
|
|
6060
6175
|
: [],
|
|
@@ -6934,6 +7049,7 @@ const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
|
|
|
6934
7049
|
"title", "description", "status", "priority", "assigneeAgentId", "assigneeUserId",
|
|
6935
7050
|
"assigneeAgentClientKey", "agentClientKey", "blockParentUntilDone", "executeBeforeConfirmation",
|
|
6936
7051
|
"clientKey", "idempotencyKey", "parentClientKey", "blockedByIssueIds", "blockedByClientKeys", "create_child_task",
|
|
7052
|
+
"inputWorkProductIds",
|
|
6937
7053
|
])],
|
|
6938
7054
|
["agent_hire", new Set([
|
|
6939
7055
|
"clientKey", "idempotencyKey", "key", "companyId", "role", "title", "icon", "reportsTo",
|
|
@@ -6954,6 +7070,9 @@ const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
|
|
|
6954
7070
|
"sourceWorkProductId", "sourceWorkProductIds", "supersedesWorkProductId", "evidenceRole", "scenarioKeys",
|
|
6955
7071
|
"idempotencyKey", "upload_artifact",
|
|
6956
7072
|
])],
|
|
7073
|
+
["upsert_document", new Set([
|
|
7074
|
+
"key", "title", "body", "format", "changeSummary", "baseRevisionId", "upsert_document",
|
|
7075
|
+
])],
|
|
6957
7076
|
["create_interaction", new Set([
|
|
6958
7077
|
"kind", "continuationPolicy", "title", "summary", "idempotencyKey", "interaction", "version",
|
|
6959
7078
|
"prompt", "question", "body", "content", "text", "message", "description", "acceptLabel",
|
|
@@ -7008,6 +7127,7 @@ const RUNTIME_ACTION_NUMBER_FIELDS = new Set(["budgetMonthlyCents", "minSelected
|
|
|
7008
7127
|
const RUNTIME_ACTION_ARRAY_FIELDS = new Set([
|
|
7009
7128
|
"blockedByIssueIds", "blockedByClientKeys", "desiredSkills", "sourceIssueIds", "artifactRefs", "tasks",
|
|
7010
7129
|
"options", "defaultSelectedOptionIds", "questions", "sourceWorkProductIds", "scenarioKeys",
|
|
7130
|
+
"inputWorkProductIds",
|
|
7011
7131
|
]);
|
|
7012
7132
|
const RUNTIME_ACTION_OBJECT_FIELDS = new Set([
|
|
7013
7133
|
"adapterConfig", "instructionsBundle", "runtimeConfig", "permissions", "metadata", "target",
|
|
@@ -7358,6 +7478,21 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
|
7358
7478
|
);
|
|
7359
7479
|
}
|
|
7360
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
|
+
}
|
|
7361
7496
|
return;
|
|
7362
7497
|
}
|
|
7363
7498
|
if (["agent_hire", "create_agent"].includes(type ?? "")) {
|
|
@@ -7413,6 +7548,20 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
|
7413
7548
|
resolveWorkspaceArtifactPath(fields, { cwd: preflightContext.cwd });
|
|
7414
7549
|
return;
|
|
7415
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
|
+
);
|
|
7563
|
+
return;
|
|
7564
|
+
}
|
|
7416
7565
|
if (type === "create_interaction") {
|
|
7417
7566
|
assertNoPlaceholderRuntimeActionText(fields, ["title", "summary", "payload"]);
|
|
7418
7567
|
assertCanonicalCreateInteractionShape(fields);
|
|
@@ -7596,6 +7745,7 @@ const SINGLE_KEY_RUNTIME_ACTION_TYPES = new Set([
|
|
|
7596
7745
|
"create_child_task",
|
|
7597
7746
|
"create_interaction",
|
|
7598
7747
|
"create_routine",
|
|
7748
|
+
"upsert_document",
|
|
7599
7749
|
"update_company_profile",
|
|
7600
7750
|
"update_parent",
|
|
7601
7751
|
"upload_artifact",
|
|
@@ -7669,6 +7819,9 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7669
7819
|
assigneeAgentId: runtimeChildAssigneeAgentId(fields, runtimeAuth, command, actionContext),
|
|
7670
7820
|
assigneeUserId: readString(fields.assigneeUserId),
|
|
7671
7821
|
blockedByIssueIds: runtimeActionBlockedByIssueIds(fields, actionContext),
|
|
7822
|
+
...(Array.isArray(fields.inputWorkProductIds)
|
|
7823
|
+
? { inputWorkProductIds: fields.inputWorkProductIds.map(readString).filter(Boolean) }
|
|
7824
|
+
: {}),
|
|
7672
7825
|
blockParentUntilDone,
|
|
7673
7826
|
originKind: "amaster_runtime_child_task",
|
|
7674
7827
|
originId: issueId,
|
|
@@ -7824,12 +7977,22 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7824
7977
|
const existingWorkProduct = existingExplicitArtifactUploadMatch(filePath, title, existingWorkProducts, fields);
|
|
7825
7978
|
if (existingWorkProduct) {
|
|
7826
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);
|
|
7827
7990
|
return {
|
|
7828
7991
|
type,
|
|
7829
7992
|
...(originalType && originalType !== type ? { originalType } : {}),
|
|
7830
7993
|
path: filePath,
|
|
7831
|
-
attachmentId:
|
|
7832
|
-
workProductId:
|
|
7994
|
+
attachmentId: existingAttachmentId,
|
|
7995
|
+
workProductId: existingWorkProductId,
|
|
7833
7996
|
sha256: readString(metadata.sha256) ?? sha256,
|
|
7834
7997
|
byteSize: readNumber(metadata.byteSize, fileBytes.byteLength),
|
|
7835
7998
|
originalFilename: readString(metadata.originalFilename) ?? filename,
|
|
@@ -7851,11 +8014,12 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7851
8014
|
if (!attachmentId) throw new Error("upload_artifact attachment response did not include id");
|
|
7852
8015
|
|
|
7853
8016
|
let workProduct = null;
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
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`, {
|
|
7859
8023
|
type: "artifact",
|
|
7860
8024
|
provider: readString(fields.provider) ?? "paperclip",
|
|
7861
8025
|
title,
|
|
@@ -7880,10 +8044,20 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7880
8044
|
...(readString(fields.evidenceRole) ? { evidenceRole: readString(fields.evidenceRole) } : {}),
|
|
7881
8045
|
...(Array.isArray(fields.scenarioKeys) ? { scenarioKeys: fields.scenarioKeys.map(readString).filter(Boolean) } : {}),
|
|
7882
8046
|
},
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
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);
|
|
7886
8057
|
}
|
|
8058
|
+
} catch (err) {
|
|
8059
|
+
await cleanupFailedArtifactUpload(config, command, runtimeAuth, attachmentId, readString(workProduct?.id), err);
|
|
8060
|
+
throw err;
|
|
7887
8061
|
}
|
|
7888
8062
|
|
|
7889
8063
|
return {
|
|
@@ -7941,6 +8115,37 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
7941
8115
|
};
|
|
7942
8116
|
}
|
|
7943
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
|
+
|
|
7944
8149
|
if (type === "update_parent") {
|
|
7945
8150
|
assertNoPlaceholderRuntimeActionText(fields, ["comment"]);
|
|
7946
8151
|
const patch = {};
|
|
@@ -8396,6 +8601,16 @@ function runtimeActionExpectedShape(actionType, actionKind) {
|
|
|
8396
8601
|
if (actionType === "upload_artifact") {
|
|
8397
8602
|
return { type: "upload_artifact", path: "relative/path.ext", title: "Artifact title", summary: "Artifact summary" };
|
|
8398
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
|
+
}
|
|
8399
8614
|
if (actionType === "agent_hire" || actionType === "create_agent") {
|
|
8400
8615
|
return {
|
|
8401
8616
|
type: actionType,
|
|
@@ -8613,6 +8828,27 @@ function piOutputUsageMetadataMissing(parsed) {
|
|
|
8613
8828
|
return totalTokens <= 0;
|
|
8614
8829
|
}
|
|
8615
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
|
+
|
|
8616
8852
|
function piCompletionOutputStopped(parsed, execution) {
|
|
8617
8853
|
return execution?.timedOut !== true
|
|
8618
8854
|
&& execution?.signal === "SIGTERM"
|
|
@@ -9071,6 +9307,72 @@ async function listExistingIssueWorkProducts(runtimeAuth, issueId) {
|
|
|
9071
9307
|
}
|
|
9072
9308
|
}
|
|
9073
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
|
+
|
|
9074
9376
|
function existingArtifactUploadMatches(artifact, existingWorkProducts) {
|
|
9075
9377
|
const filename = basename(artifact.path);
|
|
9076
9378
|
const byteSize = statSync(artifact.path).size;
|
|
@@ -9245,9 +9547,10 @@ async function executeRunCommand(config, command) {
|
|
|
9245
9547
|
});
|
|
9246
9548
|
}
|
|
9247
9549
|
await materializeIssueCheckpoint(config, command, workspace);
|
|
9248
|
-
|
|
9550
|
+
const requiredArtifactInputs = await materializeRequiredArtifactInputs(config, command, workspace);
|
|
9551
|
+
let materializedAttachments = requiredArtifactInputs;
|
|
9249
9552
|
try {
|
|
9250
|
-
materializedAttachments = await materializeIssueAttachments(config, command, workspace);
|
|
9553
|
+
materializedAttachments = [...requiredArtifactInputs, ...await materializeIssueAttachments(config, command, workspace)];
|
|
9251
9554
|
} catch (err) {
|
|
9252
9555
|
await ingestLog(config, command, "system", "warn", `Failed to materialize issue attachments: ${err instanceof Error ? err.message : String(err)}`, {
|
|
9253
9556
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -9472,6 +9775,7 @@ async function executeRunCommand(config, command) {
|
|
|
9472
9775
|
? "Executor cancelled by AMaster control plane"
|
|
9473
9776
|
: execution.spawnError ?? parsedErrorMessage ?? runtimeActionError ?? inferredArtifactError ?? (succeeded ? null : `Executor exited with code ${execution.exitCode ?? "unknown"}`);
|
|
9474
9777
|
const artifactUploadSummary = runtimeArtifactUploadSummary(runtimeActions, inferredArtifactUploads);
|
|
9778
|
+
const businessDisposition = runtimeBusinessDisposition(runtimeActions, inferredArtifactUploads);
|
|
9475
9779
|
const costUsage = parsedCostUsage(parsed.usage);
|
|
9476
9780
|
const result = {
|
|
9477
9781
|
executorKind: executor.kind,
|
|
@@ -9542,6 +9846,7 @@ async function executeRunCommand(config, command) {
|
|
|
9542
9846
|
...(runtimeActions ? { runtimeActions } : {}),
|
|
9543
9847
|
...(inferredArtifactUploads ? { inferredArtifactUploads } : {}),
|
|
9544
9848
|
...(artifactUploadSummary ? { artifactUploadSummary } : {}),
|
|
9849
|
+
businessDisposition,
|
|
9545
9850
|
};
|
|
9546
9851
|
const completion = await completeCommand(config, command, cancelled ? "cancelled" : succeeded ? "succeeded" : "failed", result, error ?? undefined);
|
|
9547
9852
|
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.8";
|
|
9
9
|
|
|
10
10
|
const CAPABILITIES = [
|
|
11
11
|
"remote_registration",
|