@amaster.ai/employee-runtime-connector 0.1.0-beta.15 → 0.1.0-beta.17
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 +231 -17
- package/dist/amaster-runtime.mjs +1 -1
- package/package.json +3 -1
|
@@ -286,6 +286,7 @@ function parseCodexJsonl(stdout) {
|
|
|
286
286
|
let sessionId = null;
|
|
287
287
|
let summary = "";
|
|
288
288
|
let errorMessage = null;
|
|
289
|
+
let terminalEvent = null;
|
|
289
290
|
const usage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
290
291
|
|
|
291
292
|
for (const rawLine of String(stdout ?? "").split(/\r?\n/)) {
|
|
@@ -303,6 +304,7 @@ function parseCodexJsonl(stdout) {
|
|
|
303
304
|
continue;
|
|
304
305
|
}
|
|
305
306
|
if (event.type === "turn.completed") {
|
|
307
|
+
terminalEvent = "completed";
|
|
306
308
|
const eventUsage = asRecord(event.usage);
|
|
307
309
|
usage.inputTokens = readNumber(eventUsage.input_tokens, usage.inputTokens);
|
|
308
310
|
usage.cachedInputTokens = readNumber(eventUsage.cached_input_tokens, usage.cachedInputTokens);
|
|
@@ -314,12 +316,13 @@ function parseCodexJsonl(stdout) {
|
|
|
314
316
|
continue;
|
|
315
317
|
}
|
|
316
318
|
if (event.type === "turn.failed") {
|
|
319
|
+
terminalEvent = "failed";
|
|
317
320
|
const error = asRecord(event.error);
|
|
318
321
|
errorMessage = readString(error.message) ?? errorMessage;
|
|
319
322
|
}
|
|
320
323
|
}
|
|
321
324
|
|
|
322
|
-
return { sessionId, summary, usage, errorMessage };
|
|
325
|
+
return { sessionId, summary, usage, errorMessage: terminalEvent === "completed" ? null : errorMessage };
|
|
323
326
|
}
|
|
324
327
|
|
|
325
328
|
function buildCodexErrorHaystack(input) {
|
|
@@ -1653,6 +1656,26 @@ function shouldPreserveExecutorJsonlForTranscript(executorKind, event) {
|
|
|
1653
1656
|
return false;
|
|
1654
1657
|
}
|
|
1655
1658
|
|
|
1659
|
+
// ---- bundled module: src/amaster-runtime-daemon/utf8-stream-decoder.mjs ----
|
|
1660
|
+
function createUtf8StreamDecoder(onText) {
|
|
1661
|
+
const decoder = new StringDecoder("utf8");
|
|
1662
|
+
let ended = false;
|
|
1663
|
+
|
|
1664
|
+
return {
|
|
1665
|
+
write(chunk) {
|
|
1666
|
+
if (ended) throw new Error("UTF-8 stream decoder received data after end");
|
|
1667
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1668
|
+
onText(decoder.write(buffer), buffer.length);
|
|
1669
|
+
},
|
|
1670
|
+
end() {
|
|
1671
|
+
if (ended) return;
|
|
1672
|
+
ended = true;
|
|
1673
|
+
const trailingText = decoder.end();
|
|
1674
|
+
if (trailingText) onText(trailingText, 0);
|
|
1675
|
+
},
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1656
1679
|
// ---- bundled module: src/amaster-runtime-daemon/version-drift.mjs ----
|
|
1657
1680
|
function normalizeRuntimeVersionText(value) {
|
|
1658
1681
|
if (typeof value !== "string") return null;
|
|
@@ -2335,7 +2358,8 @@ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync
|
|
|
2335
2358
|
import { homedir, hostname } from "node:os";
|
|
2336
2359
|
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
2337
2360
|
import { spawn, spawnSync } from "node:child_process";
|
|
2338
|
-
|
|
2361
|
+
import { StringDecoder } from "node:string_decoder";
|
|
2362
|
+
const CONNECTOR_VERSION = "0.1.0-beta.17";
|
|
2339
2363
|
const MAX_INFERRED_ARTIFACT_UPLOADS = 20;
|
|
2340
2364
|
const MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
|
|
2341
2365
|
const CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -3459,6 +3483,52 @@ function commandLegacyTaskDecompositionRequirement(command) {
|
|
|
3459
3483
|
return legacyTaskDecompositionRequirement(asRecord(payload.contextSnapshot));
|
|
3460
3484
|
}
|
|
3461
3485
|
|
|
3486
|
+
const TASK_DECOMPOSITION_MODES = new Set(["none", "required", "suggest_only"]);
|
|
3487
|
+
const TASK_DECOMPOSITION_SOURCE_TYPES = new Set([
|
|
3488
|
+
"system_default",
|
|
3489
|
+
"system_migration",
|
|
3490
|
+
"operator",
|
|
3491
|
+
"api",
|
|
3492
|
+
"accepted_plan",
|
|
3493
|
+
"workflow",
|
|
3494
|
+
]);
|
|
3495
|
+
|
|
3496
|
+
function commandTaskDecompositionRequirement(command) {
|
|
3497
|
+
const payload = asRecord(command.payload);
|
|
3498
|
+
const context = asRecord(payload.contextSnapshot);
|
|
3499
|
+
const envelope = asRecord(context.authorizationEnvelope);
|
|
3500
|
+
const authority = readString(envelope.decompositionRequirementAuthority) ?? "shadow";
|
|
3501
|
+
if (authority === "shadow") return commandLegacyTaskDecompositionRequirement(command);
|
|
3502
|
+
if (authority !== "enforce") {
|
|
3503
|
+
throw new Error(`Invalid typed decomposition requirement authority ${authority}`);
|
|
3504
|
+
}
|
|
3505
|
+
const requirement = asRecord(envelope.decompositionRequirement);
|
|
3506
|
+
const source = asRecord(requirement.source);
|
|
3507
|
+
const mode = readString(requirement.mode);
|
|
3508
|
+
const sourceType = readString(source.type);
|
|
3509
|
+
const sourceId = readString(source.id);
|
|
3510
|
+
const sourceRevision = readString(source.revision);
|
|
3511
|
+
const requirementKeys = Object.keys(requirement).sort().join(",");
|
|
3512
|
+
const sourceKeys = Object.keys(source).sort().join(",");
|
|
3513
|
+
if (
|
|
3514
|
+
!TASK_DECOMPOSITION_MODES.has(mode)
|
|
3515
|
+
|| !TASK_DECOMPOSITION_SOURCE_TYPES.has(sourceType)
|
|
3516
|
+
|| !sourceId
|
|
3517
|
+
|| !sourceRevision
|
|
3518
|
+
|| requirementKeys !== "mode,source"
|
|
3519
|
+
|| sourceKeys !== "id,revision,type"
|
|
3520
|
+
) {
|
|
3521
|
+
throw new Error("Candidate command requires one strict typed decomposition requirement");
|
|
3522
|
+
}
|
|
3523
|
+
return {
|
|
3524
|
+
required: mode === "required",
|
|
3525
|
+
suggested: mode === "suggest_only",
|
|
3526
|
+
authorityMode: "enforce",
|
|
3527
|
+
source: "typed_task_requirement",
|
|
3528
|
+
sourceRef: { type: sourceType, id: sourceId, revision: sourceRevision, mode },
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3462
3532
|
function normalizeAgentInstructionsFiles(bundle) {
|
|
3463
3533
|
const files = asRecord(bundle.files);
|
|
3464
3534
|
const selected = [];
|
|
@@ -3565,7 +3635,7 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3565
3635
|
"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. For a custom target with no business revision, use the current run id as revisionId. For an existing issue_document target, use only the exact current revision from task context and never invent a revision id. When the same runtime action batch first upserts the target issue document, omit target.revisionId; the connector binds the created revision before posting the confirmation.",
|
|
3566
3636
|
"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.",
|
|
3567
3637
|
"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.",
|
|
3568
|
-
"For create_child_task,
|
|
3638
|
+
"For create_child_task, set a stable workKey for the business work, plus parentDependency required or informational. 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. An ownerless backlog child must set ownerDisposition intentionally_unassigned and ownerReason; null owner alone is not an auditable disposition.",
|
|
3569
3639
|
"Create a child only for an independently executable residual work item. Do not create a nested child merely to prove that child creation works, and do not treat the current issue title as an instruction to recreate the issue itself. If the current child already embodies the requested test or work item, complete it directly.",
|
|
3570
3640
|
"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.",
|
|
3571
3641
|
"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.",
|
|
@@ -3575,6 +3645,9 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3575
3645
|
options.explicitDecompositionRequired
|
|
3576
3646
|
? "This standard task explicitly requires child task decomposition. The runtime action batch must include create_child_task or one suggest_tasks interaction unless child tasks already exist. A checklist in a comment or artifact is not decomposition. Create independently executable internal preparation tasks now. Use backlog for recorded work that should not start now, and use suggest_tasks for work that must wait for human approval. When a pending interaction is created, keep the parent in_review rather than in_progress or done."
|
|
3577
3647
|
: "",
|
|
3648
|
+
options.decompositionSuggested
|
|
3649
|
+
? "The typed task requirement suggests child task decomposition without making it a hard gate. Prefer create_child_task or suggest_tasks when decomposition materially helps; other valid mutations remain allowed."
|
|
3650
|
+
: "",
|
|
3578
3651
|
options.canOrchestrateOrganization
|
|
3579
3652
|
? "Pending-interaction batch gate: before emitting JSON, scan every agent_hire and create_child_task. Every co-batched hire must independently set executeBeforeConfirmation true; every create_child_task must independently be backlog, set executeBeforeConfirmation true, or move into suggest_tasks. blockParentUntilDone=false and a flagged assignee hire do not exempt a child. Otherwise move the hires to a later continuation and use suggest_tasks for work that must wait for acceptance. This applies to every pending interaction, including budget or business approval."
|
|
3580
3653
|
: "Pending-interaction batch gate: before emitting JSON, scan every create_child_task. Every create_child_task must independently be backlog, set executeBeforeConfirmation true, or move into suggest_tasks. blockParentUntilDone=false does not exempt a child. Use suggest_tasks for work that must wait for acceptance.",
|
|
@@ -3607,7 +3680,7 @@ function runtimeActionsInstruction(options = {}) {
|
|
|
3607
3680
|
"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.",
|
|
3608
3681
|
"For create_interaction request_confirmation, follow the complete example above and do not use options arrays.",
|
|
3609
3682
|
"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\"}}.",
|
|
3610
|
-
"For create_interaction suggest_tasks, use payload {\"version\":1,\"tasks\":[{\"clientKey\":\"task_key\",\"title\":\"Task title\",\"description\":\"Task description\",\"priority\":\"medium\"}]}.",
|
|
3683
|
+
"For create_interaction suggest_tasks, use payload {\"version\":1,\"tasks\":[{\"clientKey\":\"task_key\",\"workKey\":\"stable_business_work_key\",\"title\":\"Task title\",\"description\":\"Task description\",\"priority\":\"medium\",\"ownerDisposition\":\"intentionally_unassigned\",\"ownerReason\":\"Why no owner is selected yet\",\"parentDependency\":\"informational\"}]}. Use ownerDisposition assigned_agent/assigned_user with the matching assignee id for required parent dependencies, or intentionally_unassigned with ownerReason only for informational work. parentDependency required creates a first-class parent blocker when accepted; informational does not.",
|
|
3611
3684
|
"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.",
|
|
3612
3685
|
"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.",
|
|
3613
3686
|
"Do not copy placeholder names, placeholder summaries, placeholder action arrays, or placeholder paths. Every title, body, role, idempotencyKey, artifact path, and task description must be specific to the current issue and company.",
|
|
@@ -3909,9 +3982,11 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3909
3982
|
const authorizationEnvelope = runtimeActionAuthorizationEnvelope(command);
|
|
3910
3983
|
const canOrchestrateOrganization = canRuntimeAgentOrchestrateOrganization(context)
|
|
3911
3984
|
&& authorizationEnvelope.allowedActionClasses.has("organization_mutation");
|
|
3912
|
-
const decompositionRequirement =
|
|
3985
|
+
const decompositionRequirement = commandTaskDecompositionRequirement(command);
|
|
3913
3986
|
const explicitDecompositionRequired = decompositionRequirement.required
|
|
3914
3987
|
&& runtimeActionChildIssueSummaryEntries(command).length === 0;
|
|
3988
|
+
const decompositionSuggested = decompositionRequirement.suggested === true
|
|
3989
|
+
&& runtimeActionChildIssueSummaryEntries(command).length === 0;
|
|
3915
3990
|
const executingAgent = asRecord(context.paperclipExecutingAgent);
|
|
3916
3991
|
const currentAgentAdapterType = readString(executingAgent.adapterType);
|
|
3917
3992
|
const validatorContentTypes = runtimeAttachmentContentTypes(command);
|
|
@@ -3944,6 +4019,7 @@ function buildCommandPrompt(command, workspace, materializedAttachments = [], op
|
|
|
3944
4019
|
canOrchestrateOrganization,
|
|
3945
4020
|
currentAgentAdapterType,
|
|
3946
4021
|
explicitDecompositionRequired,
|
|
4022
|
+
decompositionSuggested,
|
|
3947
4023
|
artifactVerifierCommands: options.artifactVerifierCommands,
|
|
3948
4024
|
actionValidatorCommand,
|
|
3949
4025
|
authorizationEnvelope,
|
|
@@ -4865,6 +4941,8 @@ function runExecutor(command, args, options) {
|
|
|
4865
4941
|
let stoppedExitDrainTimer = null;
|
|
4866
4942
|
let completionOutputDrainTimer = null;
|
|
4867
4943
|
let piCompletionOutputLineBuffer = "";
|
|
4944
|
+
let stdoutDecoder = null;
|
|
4945
|
+
let stderrDecoder = null;
|
|
4868
4946
|
const outputBytes = { stdout: 0, stderr: 0 };
|
|
4869
4947
|
const maxOutputBytes = parsePositiveInteger(options.maxOutputBytes, 50 * 1024 * 1024);
|
|
4870
4948
|
const maxRssMb = parsePositiveInteger(options.maxRssMb, 0);
|
|
@@ -4879,6 +4957,8 @@ function runExecutor(command, args, options) {
|
|
|
4879
4957
|
const finish = (result) => {
|
|
4880
4958
|
if (settled) return;
|
|
4881
4959
|
settled = true;
|
|
4960
|
+
stdoutDecoder?.end();
|
|
4961
|
+
stderrDecoder?.end();
|
|
4882
4962
|
if (timer) clearTimeout(timer);
|
|
4883
4963
|
if (rssTimer) clearInterval(rssTimer);
|
|
4884
4964
|
if (stopKillTimer) clearTimeout(stopKillTimer);
|
|
@@ -5001,21 +5081,25 @@ function runExecutor(command, args, options) {
|
|
|
5001
5081
|
}, 1000);
|
|
5002
5082
|
rssTimer.unref?.();
|
|
5003
5083
|
}
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
outputBytes.stdout += chunk.length;
|
|
5084
|
+
stdoutDecoder = createUtf8StreamDecoder((text, byteLength) => {
|
|
5085
|
+
outputBytes.stdout += byteLength;
|
|
5007
5086
|
stdout = appendBounded(stdout, text);
|
|
5008
|
-
options.onOutput?.("stdout", text,
|
|
5087
|
+
options.onOutput?.("stdout", text, byteLength);
|
|
5009
5088
|
maybeSchedulePiCompletionDrain(text);
|
|
5010
5089
|
if (outputBytes.stdout > maxOutputBytes) killForOutputFlood("stdout");
|
|
5011
5090
|
});
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
outputBytes.stderr += chunk.length;
|
|
5091
|
+
stderrDecoder = createUtf8StreamDecoder((text, byteLength) => {
|
|
5092
|
+
outputBytes.stderr += byteLength;
|
|
5015
5093
|
stderr = appendBounded(stderr, text);
|
|
5016
|
-
options.onOutput?.("stderr", text,
|
|
5094
|
+
options.onOutput?.("stderr", text, byteLength);
|
|
5017
5095
|
if (outputBytes.stderr > maxOutputBytes) killForOutputFlood("stderr");
|
|
5018
5096
|
});
|
|
5097
|
+
child.stdout.on("data", (chunk) => {
|
|
5098
|
+
if (!settled) stdoutDecoder.write(chunk);
|
|
5099
|
+
});
|
|
5100
|
+
child.stderr.on("data", (chunk) => {
|
|
5101
|
+
if (!settled) stderrDecoder.write(chunk);
|
|
5102
|
+
});
|
|
5019
5103
|
child.on("error", (err) => {
|
|
5020
5104
|
finish({ exitCode: null, signal: null, timedOut: false, spawnError: err.message });
|
|
5021
5105
|
});
|
|
@@ -6326,6 +6410,8 @@ function hashFileSha256(filePath) {
|
|
|
6326
6410
|
}
|
|
6327
6411
|
|
|
6328
6412
|
function runtimeChildActionFingerprint(action, issueId, parentIssueId) {
|
|
6413
|
+
const workKey = readString(action.workKey);
|
|
6414
|
+
if (workKey) return `amaster-work:${workKey}`;
|
|
6329
6415
|
const explicit = readString(action.idempotencyKey);
|
|
6330
6416
|
if (explicit) return explicit.slice(0, 220);
|
|
6331
6417
|
const clientKey = readString(action.clientKey) ?? readString(action.key);
|
|
@@ -6916,10 +7002,14 @@ function normalizeRuntimeSuggestedTask(task, index) {
|
|
|
6916
7002
|
const priority = normalizeRuntimeActionPriority(record.priority);
|
|
6917
7003
|
return {
|
|
6918
7004
|
clientKey: runtimeInteractionOptionId(record.clientKey ?? record.key ?? title, fallbackClientKey),
|
|
7005
|
+
...(readString(record.workKey) ? { workKey: readString(record.workKey) } : {}),
|
|
6919
7006
|
title: title.slice(0, 240),
|
|
6920
7007
|
...(readString(record.description) ? { description: readString(record.description) } : {}),
|
|
6921
7008
|
...(readString(record.assigneeAgentId) ? { assigneeAgentId: readString(record.assigneeAgentId) } : {}),
|
|
6922
7009
|
...(readString(record.assigneeUserId) ? { assigneeUserId: readString(record.assigneeUserId) } : {}),
|
|
7010
|
+
...(readString(record.ownerDisposition) ? { ownerDisposition: readString(record.ownerDisposition) } : {}),
|
|
7011
|
+
...(readString(record.ownerReason) ? { ownerReason: readString(record.ownerReason) } : {}),
|
|
7012
|
+
...(readString(record.parentDependency) ? { parentDependency: readString(record.parentDependency) } : {}),
|
|
6923
7013
|
...(readString(record.projectId) ? { projectId: readString(record.projectId) } : {}),
|
|
6924
7014
|
...(readString(record.parentId) ? { parentId: readString(record.parentId) } : {}),
|
|
6925
7015
|
...(priority ? { priority } : {}),
|
|
@@ -7230,7 +7320,7 @@ const RUNTIME_ACTION_ALLOWED_FIELDS = new Map([
|
|
|
7230
7320
|
"title", "description", "status", "priority", "assigneeAgentId", "assigneeUserId",
|
|
7231
7321
|
"assigneeAgentClientKey", "agentClientKey", "blockParentUntilDone", "executeBeforeConfirmation",
|
|
7232
7322
|
"clientKey", "idempotencyKey", "parentClientKey", "blockedByIssueIds", "blockedByClientKeys", "create_child_task",
|
|
7233
|
-
"inputWorkProductIds",
|
|
7323
|
+
"inputWorkProductIds", "workKey", "ownerDisposition", "ownerReason", "parentDependency",
|
|
7234
7324
|
])],
|
|
7235
7325
|
["agent_hire", new Set([
|
|
7236
7326
|
"clientKey", "idempotencyKey", "key", "companyId", "role", "title", "icon", "reportsTo",
|
|
@@ -7514,11 +7604,42 @@ function assertRuntimeCreateInteractionPayload(kind, payload) {
|
|
|
7514
7604
|
for (const [index, rawTask] of tasks.entries()) {
|
|
7515
7605
|
const task = asRecord(rawTask);
|
|
7516
7606
|
const clientKey = readString(task.clientKey);
|
|
7607
|
+
const workKey = readString(task.workKey);
|
|
7517
7608
|
const title = readString(task.title);
|
|
7518
7609
|
assertRuntimeActionValid(clientKey && clientKey.length <= 120, `suggest_tasks payload tasks[${index}].clientKey is required`);
|
|
7519
7610
|
assertRuntimeActionValid(!clientKeys.has(clientKey), `suggest_tasks payload tasks[${index}].clientKey must be unique`);
|
|
7520
7611
|
clientKeys.add(clientKey);
|
|
7612
|
+
assertRuntimeActionValid(workKey && workKey.length <= 120, `suggest_tasks payload tasks[${index}].workKey is required`);
|
|
7521
7613
|
assertRuntimeActionValid(title && title.length <= 240, `suggest_tasks payload tasks[${index}].title is required`);
|
|
7614
|
+
const ownerDisposition = readString(task.ownerDisposition);
|
|
7615
|
+
const assigneeAgentId = readString(task.assigneeAgentId);
|
|
7616
|
+
const assigneeUserId = readString(task.assigneeUserId);
|
|
7617
|
+
assertRuntimeActionValid(
|
|
7618
|
+
["assigned_agent", "assigned_user", "intentionally_unassigned"].includes(ownerDisposition ?? ""),
|
|
7619
|
+
`suggest_tasks payload tasks[${index}].ownerDisposition is required`,
|
|
7620
|
+
);
|
|
7621
|
+
assertRuntimeActionValid(
|
|
7622
|
+
ownerDisposition !== "assigned_agent" || Boolean(assigneeAgentId),
|
|
7623
|
+
`suggest_tasks payload tasks[${index}] assigned_agent requires assigneeAgentId`,
|
|
7624
|
+
);
|
|
7625
|
+
assertRuntimeActionValid(
|
|
7626
|
+
ownerDisposition !== "assigned_user" || Boolean(assigneeUserId),
|
|
7627
|
+
`suggest_tasks payload tasks[${index}] assigned_user requires assigneeUserId`,
|
|
7628
|
+
);
|
|
7629
|
+
assertRuntimeActionValid(
|
|
7630
|
+
ownerDisposition !== "intentionally_unassigned"
|
|
7631
|
+
|| (!assigneeAgentId && !assigneeUserId && Boolean(readString(task.ownerReason))),
|
|
7632
|
+
`suggest_tasks payload tasks[${index}] intentionally_unassigned requires ownerReason and forbids assignee ids`,
|
|
7633
|
+
);
|
|
7634
|
+
const parentDependency = readString(task.parentDependency);
|
|
7635
|
+
assertRuntimeActionValid(
|
|
7636
|
+
["required", "informational"].includes(parentDependency ?? ""),
|
|
7637
|
+
`suggest_tasks payload tasks[${index}].parentDependency is required`,
|
|
7638
|
+
);
|
|
7639
|
+
assertRuntimeActionValid(
|
|
7640
|
+
ownerDisposition !== "intentionally_unassigned" || parentDependency !== "required",
|
|
7641
|
+
"intentionally_unassigned work cannot be a required parent dependency",
|
|
7642
|
+
);
|
|
7522
7643
|
}
|
|
7523
7644
|
}
|
|
7524
7645
|
}
|
|
@@ -7655,7 +7776,7 @@ function assertDelegatedParentWaitDisposition(actions) {
|
|
|
7655
7776
|
}
|
|
7656
7777
|
|
|
7657
7778
|
function assertExplicitTaskDecomposition(command, actions) {
|
|
7658
|
-
const requirement =
|
|
7779
|
+
const requirement = commandTaskDecompositionRequirement(command);
|
|
7659
7780
|
if (!requirement.required) return;
|
|
7660
7781
|
if (runtimeActionChildIssueSummaryEntries(command).length > 0) return;
|
|
7661
7782
|
const hasDecompositionAction = actions.some((action) => {
|
|
@@ -7666,12 +7787,15 @@ function assertExplicitTaskDecomposition(command, actions) {
|
|
|
7666
7787
|
});
|
|
7667
7788
|
if (hasDecompositionAction) return;
|
|
7668
7789
|
const error = new Error(
|
|
7669
|
-
|
|
7790
|
+
requirement.authorityMode === "enforce"
|
|
7791
|
+
? "task explicitly requires child task decomposition under the typed task requirement; emit create_child_task or suggest_tasks instead of only describing a checklist"
|
|
7792
|
+
: "task explicitly requires child task decomposition under the legacy text hard gate; emit create_child_task or suggest_tasks instead of only describing a checklist",
|
|
7670
7793
|
);
|
|
7671
7794
|
error.runtimeActionType = "decomposition";
|
|
7672
7795
|
error.runtimeActionReason = error.message;
|
|
7673
7796
|
error.runtimeActionDecompositionRequirementSource = requirement.source;
|
|
7674
7797
|
error.runtimeActionDecompositionMatch = requirement.match;
|
|
7798
|
+
error.runtimeActionDecompositionSourceRef = requirement.sourceRef;
|
|
7675
7799
|
throw error;
|
|
7676
7800
|
}
|
|
7677
7801
|
|
|
@@ -7705,12 +7829,59 @@ function preflightRuntimeAction(action, preflightContext, actionIndex) {
|
|
|
7705
7829
|
if (type === "create_child_task") {
|
|
7706
7830
|
assertNoPlaceholderRuntimeActionText(fields, ["title", "description"]);
|
|
7707
7831
|
assertRuntimeActionValid(readBoundedActionString(fields, "title", 240), "create_child_task action requires title");
|
|
7832
|
+
if (fields.workKey !== undefined) {
|
|
7833
|
+
assertRuntimeActionValid(Boolean(readString(fields.workKey)), "create_child_task workKey must be a non-empty string");
|
|
7834
|
+
}
|
|
7708
7835
|
if (fields.status !== undefined) {
|
|
7709
7836
|
assertRuntimeActionValid(readActionStatus(fields.status), `create_child_task status is unsupported: ${String(fields.status)}`);
|
|
7710
7837
|
}
|
|
7711
7838
|
if (fields.priority !== undefined) {
|
|
7712
7839
|
assertRuntimeActionValid(readActionPriority(fields.priority), `create_child_task priority is unsupported: ${String(fields.priority)}`);
|
|
7713
7840
|
}
|
|
7841
|
+
const childStatus = readActionStatus(fields.status) ?? "todo";
|
|
7842
|
+
if (fields.parentDependency !== undefined) {
|
|
7843
|
+
assertRuntimeActionValid(
|
|
7844
|
+
["required", "informational"].includes(readString(fields.parentDependency) ?? ""),
|
|
7845
|
+
"create_child_task parentDependency must be required or informational",
|
|
7846
|
+
);
|
|
7847
|
+
assertRuntimeActionValid(
|
|
7848
|
+
(readString(fields.parentDependency) === "required") === (fields.blockParentUntilDone === true || childStatus === "blocked"),
|
|
7849
|
+
"create_child_task parentDependency must match blockParentUntilDone",
|
|
7850
|
+
);
|
|
7851
|
+
}
|
|
7852
|
+
const ownerDisposition = readString(fields.ownerDisposition);
|
|
7853
|
+
if (ownerDisposition) {
|
|
7854
|
+
assertRuntimeActionValid(
|
|
7855
|
+
["assigned_agent", "assigned_user", "intentionally_unassigned"].includes(ownerDisposition),
|
|
7856
|
+
"create_child_task ownerDisposition is unsupported",
|
|
7857
|
+
);
|
|
7858
|
+
}
|
|
7859
|
+
if (
|
|
7860
|
+
childStatus === "backlog"
|
|
7861
|
+
&& !readString(fields.assigneeAgentId)
|
|
7862
|
+
&& !readString(fields.assigneeUserId)
|
|
7863
|
+
&& !readString(fields.assigneeAgentClientKey)
|
|
7864
|
+
&& !readString(fields.agentClientKey)
|
|
7865
|
+
) {
|
|
7866
|
+
assertRuntimeActionValid(
|
|
7867
|
+
ownerDisposition === "intentionally_unassigned" && Boolean(readString(fields.ownerReason)),
|
|
7868
|
+
"ownerless backlog create_child_task requires ownerDisposition intentionally_unassigned and ownerReason",
|
|
7869
|
+
);
|
|
7870
|
+
}
|
|
7871
|
+
if (ownerDisposition === "intentionally_unassigned") {
|
|
7872
|
+
assertRuntimeActionValid(
|
|
7873
|
+
!readString(fields.assigneeAgentId)
|
|
7874
|
+
&& !readString(fields.assigneeUserId)
|
|
7875
|
+
&& !readString(fields.assigneeAgentClientKey)
|
|
7876
|
+
&& !readString(fields.agentClientKey)
|
|
7877
|
+
&& Boolean(readString(fields.ownerReason)),
|
|
7878
|
+
"create_child_task intentionally_unassigned requires ownerReason and forbids assignee fields",
|
|
7879
|
+
);
|
|
7880
|
+
assertRuntimeActionValid(
|
|
7881
|
+
readString(fields.parentDependency) !== "required",
|
|
7882
|
+
"intentionally_unassigned work cannot be a required parent dependency",
|
|
7883
|
+
);
|
|
7884
|
+
}
|
|
7714
7885
|
assertRuntimeActionValid(
|
|
7715
7886
|
!readString(fields.assigneeAgentId) || (!readString(fields.assigneeAgentClientKey) && !readString(fields.agentClientKey)),
|
|
7716
7887
|
"create_child_task action cannot combine assigneeAgentId with assigneeAgentClientKey/agentClientKey",
|
|
@@ -7915,6 +8086,41 @@ function preflightRuntimeActionBatch(actions, options = {}) {
|
|
|
7915
8086
|
assertDelegatedParentWaitDisposition(actions);
|
|
7916
8087
|
const orderedActions = orderRuntimeActionsByClientKeyDependencies(actions);
|
|
7917
8088
|
const documentActionIndexByKey = new Map();
|
|
8089
|
+
const workKeyOwner = new Map();
|
|
8090
|
+
const hasSuggestedTaskProposal = orderedActions.some((action) => {
|
|
8091
|
+
const fields = runtimeActionBusinessFields(action);
|
|
8092
|
+
return readRuntimeActionType(fields) === "create_interaction" && inferCreateInteractionKind(fields) === "suggest_tasks";
|
|
8093
|
+
});
|
|
8094
|
+
for (const action of orderedActions) {
|
|
8095
|
+
const fields = runtimeActionBusinessFields(action);
|
|
8096
|
+
const actionIndex = actionIndexByObject.get(action);
|
|
8097
|
+
if (hasSuggestedTaskProposal && readRuntimeActionType(fields) === "create_child_task") {
|
|
8098
|
+
assertRuntimeActionValid(
|
|
8099
|
+
Boolean(readString(fields.workKey)),
|
|
8100
|
+
"create_child_task action requires workKey when the same batch also proposes suggest_tasks",
|
|
8101
|
+
);
|
|
8102
|
+
}
|
|
8103
|
+
const entries = readRuntimeActionType(fields) === "create_child_task"
|
|
8104
|
+
? [{ workKey: readString(fields.workKey), fieldPath: `actions[${actionIndex}].workKey` }]
|
|
8105
|
+
: readRuntimeActionType(fields) === "create_interaction" && inferCreateInteractionKind(fields) === "suggest_tasks"
|
|
8106
|
+
? (Array.isArray(asRecord(fields.payload).tasks) ? asRecord(fields.payload).tasks : []).map((task, taskIndex) => ({
|
|
8107
|
+
workKey: readString(asRecord(task).workKey),
|
|
8108
|
+
fieldPath: `actions[${actionIndex}].payload.tasks[${taskIndex}].workKey`,
|
|
8109
|
+
}))
|
|
8110
|
+
: [];
|
|
8111
|
+
for (const entry of entries) {
|
|
8112
|
+
if (!entry.workKey) continue;
|
|
8113
|
+
const priorPath = workKeyOwner.get(entry.workKey);
|
|
8114
|
+
if (priorPath) {
|
|
8115
|
+
const err = runtimeActionValidationError(
|
|
8116
|
+
`workKey ${entry.workKey} appears more than once in the runtime action batch (${priorPath}, ${entry.fieldPath})`,
|
|
8117
|
+
);
|
|
8118
|
+
err.runtimeActionFieldPath = entry.fieldPath;
|
|
8119
|
+
throw err;
|
|
8120
|
+
}
|
|
8121
|
+
workKeyOwner.set(entry.workKey, entry.fieldPath);
|
|
8122
|
+
}
|
|
8123
|
+
}
|
|
7918
8124
|
for (const action of orderedActions) {
|
|
7919
8125
|
if (readRuntimeActionType(action) !== "upsert_document") continue;
|
|
7920
8126
|
const key = readBoundedActionString(runtimeActionBusinessFields(action), "key", 64);
|
|
@@ -8227,7 +8433,7 @@ async function applyRuntimeAction(config, command, runtimeAuth, issueId, action,
|
|
|
8227
8433
|
? { inputWorkProductIds: fields.inputWorkProductIds.map(readString).filter(Boolean) }
|
|
8228
8434
|
: {}),
|
|
8229
8435
|
blockParentUntilDone,
|
|
8230
|
-
originKind: "amaster_runtime_child_task",
|
|
8436
|
+
originKind: readString(fields.workKey) ? "amaster_runtime_work" : "amaster_runtime_child_task",
|
|
8231
8437
|
originId: issueId,
|
|
8232
8438
|
originRunId: runId,
|
|
8233
8439
|
originFingerprint,
|
|
@@ -9090,6 +9296,9 @@ function runtimeActionDiagnostics(err, options = {}) {
|
|
|
9090
9296
|
...(err?.runtimeActionDecompositionMatch
|
|
9091
9297
|
? { decompositionMatch: err.runtimeActionDecompositionMatch }
|
|
9092
9298
|
: {}),
|
|
9299
|
+
...(err?.runtimeActionDecompositionSourceRef
|
|
9300
|
+
? { decompositionSourceRef: err.runtimeActionDecompositionSourceRef }
|
|
9301
|
+
: {}),
|
|
9093
9302
|
...(err?.runtimeActionInferenceTrace ? { inferenceTrace: err.runtimeActionInferenceTrace } : {}),
|
|
9094
9303
|
...(readString(err?.runtimeActionFieldPath) ? { fieldPath: readString(err.runtimeActionFieldPath) } : {}),
|
|
9095
9304
|
...(readString(err?.runtimeActionReceivedType) ? { receivedType: readString(err.runtimeActionReceivedType) } : {}),
|
|
@@ -9474,6 +9683,11 @@ function assertRuntimeMarkdownSourceTruth(sourceTruthValue, fieldPath, filePath)
|
|
|
9474
9683
|
const allowedFields = new Set(["status", "reason", "sources", "unverifiedClaims"]);
|
|
9475
9684
|
for (const field of Object.keys(sourceTruth)) {
|
|
9476
9685
|
if (!allowedFields.has(field)) {
|
|
9686
|
+
if (field === "assumptions") {
|
|
9687
|
+
throw runtimeDeliverableError(
|
|
9688
|
+
`${fieldPath}.assumptions is unsupported; use ${fieldPath}.unverifiedClaims instead`,
|
|
9689
|
+
);
|
|
9690
|
+
}
|
|
9477
9691
|
throw runtimeDeliverableError(`${fieldPath}.${field} is unsupported`);
|
|
9478
9692
|
}
|
|
9479
9693
|
}
|
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.17";
|
|
9
9
|
|
|
10
10
|
const CAPABILITIES = [
|
|
11
11
|
"remote_registration",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amaster.ai/employee-runtime-connector",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.17",
|
|
4
4
|
"description": "AMaster Employee runtime connector CLI and daemon",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "node scripts/build.mjs",
|
|
17
17
|
"clean": "node scripts/clean.mjs",
|
|
18
|
+
"phase0b:live": "node probes/runtime-v2-phase0b/live-probe.mjs",
|
|
19
|
+
"phase0b:test": "node --test probes/runtime-v2-phase0b/*.test.mjs",
|
|
18
20
|
"typecheck": "node --check scripts/build.mjs && node --check scripts/bundle.mjs && node --check src/amaster-runtime.mjs && node --check src/amaster-runtime-daemon.mjs",
|
|
19
21
|
"test": "pnpm run build && node --test test/*.test.mjs src/amaster-runtime-daemon/*.test.mjs",
|
|
20
22
|
"prepack": "pnpm run test"
|