@amaster.ai/employee-runtime-connector 0.1.0-beta.11 → 0.1.0-beta.12
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 +462 -21
- package/dist/amaster-runtime.mjs +1 -1
- package/package.json +1 -1
|
@@ -2335,7 +2335,7 @@ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync
|
|
|
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.12";
|
|
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;
|
|
@@ -3556,6 +3556,8 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3556
3556
|
authorizationOptionIdGuidance,
|
|
3557
3557
|
"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.",
|
|
3558
3558
|
"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.",
|
|
3559
|
+
"Source-truth contract: every externally derived factual or numeric claim in a comment, document, or artifact must include the source title, publication or retrieval date, and URL or durable source identifier next to the claim. A generic source family such as 'industry reports' or 'public data' is not evidence. If the source cannot be accessed or identified, label the claim as unverified or an assumption, name the verification owner and next action, and do not describe the deliverable or issue as verified or complete on the strength of that claim.",
|
|
3560
|
+
"Every Markdown upload must declare sourceTruth either on its top-level deliverables[] entry or on the explicit upload_artifact action. Use status verified, mixed, unverified, or not_applicable. Verified sources require marker, title, an exact YYYY-MM-DD publication/retrieval date, and url or sourceId; put the marker as [SRC-1] next to the claim and repeat the title, date, and URL/source ID in the file. A generic site homepage is not a durable source URL: use the exact report/page URL. A sourceId must be a stable namespace:value identifier without spaces, such as doi:10.1000/example or report:vendor-report-2026-01; descriptive phrases such as 'industry reports' or 'official materials' are not source IDs. Unverified claims require marker, claim, owner, and nextAction; put [ASSUMPTION-1] next to the claim and repeat those fields in the file. Use not_applicable only when the document contains no externally derived factual or numeric claim, and provide a reason. Omitting deliverables[] does not bypass this preflight.",
|
|
3559
3561
|
"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.",
|
|
3560
3562
|
"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.",
|
|
3561
3563
|
"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.",
|
|
@@ -3596,11 +3598,11 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3596
3598
|
"The runtime action block must be strict JSON parseable by JSON.parse. Do not use ellipses, placeholders such as [...], comments, trailing commas, single quotes, markdown tables outside quoted strings, or raw double quotes inside string values. Escape quotes and newlines in string values, or generate the block with JSON.stringify / @amaster/runtime-sdk before including it in the final assistant message.",
|
|
3597
3599
|
"Before final output, validate the runtime action block with JSON.parse. If it would fail, fix the JSON and only then include AMASTER_RUNTIME_ACTIONS in the final assistant message.",
|
|
3598
3600
|
actionValidatorCommand
|
|
3599
|
-
? `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.`
|
|
3601
|
+
? `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. After it passes, copy the validated file bytes exactly into the fenced final action block; do not retype, reformat, add, or remove any brace. Any post-validation edit invalidates the validation and requires another validator run.`
|
|
3600
3602
|
: "",
|
|
3601
3603
|
`Allowed action types: ${allowedActionTypes}.`,
|
|
3602
|
-
"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.",
|
|
3603
|
-
"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\":\"
|
|
3604
|
+
"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. Omit baseRevisionId unless the current task context provides the exact revision; the connector resolves the latest revision before applying any batch mutations.",
|
|
3605
|
+
"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\",\"payload\":{\"version\":1,\"prompt\":\"Confirm this result?\",\"acceptLabel\":\"确认\",\"rejectLabel\":\"调整\",\"target\":{\"type\":\"custom\",\"key\":\"delivery-review\",\"label\":\"交付结果\",\"revisionId\":\"v1\"}}}. Use wake_assignee when rejection means revise the work; use wake_assignee_on_accept only when rejection intentionally ends the execution path. Never use interactionType.",
|
|
3604
3606
|
"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.",
|
|
3605
3607
|
"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.",
|
|
3606
3608
|
"For create_interaction request_confirmation, follow the complete example above and do not use options arrays.",
|
|
@@ -3916,7 +3918,7 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3916
3918
|
// Built-in executors run on the connector host, so the prompt may reference these host paths.
|
|
3917
3919
|
// A custom wrapper may isolate model tools; until isolation is an explicit capability, this
|
|
3918
3920
|
// validator is same-host only and must fail visibly rather than fall back to a weaker check.
|
|
3919
|
-
const actionValidatorCommand = `${quoteShell(process.execPath)} ${quoteShell(resolve(process.argv[1]))} validate-actions --file .amaster-runtime-actions.json --cwd . --allowed-content-types ${quoteShell(validatorContentTypes.join(","))}`;
|
|
3921
|
+
const actionValidatorCommand = `${quoteShell(process.execPath)} ${quoteShell(resolve(process.argv[1]))} validate-actions --file .amaster-runtime-actions.json --cwd . --allowed-content-types ${quoteShell(validatorContentTypes.join(","))} --require-source-truth true`;
|
|
3920
3922
|
return [
|
|
3921
3923
|
"## AMaster Runtime Connector Task",
|
|
3922
3924
|
"",
|
|
@@ -3961,6 +3963,10 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3961
3963
|
`path: ${attachmentPath}`,
|
|
3962
3964
|
attachment.contentType ? `contentType: ${attachment.contentType}` : "",
|
|
3963
3965
|
attachment.byteSize ? `bytes: ${attachment.byteSize}` : "",
|
|
3966
|
+
attachment.workProductId ? `workProductId: ${attachment.workProductId}` : "",
|
|
3967
|
+
attachment.evidenceRole ? `evidenceRole: ${attachment.evidenceRole}` : "",
|
|
3968
|
+
Array.isArray(attachment.scenarioKeys) ? `scenarioKeys: ${JSON.stringify(attachment.scenarioKeys)}` : "",
|
|
3969
|
+
attachment.sha256 ? `sha256: ${attachment.sha256}` : "",
|
|
3964
3970
|
].filter(Boolean).join(" | ");
|
|
3965
3971
|
return `- ${details}`;
|
|
3966
3972
|
}),
|
|
@@ -5511,15 +5517,25 @@ function completedChildIssueForRuntimeAction(action, actionContext) {
|
|
|
5511
5517
|
return normalizedTitle ? completedByTitle.get(normalizedTitle) ?? null : null;
|
|
5512
5518
|
}
|
|
5513
5519
|
|
|
5514
|
-
function runtimeActionExecutionPriority(action) {
|
|
5520
|
+
function runtimeActionExecutionPriority(action, options = {}) {
|
|
5515
5521
|
const fields = runtimeActionBusinessFields(asRecord(action));
|
|
5516
5522
|
const type = readRuntimeActionType(fields);
|
|
5517
|
-
|
|
5523
|
+
if (type !== "update_parent") return 0;
|
|
5524
|
+
// A same-batch blocking child makes the parent ineligible for a later
|
|
5525
|
+
// in_progress transition. Preserve the requested transition before those
|
|
5526
|
+
// blocker relations exist; terminal/review dispositions still run last so
|
|
5527
|
+
// they can account for every governance mutation created by the batch.
|
|
5528
|
+
return readActionStatus(fields.status) === "in_progress" && options.createdReviewPath !== true ? -10 : 10;
|
|
5518
5529
|
}
|
|
5519
5530
|
|
|
5520
5531
|
function orderRuntimeActionsForGovernance(actions) {
|
|
5532
|
+
const createdReviewPath = actions.some(willRuntimeActionCreateReviewPath);
|
|
5521
5533
|
return actions
|
|
5522
|
-
.map((action, index) => ({
|
|
5534
|
+
.map((action, index) => ({
|
|
5535
|
+
action,
|
|
5536
|
+
index,
|
|
5537
|
+
priority: runtimeActionExecutionPriority(action, { createdReviewPath }),
|
|
5538
|
+
}))
|
|
5523
5539
|
.sort((left, right) => left.priority - right.priority || left.index - right.index)
|
|
5524
5540
|
.map((entry) => entry.action);
|
|
5525
5541
|
}
|
|
@@ -5728,6 +5744,113 @@ function safeMaterializedAttachmentFilename(attachment, index) {
|
|
|
5728
5744
|
return name && name !== "." && name !== ".." ? name : `${id}.bin`;
|
|
5729
5745
|
}
|
|
5730
5746
|
|
|
5747
|
+
function compareAttachmentLineageCandidates(left, right) {
|
|
5748
|
+
// Do not depend on API array order: prefer explicit current lineage, then primary,
|
|
5749
|
+
// then the newest update, with the UUID as a stable final tie-breaker.
|
|
5750
|
+
const versionDiff = Number(right.artifactVersionState === "current")
|
|
5751
|
+
- Number(left.artifactVersionState === "current");
|
|
5752
|
+
if (versionDiff !== 0) return versionDiff;
|
|
5753
|
+
const primaryDiff = Number(right.isPrimary) - Number(left.isPrimary);
|
|
5754
|
+
if (primaryDiff !== 0) return primaryDiff;
|
|
5755
|
+
const updatedAtDiff = right.updatedAtMs - left.updatedAtMs;
|
|
5756
|
+
if (updatedAtDiff !== 0) return updatedAtDiff;
|
|
5757
|
+
return left.workProductId.localeCompare(right.workProductId);
|
|
5758
|
+
}
|
|
5759
|
+
|
|
5760
|
+
async function loadIssueAttachmentLineageCandidates(config, command, runtimeAuth, issueId) {
|
|
5761
|
+
let workProducts;
|
|
5762
|
+
try {
|
|
5763
|
+
workProducts = await runtimeApiJson(
|
|
5764
|
+
runtimeAuth,
|
|
5765
|
+
"GET",
|
|
5766
|
+
`/api/issues/${encodeURIComponent(issueId)}/work-products`,
|
|
5767
|
+
);
|
|
5768
|
+
} catch (err) {
|
|
5769
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5770
|
+
const details = asRecord(err);
|
|
5771
|
+
await ingestLog(
|
|
5772
|
+
config,
|
|
5773
|
+
command,
|
|
5774
|
+
"system",
|
|
5775
|
+
"warn",
|
|
5776
|
+
"Issue attachment lineage unavailable; materializing attachments without work-product metadata",
|
|
5777
|
+
{
|
|
5778
|
+
issueId,
|
|
5779
|
+
reason: "request_failed",
|
|
5780
|
+
error: truncateText(error.message, 1000),
|
|
5781
|
+
httpStatus: readNumber(details.httpStatus, null),
|
|
5782
|
+
httpMethod: readString(details.httpMethod),
|
|
5783
|
+
httpPath: readString(details.httpPath),
|
|
5784
|
+
},
|
|
5785
|
+
);
|
|
5786
|
+
return new Map();
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5789
|
+
if (!Array.isArray(workProducts)) {
|
|
5790
|
+
await ingestLog(
|
|
5791
|
+
config,
|
|
5792
|
+
command,
|
|
5793
|
+
"system",
|
|
5794
|
+
"warn",
|
|
5795
|
+
"Issue attachment lineage unavailable; materializing attachments without work-product metadata",
|
|
5796
|
+
{ issueId, reason: "invalid_response_shape", responseType: workProducts === null ? "null" : typeof workProducts },
|
|
5797
|
+
);
|
|
5798
|
+
return new Map();
|
|
5799
|
+
}
|
|
5800
|
+
|
|
5801
|
+
const candidatesByAttachmentId = new Map();
|
|
5802
|
+
for (const rawWorkProduct of workProducts) {
|
|
5803
|
+
const workProduct = asRecord(rawWorkProduct);
|
|
5804
|
+
// Canonical keys: attachmentId/sha256 plus artifactVersionState/evidenceRole/scenarioKeys in packages/shared/src/validators/work-product.ts.
|
|
5805
|
+
const metadata = asRecord(workProduct.metadata);
|
|
5806
|
+
const attachmentId = readString(metadata.attachmentId);
|
|
5807
|
+
const workProductId = readString(workProduct.id);
|
|
5808
|
+
const artifactVersionState = readString(metadata.artifactVersionState);
|
|
5809
|
+
if (
|
|
5810
|
+
!attachmentId
|
|
5811
|
+
|| !workProductId
|
|
5812
|
+
|| readString(workProduct.type) !== "artifact"
|
|
5813
|
+
|| readString(workProduct.status) === "archived"
|
|
5814
|
+
|| artifactVersionState === "superseded"
|
|
5815
|
+
) {
|
|
5816
|
+
continue;
|
|
5817
|
+
}
|
|
5818
|
+
const updatedAt = readString(workProduct.updatedAt);
|
|
5819
|
+
const updatedAtMs = updatedAt ? Date.parse(updatedAt) : 0;
|
|
5820
|
+
const candidate = {
|
|
5821
|
+
workProductId,
|
|
5822
|
+
evidenceRole: readString(metadata.evidenceRole),
|
|
5823
|
+
scenarioKeys: Array.isArray(metadata.scenarioKeys)
|
|
5824
|
+
? metadata.scenarioKeys.map(readString).filter(Boolean)
|
|
5825
|
+
: null,
|
|
5826
|
+
declaredSha256: readString(metadata.sha256)?.toLowerCase() ?? null,
|
|
5827
|
+
artifactVersionState,
|
|
5828
|
+
isPrimary: workProduct.isPrimary === true,
|
|
5829
|
+
updatedAtMs: Number.isFinite(updatedAtMs) ? updatedAtMs : 0,
|
|
5830
|
+
};
|
|
5831
|
+
const candidates = candidatesByAttachmentId.get(attachmentId) ?? [];
|
|
5832
|
+
candidates.push(candidate);
|
|
5833
|
+
candidatesByAttachmentId.set(attachmentId, candidates);
|
|
5834
|
+
}
|
|
5835
|
+
for (const candidates of candidatesByAttachmentId.values()) {
|
|
5836
|
+
candidates.sort(compareAttachmentLineageCandidates);
|
|
5837
|
+
}
|
|
5838
|
+
return candidatesByAttachmentId;
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5841
|
+
function selectAttachmentLineage(candidates, actualSha256) {
|
|
5842
|
+
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
|
5843
|
+
const selected = candidates.find((candidate) => candidate.declaredSha256 === actualSha256)
|
|
5844
|
+
?? candidates.find((candidate) => candidate.declaredSha256 === null)
|
|
5845
|
+
?? null;
|
|
5846
|
+
if (!selected) return null;
|
|
5847
|
+
return {
|
|
5848
|
+
workProductId: selected.workProductId,
|
|
5849
|
+
evidenceRole: selected.evidenceRole,
|
|
5850
|
+
scenarioKeys: selected.scenarioKeys,
|
|
5851
|
+
};
|
|
5852
|
+
}
|
|
5853
|
+
|
|
5731
5854
|
async function materializeIssueAttachments(config, command, workspace) {
|
|
5732
5855
|
const runtimeAuth = commandRuntimeAuth(command);
|
|
5733
5856
|
const issueId = commandIssueId(command);
|
|
@@ -5735,6 +5858,12 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
5735
5858
|
|
|
5736
5859
|
const attachments = await runtimeApiJson(runtimeAuth, "GET", `/api/issues/${encodeURIComponent(issueId)}/attachments`);
|
|
5737
5860
|
if (!Array.isArray(attachments) || attachments.length === 0) return [];
|
|
5861
|
+
const lineageCandidatesByAttachmentId = await loadIssueAttachmentLineageCandidates(
|
|
5862
|
+
config,
|
|
5863
|
+
command,
|
|
5864
|
+
runtimeAuth,
|
|
5865
|
+
issueId,
|
|
5866
|
+
);
|
|
5738
5867
|
|
|
5739
5868
|
const targetDir = join(workspace.cwd, "input-attachments");
|
|
5740
5869
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -5757,14 +5886,36 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
5757
5886
|
const targetPath = join(targetDir, filename);
|
|
5758
5887
|
const body = await runtimeApiBuffer(runtimeAuth, contentPath);
|
|
5759
5888
|
writeFileSync(targetPath, body);
|
|
5889
|
+
const attachmentId = readString(attachment.id);
|
|
5890
|
+
const actualSha256 = createHash("sha256").update(body).digest("hex");
|
|
5891
|
+
const lineageCandidates = lineageCandidatesByAttachmentId.get(attachmentId) ?? [];
|
|
5892
|
+
const lineage = selectAttachmentLineage(lineageCandidates, actualSha256);
|
|
5893
|
+
if (lineageCandidates.length > 0 && !lineage) {
|
|
5894
|
+
await ingestLog(
|
|
5895
|
+
config,
|
|
5896
|
+
command,
|
|
5897
|
+
"system",
|
|
5898
|
+
"warn",
|
|
5899
|
+
"Issue attachment lineage SHA mismatch; omitting work-product metadata",
|
|
5900
|
+
{
|
|
5901
|
+
issueId,
|
|
5902
|
+
attachmentId,
|
|
5903
|
+
actualSha256,
|
|
5904
|
+
declaredSha256: lineageCandidates.map((candidate) => candidate.declaredSha256).filter(Boolean),
|
|
5905
|
+
workProductIds: lineageCandidates.map((candidate) => candidate.workProductId),
|
|
5906
|
+
},
|
|
5907
|
+
);
|
|
5908
|
+
}
|
|
5760
5909
|
materialized.push({
|
|
5761
|
-
id:
|
|
5910
|
+
id: attachmentId,
|
|
5762
5911
|
name: readString(attachment.originalFilename) ?? filename,
|
|
5763
5912
|
path: targetPath,
|
|
5764
5913
|
relativePath: relative(workspace.cwd, targetPath),
|
|
5765
5914
|
contentType: readString(attachment.contentType),
|
|
5766
5915
|
byteSize: body.byteLength,
|
|
5767
5916
|
contentPath,
|
|
5917
|
+
sha256: actualSha256,
|
|
5918
|
+
...(lineage ?? {}),
|
|
5768
5919
|
});
|
|
5769
5920
|
}
|
|
5770
5921
|
|
|
@@ -5777,6 +5928,10 @@ async function materializeIssueAttachments(config, command, workspace) {
|
|
|
5777
5928
|
path: attachment.relativePath,
|
|
5778
5929
|
contentType: attachment.contentType ?? null,
|
|
5779
5930
|
byteSize: attachment.byteSize,
|
|
5931
|
+
workProductId: attachment.workProductId ?? null,
|
|
5932
|
+
evidenceRole: attachment.evidenceRole ?? null,
|
|
5933
|
+
scenarioKeys: attachment.scenarioKeys ?? null,
|
|
5934
|
+
sha256: attachment.sha256 ?? null,
|
|
5780
5935
|
})),
|
|
5781
5936
|
});
|
|
5782
5937
|
await ingestLog(config, command, "system", "info", `Materialized ${materialized.length} issue attachment(s) into the execution workspace`, {
|
|
@@ -7094,7 +7249,7 @@ const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
|
|
|
7094
7249
|
"path", "filePath", "artifactPath", "artifact_path", "title", "summary", "contentType",
|
|
7095
7250
|
"createWorkProduct", "provider", "status", "reviewState", "isPrimary", "healthStatus",
|
|
7096
7251
|
"sourceWorkProductId", "sourceWorkProductIds", "supersedesWorkProductId", "evidenceRole", "scenarioKeys",
|
|
7097
|
-
"idempotencyKey", "upload_artifact",
|
|
7252
|
+
"idempotencyKey", "sourceTruth", "upload_artifact",
|
|
7098
7253
|
])],
|
|
7099
7254
|
["upsert_document", new Set([
|
|
7100
7255
|
"key", "title", "body", "format", "changeSummary", "baseRevisionId", "upsert_document",
|
|
@@ -7160,7 +7315,7 @@ const RUNTIME_ACTION_ARRAY_FIELDS = new Set([
|
|
|
7160
7315
|
]);
|
|
7161
7316
|
const RUNTIME_ACTION_OBJECT_FIELDS = new Set([
|
|
7162
7317
|
"adapterConfig", "instructionsBundle", "runtimeConfig", "permissions", "metadata", "target",
|
|
7163
|
-
"interaction",
|
|
7318
|
+
"interaction", "sourceTruth",
|
|
7164
7319
|
]);
|
|
7165
7320
|
|
|
7166
7321
|
function assertRuntimeActionFieldTypes(action, actionIndex) {
|
|
@@ -7368,6 +7523,45 @@ function assertRuntimeCreateInteractionPayload(kind, payload) {
|
|
|
7368
7523
|
}
|
|
7369
7524
|
}
|
|
7370
7525
|
|
|
7526
|
+
function assertRuntimeConfirmationTarget(target) {
|
|
7527
|
+
const record = asRecord(target);
|
|
7528
|
+
const type = readString(record.type);
|
|
7529
|
+
assertRuntimeActionValid(
|
|
7530
|
+
["custom", "issue_document"].includes(type ?? ""),
|
|
7531
|
+
`confirmation payload target type is unsupported: ${String(record.type)}; use custom or issue_document`,
|
|
7532
|
+
);
|
|
7533
|
+
const key = readString(record.key);
|
|
7534
|
+
const label = readString(record.label);
|
|
7535
|
+
const href = readString(record.href);
|
|
7536
|
+
const revisionId = readString(record.revisionId);
|
|
7537
|
+
const revisionNumber = record.revisionNumber;
|
|
7538
|
+
assertRuntimeActionValid(key, "confirmation payload target requires key");
|
|
7539
|
+
assertRuntimeActionValid(!label || label.length <= 120, "confirmation payload target label must be at most 120 characters");
|
|
7540
|
+
assertRuntimeActionValid(
|
|
7541
|
+
!href || href.startsWith("#") || (href.startsWith("/") && !href.startsWith("//")) || /^https?:\/\//i.test(href),
|
|
7542
|
+
"confirmation payload target href must be a root-relative path, same-page fragment, or http(s) URL",
|
|
7543
|
+
);
|
|
7544
|
+
assertRuntimeActionValid(
|
|
7545
|
+
revisionNumber === undefined || revisionNumber === null || (Number.isInteger(revisionNumber) && revisionNumber > 0),
|
|
7546
|
+
"confirmation payload target revisionNumber must be a positive integer",
|
|
7547
|
+
);
|
|
7548
|
+
if (type === "custom") {
|
|
7549
|
+
assertRuntimeActionValid(key.length <= 120, "confirmation custom target key must be at most 120 characters");
|
|
7550
|
+
assertRuntimeActionValid(!revisionId || revisionId.length <= 255, "confirmation custom target revisionId must be at most 255 characters");
|
|
7551
|
+
return;
|
|
7552
|
+
}
|
|
7553
|
+
const uuidPattern = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
7554
|
+
assertRuntimeActionValid(
|
|
7555
|
+
key.length <= 64 && /^[a-z0-9][a-z0-9_-]*$/.test(key),
|
|
7556
|
+
"confirmation issue_document target key must use lowercase letters, numbers, _ or -",
|
|
7557
|
+
);
|
|
7558
|
+
assertRuntimeActionValid(revisionId && uuidPattern.test(revisionId), "confirmation issue_document target revisionId must be a UUID");
|
|
7559
|
+
for (const field of ["issueId", "documentId"]) {
|
|
7560
|
+
const value = readString(record[field]);
|
|
7561
|
+
assertRuntimeActionValid(!value || uuidPattern.test(value), `confirmation issue_document target ${field} must be a UUID`);
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
|
|
7371
7565
|
function willRuntimeActionCreateReviewPath(action) {
|
|
7372
7566
|
const fields = runtimeActionBusinessFields(action);
|
|
7373
7567
|
const type = readRuntimeActionType(fields);
|
|
@@ -7572,7 +7766,8 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
|
7572
7766
|
}
|
|
7573
7767
|
if (type === "upload_artifact") {
|
|
7574
7768
|
assertNoPlaceholderRuntimeActionText(fields, ["path", "filePath", "artifactPath", "artifact_path", "title", "summary"]);
|
|
7575
|
-
|
|
7769
|
+
const artifactPath = readRuntimeArtifactPath(fields);
|
|
7770
|
+
assertRuntimeActionValid(artifactPath, "upload_artifact action requires path");
|
|
7576
7771
|
const pathFields = ["path", "filePath", "artifactPath", "artifact_path"].filter((field) => readString(fields[field]));
|
|
7577
7772
|
assertRuntimeActionValid(pathFields.length === 1, "upload_artifact action requires exactly one path field");
|
|
7578
7773
|
const sourceWorkProductId = readString(fields.sourceWorkProductId);
|
|
@@ -7602,7 +7797,14 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
|
7602
7797
|
assertRuntimeActionValid(fields.scenarioKeys.map(readString).filter(Boolean).length === fields.scenarioKeys.length, "upload_artifact scenarioKeys must contain non-empty strings");
|
|
7603
7798
|
}
|
|
7604
7799
|
assertRuntimeArtifactContentType(fields, preflightContext.allowedContentTypes);
|
|
7605
|
-
resolveWorkspaceArtifactPath(fields, { cwd: preflightContext.cwd });
|
|
7800
|
+
const filePath = resolveWorkspaceArtifactPath(fields, { cwd: preflightContext.cwd });
|
|
7801
|
+
if (preflightContext.requireSourceTruth && extname(filePath).toLowerCase() === ".md") {
|
|
7802
|
+
assertRuntimeMarkdownSourceTruth(
|
|
7803
|
+
fields.sourceTruth ?? preflightContext.sourceTruthByPath.get(filePath),
|
|
7804
|
+
`actions[${actionIndex}].sourceTruth`,
|
|
7805
|
+
filePath,
|
|
7806
|
+
);
|
|
7807
|
+
}
|
|
7606
7808
|
return;
|
|
7607
7809
|
}
|
|
7608
7810
|
if (type === "upsert_document") {
|
|
@@ -7648,6 +7850,13 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
|
7648
7850
|
"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",
|
|
7649
7851
|
);
|
|
7650
7852
|
}
|
|
7853
|
+
if (
|
|
7854
|
+
["request_confirmation", "request_checkbox_confirmation"].includes(kind) &&
|
|
7855
|
+
payload.target !== undefined &&
|
|
7856
|
+
payload.target !== null
|
|
7857
|
+
) {
|
|
7858
|
+
assertRuntimeConfirmationTarget(payload.target);
|
|
7859
|
+
}
|
|
7651
7860
|
preflightContext.createdReviewPathForInReview = true;
|
|
7652
7861
|
return;
|
|
7653
7862
|
}
|
|
@@ -7705,6 +7914,22 @@ function preflightRuntimeActionBatch(actions, options = {}) {
|
|
|
7705
7914
|
const actionIndexByObject = new Map(actions.map((action, index) => [action, index]));
|
|
7706
7915
|
assertDelegatedParentWaitDisposition(actions);
|
|
7707
7916
|
const orderedActions = orderRuntimeActionsByClientKeyDependencies(actions);
|
|
7917
|
+
const documentActionIndexByKey = new Map();
|
|
7918
|
+
for (const action of orderedActions) {
|
|
7919
|
+
if (readRuntimeActionType(action) !== "upsert_document") continue;
|
|
7920
|
+
const key = readBoundedActionString(runtimeActionBusinessFields(action), "key", 64);
|
|
7921
|
+
if (!key) continue;
|
|
7922
|
+
const actionIndex = actionIndexByObject.get(action);
|
|
7923
|
+
if (documentActionIndexByKey.has(key)) {
|
|
7924
|
+
const err = runtimeActionValidationError(
|
|
7925
|
+
`upsert_document key ${key} appears more than once in the same runtime action batch`,
|
|
7926
|
+
);
|
|
7927
|
+
err.runtimeActionType = "upsert_document";
|
|
7928
|
+
err.runtimeActionFieldPath = `actions[${actionIndex}].key`;
|
|
7929
|
+
throw err;
|
|
7930
|
+
}
|
|
7931
|
+
documentActionIndexByKey.set(key, actionIndex);
|
|
7932
|
+
}
|
|
7708
7933
|
assertRuntimeActionsAllowedByContinuationScope(orderedActions, options.continuationScope);
|
|
7709
7934
|
assertNoAmbiguousApprovalGatedSideEffects(orderedActions);
|
|
7710
7935
|
const preflightContext = {
|
|
@@ -7715,6 +7940,8 @@ function preflightRuntimeActionBatch(actions, options = {}) {
|
|
|
7715
7940
|
acceptedConfirmationContinuation: options.acceptedConfirmationContinuation === true,
|
|
7716
7941
|
allowedContentTypes: Array.isArray(options.allowedContentTypes) ? options.allowedContentTypes : [],
|
|
7717
7942
|
cwd: readString(options.cwd),
|
|
7943
|
+
requireSourceTruth: options.requireSourceTruth === true,
|
|
7944
|
+
sourceTruthByPath: options.sourceTruthByPath instanceof Map ? options.sourceTruthByPath : new Map(),
|
|
7718
7945
|
finalCommentBodies: [],
|
|
7719
7946
|
};
|
|
7720
7947
|
for (const action of orderedActions) {
|
|
@@ -7754,6 +7981,56 @@ function preflightRuntimeActionBatch(actions, options = {}) {
|
|
|
7754
7981
|
return orderedActions;
|
|
7755
7982
|
}
|
|
7756
7983
|
|
|
7984
|
+
async function preflightRuntimeDocumentRevisions(runtimeAuth, issueId, actions) {
|
|
7985
|
+
const baseRevisionIdsByKey = new Map();
|
|
7986
|
+
for (const [actionIndex, action] of actions.entries()) {
|
|
7987
|
+
if (readRuntimeActionType(action) !== "upsert_document") continue;
|
|
7988
|
+
const fields = runtimeActionBusinessFields(action);
|
|
7989
|
+
const key = readBoundedActionString(fields, "key", 64);
|
|
7990
|
+
if (!key) continue;
|
|
7991
|
+
let existingDocument = null;
|
|
7992
|
+
try {
|
|
7993
|
+
existingDocument = asRecord(await runtimeApiJson(
|
|
7994
|
+
runtimeAuth,
|
|
7995
|
+
"GET",
|
|
7996
|
+
`/api/issues/${encodeURIComponent(issueId)}/documents/${encodeURIComponent(key)}`,
|
|
7997
|
+
));
|
|
7998
|
+
} catch (err) {
|
|
7999
|
+
if (err?.httpStatus !== 404) throw err;
|
|
8000
|
+
}
|
|
8001
|
+
|
|
8002
|
+
const explicitBaseRevisionId = readString(fields.baseRevisionId);
|
|
8003
|
+
const currentRevisionId = readString(existingDocument?.latestRevisionId);
|
|
8004
|
+
if (existingDocument && !currentRevisionId) {
|
|
8005
|
+
const err = runtimeActionValidationError(
|
|
8006
|
+
`upsert_document ${key} exists but did not expose latestRevisionId`,
|
|
8007
|
+
);
|
|
8008
|
+
err.runtimeActionType = "upsert_document";
|
|
8009
|
+
err.runtimeActionFieldPath = `actions[${actionIndex}].baseRevisionId`;
|
|
8010
|
+
throw err;
|
|
8011
|
+
}
|
|
8012
|
+
if (!existingDocument && explicitBaseRevisionId) {
|
|
8013
|
+
const err = runtimeActionValidationError(
|
|
8014
|
+
`upsert_document ${key} supplied baseRevisionId but the document does not exist`,
|
|
8015
|
+
);
|
|
8016
|
+
err.runtimeActionType = "upsert_document";
|
|
8017
|
+
err.runtimeActionFieldPath = `actions[${actionIndex}].baseRevisionId`;
|
|
8018
|
+
throw err;
|
|
8019
|
+
}
|
|
8020
|
+
if (currentRevisionId && explicitBaseRevisionId && explicitBaseRevisionId !== currentRevisionId) {
|
|
8021
|
+
const err = new Error(
|
|
8022
|
+
`upsert_document ${key} baseRevisionId is stale; current revision is ${currentRevisionId}`,
|
|
8023
|
+
);
|
|
8024
|
+
err.httpStatus = 409;
|
|
8025
|
+
err.runtimeActionType = "upsert_document";
|
|
8026
|
+
err.runtimeActionFieldPath = `actions[${actionIndex}].baseRevisionId`;
|
|
8027
|
+
throw err;
|
|
8028
|
+
}
|
|
8029
|
+
baseRevisionIdsByKey.set(key, currentRevisionId);
|
|
8030
|
+
}
|
|
8031
|
+
return baseRevisionIdsByKey;
|
|
8032
|
+
}
|
|
8033
|
+
|
|
7757
8034
|
function runtimeActionAuthorizationClass(action) {
|
|
7758
8035
|
// Keep these high-risk route mappings aligned with server-side checks in
|
|
7759
8036
|
// routes/agents.ts (organization_mutation) and routes/issues.ts
|
|
@@ -8161,7 +8438,7 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
8161
8438
|
if (Object.keys(payload).length === 0) {
|
|
8162
8439
|
throw new Error("create_interaction action requires payload");
|
|
8163
8440
|
}
|
|
8164
|
-
const defaultContinuationPolicy =
|
|
8441
|
+
const defaultContinuationPolicy = "wake_assignee";
|
|
8165
8442
|
const createBody = {
|
|
8166
8443
|
kind,
|
|
8167
8444
|
continuationPolicy: readString(fields.continuationPolicy) ?? defaultContinuationPolicy,
|
|
@@ -8210,7 +8487,9 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
8210
8487
|
format: "markdown",
|
|
8211
8488
|
body,
|
|
8212
8489
|
changeSummary: readBoundedActionString(fields, "changeSummary", 500) ?? null,
|
|
8213
|
-
baseRevisionId: readString(fields.baseRevisionId)
|
|
8490
|
+
baseRevisionId: readString(fields.baseRevisionId)
|
|
8491
|
+
?? actionContext.documentBaseRevisionIdsByKey.get(key)
|
|
8492
|
+
?? null,
|
|
8214
8493
|
},
|
|
8215
8494
|
));
|
|
8216
8495
|
return {
|
|
@@ -9100,7 +9379,138 @@ function attachRuntimeCheckpointCandidates(err, envelope, cwd) {
|
|
|
9100
9379
|
}
|
|
9101
9380
|
}
|
|
9102
9381
|
|
|
9103
|
-
function
|
|
9382
|
+
function assertRuntimeMarkdownSourceTruth(sourceTruthValue, fieldPath, filePath) {
|
|
9383
|
+
const sourceTruth = asRecord(sourceTruthValue);
|
|
9384
|
+
if (Object.keys(sourceTruth).length === 0) {
|
|
9385
|
+
throw runtimeDeliverableError(`${fieldPath} is required for Markdown deliverables`);
|
|
9386
|
+
}
|
|
9387
|
+
const allowedFields = new Set(["status", "reason", "sources", "unverifiedClaims"]);
|
|
9388
|
+
for (const field of Object.keys(sourceTruth)) {
|
|
9389
|
+
if (!allowedFields.has(field)) {
|
|
9390
|
+
throw runtimeDeliverableError(`${fieldPath}.${field} is unsupported`);
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9393
|
+
const status = readString(sourceTruth.status);
|
|
9394
|
+
if (!["verified", "mixed", "unverified", "not_applicable"].includes(status ?? "")) {
|
|
9395
|
+
throw runtimeDeliverableError(`${fieldPath}.status must be verified, mixed, unverified, or not_applicable`);
|
|
9396
|
+
}
|
|
9397
|
+
const sources = Array.isArray(sourceTruth.sources) ? sourceTruth.sources : [];
|
|
9398
|
+
const unverifiedClaims = Array.isArray(sourceTruth.unverifiedClaims) ? sourceTruth.unverifiedClaims : [];
|
|
9399
|
+
if (sourceTruth.sources !== undefined && !Array.isArray(sourceTruth.sources)) {
|
|
9400
|
+
throw runtimeDeliverableError(`${fieldPath}.sources must be an array`);
|
|
9401
|
+
}
|
|
9402
|
+
if (sourceTruth.unverifiedClaims !== undefined && !Array.isArray(sourceTruth.unverifiedClaims)) {
|
|
9403
|
+
throw runtimeDeliverableError(`${fieldPath}.unverifiedClaims must be an array`);
|
|
9404
|
+
}
|
|
9405
|
+
if (sources.length > 100 || unverifiedClaims.length > 100) {
|
|
9406
|
+
throw runtimeDeliverableError(`${fieldPath} exceeds the limit of 100 source records per category`);
|
|
9407
|
+
}
|
|
9408
|
+
if (status === "verified" && (sources.length === 0 || unverifiedClaims.length > 0)) {
|
|
9409
|
+
throw runtimeDeliverableError(`${fieldPath} status verified requires sources and forbids unverifiedClaims`);
|
|
9410
|
+
}
|
|
9411
|
+
if (status === "mixed" && (sources.length === 0 || unverifiedClaims.length === 0)) {
|
|
9412
|
+
throw runtimeDeliverableError(`${fieldPath} status mixed requires both sources and unverifiedClaims`);
|
|
9413
|
+
}
|
|
9414
|
+
if (status === "unverified" && (sources.length > 0 || unverifiedClaims.length === 0)) {
|
|
9415
|
+
throw runtimeDeliverableError(`${fieldPath} status unverified requires unverifiedClaims and forbids sources`);
|
|
9416
|
+
}
|
|
9417
|
+
|
|
9418
|
+
const content = readFileSync(filePath, "utf8");
|
|
9419
|
+
const markdownSourceMarkers = new Set(
|
|
9420
|
+
[...content.matchAll(/\[(SRC-[A-Z0-9_-]+)\]/gi)].map((match) => match[1]),
|
|
9421
|
+
);
|
|
9422
|
+
const markdownAssumptionMarkers = new Set(
|
|
9423
|
+
[...content.matchAll(/\[(ASSUMPTION-[A-Z0-9_-]+)\]/gi)].map((match) => match[1]),
|
|
9424
|
+
);
|
|
9425
|
+
if (status === "not_applicable") {
|
|
9426
|
+
if (sources.length > 0 || unverifiedClaims.length > 0 || !readString(sourceTruth.reason)) {
|
|
9427
|
+
throw runtimeDeliverableError(`${fieldPath} status not_applicable requires reason and forbids source records`);
|
|
9428
|
+
}
|
|
9429
|
+
const sourceMarker = markdownSourceMarkers.values().next().value;
|
|
9430
|
+
if (sourceMarker) {
|
|
9431
|
+
throw runtimeDeliverableError(`Markdown marker ${sourceMarker} is missing from sourceTruth.sources`);
|
|
9432
|
+
}
|
|
9433
|
+
const assumptionMarker = markdownAssumptionMarkers.values().next().value;
|
|
9434
|
+
if (assumptionMarker) {
|
|
9435
|
+
throw runtimeDeliverableError(`Markdown marker ${assumptionMarker} is missing from sourceTruth.unverifiedClaims`);
|
|
9436
|
+
}
|
|
9437
|
+
return;
|
|
9438
|
+
}
|
|
9439
|
+
|
|
9440
|
+
const markers = new Set();
|
|
9441
|
+
const declaredSourceMarkers = new Set();
|
|
9442
|
+
const declaredAssumptionMarkers = new Set();
|
|
9443
|
+
for (const [sourceIndex, rawSource] of sources.entries()) {
|
|
9444
|
+
const sourcePath = `${fieldPath}.sources[${sourceIndex}]`;
|
|
9445
|
+
const source = asRecord(rawSource);
|
|
9446
|
+
const marker = readString(source.marker);
|
|
9447
|
+
const title = readString(source.title);
|
|
9448
|
+
const date = readString(source.date);
|
|
9449
|
+
const url = readString(source.url);
|
|
9450
|
+
const sourceId = readString(source.sourceId);
|
|
9451
|
+
if (!marker || !/^SRC-[A-Z0-9_-]+$/i.test(marker)) {
|
|
9452
|
+
throw runtimeDeliverableError(`${sourcePath}.marker must use SRC-*`);
|
|
9453
|
+
}
|
|
9454
|
+
if (markers.has(marker)) throw runtimeDeliverableError(`${sourcePath}.marker must be unique`);
|
|
9455
|
+
markers.add(marker);
|
|
9456
|
+
declaredSourceMarkers.add(marker);
|
|
9457
|
+
if (!title) throw runtimeDeliverableError(`${sourcePath}.title is required`);
|
|
9458
|
+
if (!date || !/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
9459
|
+
throw runtimeDeliverableError(`${sourcePath}.date must be a valid YYYY-MM-DD date`);
|
|
9460
|
+
}
|
|
9461
|
+
const parsedDate = new Date(`${date}T00:00:00.000Z`);
|
|
9462
|
+
if (Number.isNaN(parsedDate.getTime()) || parsedDate.toISOString().slice(0, 10) !== date) {
|
|
9463
|
+
throw runtimeDeliverableError(`${sourcePath}.date must be a valid YYYY-MM-DD date`);
|
|
9464
|
+
}
|
|
9465
|
+
if (!url && !sourceId) throw runtimeDeliverableError(`${sourcePath} requires url or sourceId`);
|
|
9466
|
+
if (url) {
|
|
9467
|
+
if (!/^https?:\/\//i.test(url)) throw runtimeDeliverableError(`${sourcePath}.url must use http or https`);
|
|
9468
|
+
if (/\s/.test(url)) throw runtimeDeliverableError(`${sourcePath}.url must be one exact URL without spaces`);
|
|
9469
|
+
const parsedUrl = new URL(url);
|
|
9470
|
+
if (parsedUrl.pathname === "/" && !parsedUrl.search && !parsedUrl.hash && !sourceId) {
|
|
9471
|
+
throw runtimeDeliverableError(`${sourcePath}.url must identify a document or page, not only a site root`);
|
|
9472
|
+
}
|
|
9473
|
+
}
|
|
9474
|
+
if (sourceId && !/^[a-z][a-z0-9._-]*:[a-z0-9][a-z0-9._/+:-]*$/i.test(sourceId)) {
|
|
9475
|
+
throw runtimeDeliverableError(`${sourcePath}.sourceId must use a stable namespace:value identifier without spaces`);
|
|
9476
|
+
}
|
|
9477
|
+
for (const [label, value] of [["marker", `[${marker}]`], ["title", title], ["date", date], [url ? "url" : "sourceId", url ?? sourceId]]) {
|
|
9478
|
+
if (!content.includes(value)) throw runtimeDeliverableError(`${sourcePath}.${label} must appear in the Markdown deliverable`);
|
|
9479
|
+
}
|
|
9480
|
+
}
|
|
9481
|
+
for (const [claimIndex, rawClaim] of unverifiedClaims.entries()) {
|
|
9482
|
+
const claimPath = `${fieldPath}.unverifiedClaims[${claimIndex}]`;
|
|
9483
|
+
const claim = asRecord(rawClaim);
|
|
9484
|
+
const marker = readString(claim.marker);
|
|
9485
|
+
const claimText = readString(claim.claim);
|
|
9486
|
+
const owner = readString(claim.owner);
|
|
9487
|
+
const nextAction = readString(claim.nextAction);
|
|
9488
|
+
if (!marker || !/^ASSUMPTION-[A-Z0-9_-]+$/i.test(marker)) {
|
|
9489
|
+
throw runtimeDeliverableError(`${claimPath}.marker must use ASSUMPTION-*`);
|
|
9490
|
+
}
|
|
9491
|
+
if (markers.has(marker)) throw runtimeDeliverableError(`${claimPath}.marker must be unique`);
|
|
9492
|
+
markers.add(marker);
|
|
9493
|
+
declaredAssumptionMarkers.add(marker);
|
|
9494
|
+
if (!claimText) throw runtimeDeliverableError(`${claimPath}.claim is required`);
|
|
9495
|
+
if (!owner) throw runtimeDeliverableError(`${claimPath}.owner is required`);
|
|
9496
|
+
if (!nextAction) throw runtimeDeliverableError(`${claimPath}.nextAction is required`);
|
|
9497
|
+
for (const [label, value] of [["marker", `[${marker}]`], ["claim", claimText], ["owner", owner], ["nextAction", nextAction]]) {
|
|
9498
|
+
if (!content.includes(value)) throw runtimeDeliverableError(`${claimPath}.${label} must appear in the Markdown deliverable`);
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
for (const marker of markdownSourceMarkers) {
|
|
9502
|
+
if (!declaredSourceMarkers.has(marker)) {
|
|
9503
|
+
throw runtimeDeliverableError(`Markdown marker ${marker} is missing from sourceTruth.sources`);
|
|
9504
|
+
}
|
|
9505
|
+
}
|
|
9506
|
+
for (const marker of markdownAssumptionMarkers) {
|
|
9507
|
+
if (!declaredAssumptionMarkers.has(marker)) {
|
|
9508
|
+
throw runtimeDeliverableError(`Markdown marker ${marker} is missing from sourceTruth.unverifiedClaims`);
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
}
|
|
9512
|
+
|
|
9513
|
+
function preflightRuntimeDeliverables(config, envelope, cwd, allowedContentTypes, options = {}) {
|
|
9104
9514
|
const rawDeliverables = Array.isArray(envelope.deliverables) ? envelope.deliverables : [];
|
|
9105
9515
|
if (rawDeliverables.length > 20) {
|
|
9106
9516
|
throw runtimeDeliverableError("deliverables manifest exceeds the limit of 20");
|
|
@@ -9137,7 +9547,15 @@ function preflightRuntimeDeliverables(config, envelope, cwd, allowedContentTypes
|
|
|
9137
9547
|
runtimeActionAllowedContentTypes: err?.runtimeActionAllowedContentTypes,
|
|
9138
9548
|
});
|
|
9139
9549
|
}
|
|
9550
|
+
if (options.requireSourceTruth === true && extname(filePath).toLowerCase() === ".md") {
|
|
9551
|
+
assertRuntimeMarkdownSourceTruth(
|
|
9552
|
+
deliverable.sourceTruth,
|
|
9553
|
+
`deliverables[${index}].sourceTruth`,
|
|
9554
|
+
filePath,
|
|
9555
|
+
);
|
|
9556
|
+
}
|
|
9140
9557
|
}
|
|
9558
|
+
normalized.sourceTruth = deliverable.sourceTruth;
|
|
9141
9559
|
deliverables.push(normalized);
|
|
9142
9560
|
if (deliverable.verification !== undefined) verifications.push(deliverable.verification);
|
|
9143
9561
|
}
|
|
@@ -9203,7 +9621,9 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
9203
9621
|
const allowedContentTypes = runtimeAttachmentContentTypes(command);
|
|
9204
9622
|
let deliverableManifest;
|
|
9205
9623
|
try {
|
|
9206
|
-
deliverableManifest = preflightRuntimeDeliverables(config, envelope, cwd, allowedContentTypes
|
|
9624
|
+
deliverableManifest = preflightRuntimeDeliverables(config, envelope, cwd, allowedContentTypes, {
|
|
9625
|
+
requireSourceTruth: true,
|
|
9626
|
+
});
|
|
9207
9627
|
} catch (err) {
|
|
9208
9628
|
attachRuntimeCheckpointCandidates(err, envelope, cwd);
|
|
9209
9629
|
throw err;
|
|
@@ -9211,11 +9631,12 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
9211
9631
|
const explicitUploadPaths = new Set(rawActions
|
|
9212
9632
|
.filter((action) => readRuntimeActionType(action) === "upload_artifact")
|
|
9213
9633
|
.map((action) => readRuntimeArtifactPath(runtimeActionBusinessFields(action)))
|
|
9214
|
-
.filter(Boolean)
|
|
9634
|
+
.filter(Boolean)
|
|
9635
|
+
.map((artifactPath) => realOrResolvedPath(resolve(cwd, expandHomePath(artifactPath)))));
|
|
9215
9636
|
const hasExplicitPrimaryArtifact = rawActions.some((action) =>
|
|
9216
9637
|
readRuntimeActionType(action) === "upload_artifact" && runtimeActionBusinessFields(action).isPrimary === true);
|
|
9217
9638
|
const generatedUploadActions = deliverableManifest.deliverables
|
|
9218
|
-
.filter((deliverable) => deliverable.required && !explicitUploadPaths.has(deliverable.
|
|
9639
|
+
.filter((deliverable) => deliverable.required && !explicitUploadPaths.has(deliverable.path))
|
|
9219
9640
|
.map((deliverable, index) => ({
|
|
9220
9641
|
type: "upload_artifact",
|
|
9221
9642
|
path: deliverable.rawPath,
|
|
@@ -9234,6 +9655,7 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
9234
9655
|
let actions = orderRuntimeActionsForGovernance([...generatedUploadActions, ...rawActions]);
|
|
9235
9656
|
const answeredQuestionContinuation = runtimeActionAnsweredQuestionContinuation(command);
|
|
9236
9657
|
const acceptedConfirmationContinuation = runtimeActionAcceptedConfirmationContinuation(command);
|
|
9658
|
+
let documentBaseRevisionIdsByKey;
|
|
9237
9659
|
try {
|
|
9238
9660
|
actions = preflightRuntimeActionBatch(actions, {
|
|
9239
9661
|
answeredQuestionContinuation,
|
|
@@ -9241,8 +9663,14 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
9241
9663
|
allowedContentTypes,
|
|
9242
9664
|
continuationScope,
|
|
9243
9665
|
cwd,
|
|
9666
|
+
requireSourceTruth: true,
|
|
9667
|
+
sourceTruthByPath: new Map(deliverableManifest.deliverables.map((deliverable) => [
|
|
9668
|
+
deliverable.path,
|
|
9669
|
+
deliverable.sourceTruth,
|
|
9670
|
+
])),
|
|
9244
9671
|
});
|
|
9245
9672
|
assertRuntimeActionsAllowedByAuthorizationEnvelope(actions, authorizationEnvelope);
|
|
9673
|
+
documentBaseRevisionIdsByKey = await preflightRuntimeDocumentRevisions(runtimeAuth, issueId, actions);
|
|
9246
9674
|
} catch (err) {
|
|
9247
9675
|
if (err && typeof err === "object" && !err.runtimeActionLedger) {
|
|
9248
9676
|
const fieldPath = readString(err.runtimeActionFieldPath);
|
|
@@ -9277,6 +9705,7 @@ async function applyRuntimeActionBridge(config, command, parsed, execution, cwd)
|
|
|
9277
9705
|
createdReviewPathForInReview: false,
|
|
9278
9706
|
acceptedConfirmationContinuation,
|
|
9279
9707
|
answeredQuestionContinuation,
|
|
9708
|
+
documentBaseRevisionIdsByKey,
|
|
9280
9709
|
finalCommentBodies: [],
|
|
9281
9710
|
};
|
|
9282
9711
|
for (const [actionIndex, action] of actions.entries()) {
|
|
@@ -9537,6 +9966,7 @@ async function applyInferredArtifactUploads(config, command, parsed, execution,
|
|
|
9537
9966
|
.filter(Boolean)
|
|
9538
9967
|
.map((entry) => resolve(cwd, entry)),
|
|
9539
9968
|
);
|
|
9969
|
+
// Refetch after execution: runtime actions may have created work products since input attachment lineage was loaded.
|
|
9540
9970
|
const existingWorkProducts = await listExistingIssueWorkProducts(runtimeAuth, issueId);
|
|
9541
9971
|
const inferenceText = [
|
|
9542
9972
|
parsed.summary,
|
|
@@ -10134,9 +10564,20 @@ function validateRuntimeActionsFile(config, flags) {
|
|
|
10134
10564
|
assertRuntimeActionValid(Array.isArray(envelope.actions), "runtime action envelope actions must be an array");
|
|
10135
10565
|
assertRuntimeActionValid(envelope.actions.length <= 20, "runtime action envelope actions exceed the limit of 20");
|
|
10136
10566
|
|
|
10137
|
-
|
|
10567
|
+
const requireSourceTruth = flags["require-source-truth"] === "true";
|
|
10568
|
+
const deliverableManifest = preflightRuntimeDeliverables(config, envelope, cwd, allowedContentTypes, {
|
|
10569
|
+
requireSourceTruth,
|
|
10570
|
+
});
|
|
10138
10571
|
const actions = orderRuntimeActionsForGovernance(envelope.actions.map(asRecord));
|
|
10139
|
-
preflightRuntimeActionBatch(actions, {
|
|
10572
|
+
preflightRuntimeActionBatch(actions, {
|
|
10573
|
+
cwd,
|
|
10574
|
+
allowedContentTypes,
|
|
10575
|
+
requireSourceTruth,
|
|
10576
|
+
sourceTruthByPath: new Map(deliverableManifest.deliverables.map((deliverable) => [
|
|
10577
|
+
deliverable.path,
|
|
10578
|
+
deliverable.sourceTruth,
|
|
10579
|
+
])),
|
|
10580
|
+
});
|
|
10140
10581
|
process.stdout.write(`${JSON.stringify({ valid: true, actionCount: actions.length }, null, 2)}\n`);
|
|
10141
10582
|
}
|
|
10142
10583
|
|
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.12";
|
|
9
9
|
|
|
10
10
|
const CAPABILITIES = [
|
|
11
11
|
"remote_registration",
|