@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.
@@ -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
- const CONNECTOR_VERSION = "0.1.0-beta.15";
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, do not create ownerless executable todo tasks. Set assigneeAgentId only when the target agent id came from the current AMaster task context or an agent_hire action in this same batch. Do not invent agent ids, role ids, or ids from another company. If you are unsure, omit assigneeAgentId and assigneeUserId; AMaster will assign the current executing agent for todo/in_progress children. Use backlog only when a task is intentionally unassigned.",
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 = commandLegacyTaskDecompositionRequirement(command);
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
- child.stdout.on("data", (chunk) => {
5005
- const text = chunk.toString("utf8");
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, chunk.length);
5087
+ options.onOutput?.("stdout", text, byteLength);
5009
5088
  maybeSchedulePiCompletionDrain(text);
5010
5089
  if (outputBytes.stdout > maxOutputBytes) killForOutputFlood("stdout");
5011
5090
  });
5012
- child.stderr.on("data", (chunk) => {
5013
- const text = chunk.toString("utf8");
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, chunk.length);
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 = commandLegacyTaskDecompositionRequirement(command);
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
- "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",
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
  }
@@ -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.15";
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.15",
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"