@mastra/factory 0.15.0 → 0.16.0-alpha.10
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/README.md +4 -3
- package/dist/boards/index.d.ts +1 -1
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js +2 -2
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +4 -1
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/semantics.d.ts +7 -0
- package/dist/boards/semantics.d.ts.map +1 -1
- package/dist/boards/semantics.js +15 -1
- package/dist/boards/semantics.js.map +1 -1
- package/dist/boards/transition-policy.d.ts +4 -0
- package/dist/boards/transition-policy.d.ts.map +1 -1
- package/dist/boards/transition-policy.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +6 -1
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +37 -3
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +4 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/factory.d.ts +6 -3
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +66 -18
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +6 -2
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/incidentio/agent-tools.d.ts +49 -0
- package/dist/integrations/incidentio/agent-tools.d.ts.map +1 -0
- package/dist/integrations/incidentio/agent-tools.js +90 -0
- package/dist/integrations/incidentio/agent-tools.js.map +1 -0
- package/dist/integrations/incidentio/claim.d.ts +8 -0
- package/dist/integrations/incidentio/claim.d.ts.map +1 -0
- package/dist/integrations/incidentio/claim.js +14 -0
- package/dist/integrations/incidentio/claim.js.map +1 -0
- package/dist/integrations/incidentio/default-rules.d.ts +46 -0
- package/dist/integrations/incidentio/default-rules.d.ts.map +1 -0
- package/dist/integrations/incidentio/default-rules.js +66 -0
- package/dist/integrations/incidentio/default-rules.js.map +1 -0
- package/dist/integrations/incidentio/integration.d.ts +48 -2
- package/dist/integrations/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/incidentio/integration.js +88 -2
- package/dist/integrations/incidentio/integration.js.map +1 -1
- package/dist/integrations/incidentio/issue-reconciler.d.ts +2 -0
- package/dist/integrations/incidentio/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/incidentio/issue-reconciler.js +38 -2
- package/dist/integrations/incidentio/issue-reconciler.js.map +1 -1
- package/dist/integrations/incidentio/routes.d.ts +71 -0
- package/dist/integrations/incidentio/routes.d.ts.map +1 -0
- package/dist/integrations/incidentio/routes.js +245 -0
- package/dist/integrations/incidentio/routes.js.map +1 -0
- package/dist/integrations/incidentio/rules.d.ts +55 -0
- package/dist/integrations/incidentio/rules.d.ts.map +1 -0
- package/dist/integrations/incidentio/rules.js +175 -0
- package/dist/integrations/incidentio/rules.js.map +1 -0
- package/dist/integrations/jira/adf.d.ts +26 -0
- package/dist/integrations/jira/adf.d.ts.map +1 -0
- package/dist/integrations/jira/adf.js +73 -0
- package/dist/integrations/jira/adf.js.map +1 -0
- package/dist/integrations/jira/agent-tools.d.ts +45 -0
- package/dist/integrations/jira/agent-tools.d.ts.map +1 -0
- package/dist/integrations/jira/agent-tools.js +87 -0
- package/dist/integrations/jira/agent-tools.js.map +1 -0
- package/dist/integrations/jira/api.d.ts +157 -0
- package/dist/integrations/jira/api.d.ts.map +1 -0
- package/dist/integrations/jira/api.js +141 -0
- package/dist/integrations/jira/api.js.map +1 -0
- package/dist/integrations/jira/claim.d.ts +9 -0
- package/dist/integrations/jira/claim.d.ts.map +1 -0
- package/dist/integrations/jira/claim.js +15 -0
- package/dist/integrations/jira/claim.js.map +1 -0
- package/dist/integrations/jira/default-rules.d.ts +47 -0
- package/dist/integrations/jira/default-rules.d.ts.map +1 -0
- package/dist/integrations/jira/default-rules.js +67 -0
- package/dist/integrations/jira/default-rules.js.map +1 -0
- package/dist/integrations/jira/integration.d.ts +114 -0
- package/dist/integrations/jira/integration.d.ts.map +1 -0
- package/dist/integrations/jira/integration.js +401 -0
- package/dist/integrations/jira/integration.js.map +1 -0
- package/dist/integrations/jira/issue-reconciler.d.ts +10 -0
- package/dist/integrations/jira/issue-reconciler.d.ts.map +1 -0
- package/dist/integrations/jira/issue-reconciler.js +70 -0
- package/dist/integrations/jira/issue-reconciler.js.map +1 -0
- package/dist/integrations/jira/reconciliation-config.d.ts +3 -0
- package/dist/integrations/jira/reconciliation-config.d.ts.map +1 -0
- package/dist/integrations/jira/reconciliation-config.js +17 -0
- package/dist/integrations/jira/reconciliation-config.js.map +1 -0
- package/dist/integrations/jira/routes.d.ts +54 -0
- package/dist/integrations/jira/routes.d.ts.map +1 -0
- package/dist/integrations/jira/routes.js +260 -0
- package/dist/integrations/jira/routes.js.map +1 -0
- package/dist/integrations/jira/rules.d.ts +57 -0
- package/dist/integrations/jira/rules.d.ts.map +1 -0
- package/dist/integrations/jira/rules.js +175 -0
- package/dist/integrations/jira/rules.js.map +1 -0
- package/dist/integrations/linear/claim.d.ts +8 -0
- package/dist/integrations/linear/claim.d.ts.map +1 -0
- package/dist/integrations/linear/claim.js +14 -0
- package/dist/integrations/linear/claim.js.map +1 -0
- package/dist/integrations/linear/default-rules.d.ts +1 -0
- package/dist/integrations/linear/default-rules.d.ts.map +1 -1
- package/dist/integrations/linear/default-rules.js +2 -0
- package/dist/integrations/linear/default-rules.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts +24 -6
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +257 -37
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts +10 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +74 -12
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +1 -1
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +27 -2
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/connect/routes.d.ts +39 -0
- package/dist/integrations/platform/connect/routes.d.ts.map +1 -0
- package/dist/integrations/platform/connect/routes.js +129 -0
- package/dist/integrations/platform/connect/routes.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +1 -1
- package/dist/integrations/platform/github/integration.js +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts +61 -4
- package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js +371 -22
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/agent-tools.d.ts +36 -0
- package/dist/integrations/platform/jira/agent-tools.d.ts.map +1 -0
- package/dist/integrations/platform/jira/agent-tools.js +85 -0
- package/dist/integrations/platform/jira/agent-tools.js.map +1 -0
- package/dist/integrations/platform/jira/integration.d.ts +65 -0
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -0
- package/dist/integrations/platform/jira/integration.js +540 -0
- package/dist/integrations/platform/jira/integration.js.map +1 -0
- package/dist/integrations/platform/jira/routes.d.ts +55 -0
- package/dist/integrations/platform/jira/routes.d.ts.map +1 -0
- package/dist/integrations/platform/jira/routes.js +274 -0
- package/dist/integrations/platform/jira/routes.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +34 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +271 -48
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +15 -5
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +2 -5
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +24 -4
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +8 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +1 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +180 -106
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -2
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +4 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/terminal-cleanup.d.ts +14 -1
- package/dist/rules/terminal-cleanup.d.ts.map +1 -1
- package/dist/rules/terminal-cleanup.js +20 -0
- package/dist/rules/terminal-cleanup.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -0
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +30 -2
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +13 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +11 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +72 -3
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +9 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +13 -2
- package/dist/rules/validation.js.map +1 -1
- package/dist/session/run-audit.d.ts +9 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +37 -6
- package/dist/session/run-audit.js.map +1 -1
- package/dist/skills/service.d.ts +12 -0
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +27 -1
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +11 -8
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +69 -24
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +41 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +159 -12
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/session-messaging.d.ts +23 -0
- package/dist/supervisor/session-messaging.d.ts.map +1 -0
- package/dist/supervisor/session-messaging.js +15 -0
- package/dist/supervisor/session-messaging.js.map +1 -0
- package/dist/supervisor/write-tools.d.ts +2 -1
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +11 -7
- package/dist/supervisor/write-tools.js.map +1 -1
- package/dist/work-item-branch.d.ts +1 -1
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +4 -2
- package/dist/work-item-branch.js.map +1 -1
- package/package.json +11 -11
package/dist/rules/dispatcher.js
CHANGED
|
@@ -3,11 +3,11 @@ import { boardForWorkItem, resolvePhaseSemantics, workItemPhaseSemantics } from
|
|
|
3
3
|
import { FACTORY_RULE_STAGES, externallyAuthoredWorkItem } from "./types.js";
|
|
4
4
|
import { createBoardRegistry } from "../boards/registry.js";
|
|
5
5
|
import "../boards/index.js";
|
|
6
|
-
import { FACTORY_RULE_MATERIALIZATION_KEY } from "../storage/domains/work-items/base.js";
|
|
6
|
+
import { FACTORY_RULE_MATERIALIZATION_KEY, WorkItemClaimConflictError } from "../storage/domains/work-items/base.js";
|
|
7
7
|
import { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata } from "./dispatch-errors.js";
|
|
8
8
|
import { isHumanActorId } from "../storage/domains/audit/actors.js";
|
|
9
|
-
import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
|
|
10
|
-
import { recordSessionRunStart } from "../session/run-audit.js";
|
|
9
|
+
import { resolvePromptInvocation, resolveSkillInvocation, resolveSkillResumeInvocation } from "../skills/service.js";
|
|
10
|
+
import { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun, isOpenRunLiveElsewhere, listSessionOpenRuns, recordSessionRunStart, waitForSessionRunAudit } from "../session/run-audit.js";
|
|
11
11
|
import { withWorkItemFeed } from "../storage/domains/comments/feed-context.js";
|
|
12
12
|
import { RequestContext } from "@mastra/core/request-context";
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
@@ -103,7 +103,7 @@ function watchRun(session, { timeoutMs, approvePlans, onAgentEnd, label, runStil
|
|
|
103
103
|
});
|
|
104
104
|
observed = await wait();
|
|
105
105
|
}
|
|
106
|
-
if (!observed) throw new
|
|
106
|
+
if (!observed) throw new FactoryDispatchError("run_terminal_event_missing", `${label} terminal event was not observed before timeout.`);
|
|
107
107
|
if (endReason === "error") throw new Error(`${label} ended in error.`);
|
|
108
108
|
if (endReason === "aborted") {
|
|
109
109
|
if (omFailure) {
|
|
@@ -158,7 +158,7 @@ function retryAt(now, attempts) {
|
|
|
158
158
|
return new Date(now.getTime() + Math.min(1e3 * 2 ** Math.max(0, attempts - 1), MAX_BACKOFF_MS));
|
|
159
159
|
}
|
|
160
160
|
function externalSourceForDecision(decision) {
|
|
161
|
-
const [integrationId, type] = decision.source === "github-pr" ? ["github", "pull-request"] : decision.source === "github-issue" ? ["github", "issue"] : decision.source === "linear-issue" ? ["linear", "issue"] : ["factory", "manual"];
|
|
161
|
+
const [integrationId, type] = decision.source === "github-pr" ? ["github", "pull-request"] : decision.source === "github-issue" ? ["github", "issue"] : decision.source === "linear-issue" ? ["linear", "issue"] : decision.source === "jira-issue" ? ["jira", "issue"] : decision.source === "incidentio-follow-up" ? ["incidentio", "issue"] : ["factory", "manual"];
|
|
162
162
|
return {
|
|
163
163
|
integrationId,
|
|
164
164
|
type,
|
|
@@ -243,6 +243,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
243
243
|
#timer;
|
|
244
244
|
#activeClaim;
|
|
245
245
|
#inFlight = /* @__PURE__ */ new Set();
|
|
246
|
+
#bindingSkillRuns = /* @__PURE__ */ new Map();
|
|
246
247
|
constructor(options) {
|
|
247
248
|
this.#audit = options.audit;
|
|
248
249
|
this.#controller = options.controller;
|
|
@@ -521,93 +522,120 @@ var FactoryDecisionDispatcher = class {
|
|
|
521
522
|
case "invokeSkill": {
|
|
522
523
|
if (await this.#roleSuperseded(record, decision.role)) return;
|
|
523
524
|
const binding = await this.#requireOrPrepareBinding(record, decision.role);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const run = watchRun(session, {
|
|
573
|
-
timeoutMs: this.#skillCompletionObservationTimeoutMs,
|
|
574
|
-
approvePlans: await this.#plansAreAutoApproved(record, item),
|
|
575
|
-
onAgentEnd: () => this.#roleSuperseded(record, decision.role),
|
|
576
|
-
label: "Factory skill run",
|
|
577
|
-
runStillActive: () => this.#controller.listActiveThreadRuns().some((active) => active.threadId === binding.threadId)
|
|
578
|
-
});
|
|
579
|
-
const sendKickoff = async () => {
|
|
580
|
-
const settled = await session.sendSignal({
|
|
581
|
-
id: deliveryId,
|
|
582
|
-
type: "user",
|
|
583
|
-
tagName: "user",
|
|
584
|
-
contents: kickoffContents
|
|
585
|
-
}, {
|
|
586
|
-
requestContext,
|
|
587
|
-
requireDelivery: true
|
|
588
|
-
}).accepted;
|
|
589
|
-
if (settled.action !== "wake" && settled.action !== "deliver") throw new Error(`Factory skill invocation signal did not reach the agent (${String(settled.action)}).`);
|
|
590
|
-
return settled;
|
|
591
|
-
};
|
|
592
|
-
try {
|
|
593
|
-
let settled = await sendKickoff();
|
|
594
|
-
if (settled.action === "deliver") {
|
|
595
|
-
if (!(await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) {
|
|
596
|
-
if (!await run.wait()) throw new Error("Factory skill invocation is waiting on a run that has not ended.");
|
|
597
|
-
run.arm();
|
|
598
|
-
settled = await sendKickoff();
|
|
599
|
-
if (settled.action !== "wake") throw new Error("Factory skill invocation was queued onto an ending run and never reached the agent.");
|
|
525
|
+
await this.#withBindingSkillRun(binding.id, async () => {
|
|
526
|
+
const item = record.workItemId ? await this.#storage.get({
|
|
527
|
+
orgId: record.orgId,
|
|
528
|
+
id: record.workItemId
|
|
529
|
+
}) : null;
|
|
530
|
+
const startedBy = item?.sessions[binding.role]?.startedBy;
|
|
531
|
+
if (!startedBy) throw new Error(`Factory binding ${binding.id} has no authenticated session owner.`);
|
|
532
|
+
await this.#primeCredentials?.({
|
|
533
|
+
orgId: record.orgId,
|
|
534
|
+
userId: startedBy
|
|
535
|
+
});
|
|
536
|
+
const session = await this.#requireSession(binding);
|
|
537
|
+
const requestContext = factoryRequestContext({
|
|
538
|
+
session,
|
|
539
|
+
binding,
|
|
540
|
+
userId: startedBy,
|
|
541
|
+
orgId: record.orgId
|
|
542
|
+
});
|
|
543
|
+
const resolved = decision.skillName === void 0 ? await resolvePromptInvocation(this.#controller, {
|
|
544
|
+
resourceId: binding.resourceId,
|
|
545
|
+
prompt: decision.prompt
|
|
546
|
+
}) : decision.resume === true ? await resolveSkillResumeInvocation(this.#controller, {
|
|
547
|
+
resourceId: binding.resourceId,
|
|
548
|
+
name: decision.skillName,
|
|
549
|
+
arguments: decision.arguments
|
|
550
|
+
}) : await resolveSkillInvocation(this.#controller, {
|
|
551
|
+
resourceId: binding.resourceId,
|
|
552
|
+
name: decision.skillName,
|
|
553
|
+
arguments: decision.arguments
|
|
554
|
+
});
|
|
555
|
+
await this.#switchThread(session, binding);
|
|
556
|
+
const deliveryId = record.deliveryGeneration === 0 ? record.id : `${record.id}:retry:${record.deliveryGeneration}`;
|
|
557
|
+
const runStillActive = () => this.#controller.listActiveThreadRuns().some((active) => active.threadId === binding.threadId);
|
|
558
|
+
while (true) {
|
|
559
|
+
const openRun = watchRun(session, {
|
|
560
|
+
timeoutMs: this.#skillCompletionObservationTimeoutMs,
|
|
561
|
+
approvePlans: false,
|
|
562
|
+
label: "Factory skill run",
|
|
563
|
+
runStillActive
|
|
564
|
+
});
|
|
565
|
+
try {
|
|
566
|
+
const open = (await listSessionOpenRuns(session)).filter((entry) => entry.bindingId === binding.id);
|
|
567
|
+
if (open.length === 0) break;
|
|
568
|
+
if (open.some((entry) => isOpenRunLiveElsewhere(entry, this.#ownerId))) throw new FactoryDispatchError("session_unavailable", `Factory binding ${binding.id} has a run in flight on another dispatcher.`);
|
|
569
|
+
if (!runStillActive()) break;
|
|
570
|
+
if (!await openRun.wait()) break;
|
|
571
|
+
} finally {
|
|
572
|
+
openRun.close();
|
|
600
573
|
}
|
|
574
|
+
await waitForSessionRunAudit(session);
|
|
601
575
|
}
|
|
602
|
-
await this.#
|
|
576
|
+
if (await this.#roleSuperseded(record, decision.role)) return;
|
|
577
|
+
if ((await this.#requireBinding(record, decision.role)).id !== binding.id) throw new FactoryDispatchError("session_unavailable", `Factory binding ${binding.id} changed while waiting for its open run to end.`);
|
|
578
|
+
if ((await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) return;
|
|
579
|
+
const kickoffContents = await withWorkItemFeed(this.#feedReader, {
|
|
580
|
+
orgId: record.orgId,
|
|
581
|
+
factoryProjectId: record.factoryProjectId,
|
|
582
|
+
workItemId: record.workItemId
|
|
583
|
+
}, resolved.message);
|
|
584
|
+
if (decision.cancelInFlight && session.stream.isActive()) session.abort();
|
|
585
|
+
const precedingMessage = decision.precedingMessage;
|
|
586
|
+
if (precedingMessage) await awaitNotification(() => session.sendNotificationSignal({
|
|
587
|
+
source: "factory",
|
|
588
|
+
kind: "stage-transition",
|
|
589
|
+
summary: precedingMessage,
|
|
590
|
+
priority: "medium",
|
|
591
|
+
payload: { message: precedingMessage },
|
|
592
|
+
sourceId: `${record.id}:stage-transition`,
|
|
593
|
+
dedupeKey: `${record.idempotencyKey}:stage-transition`
|
|
594
|
+
}, {
|
|
595
|
+
ifActive: { behavior: "deliver" },
|
|
596
|
+
ifIdle: { behavior: "persist" },
|
|
597
|
+
requestContext
|
|
598
|
+
}));
|
|
599
|
+
const run = watchRun(session, {
|
|
600
|
+
timeoutMs: this.#skillCompletionObservationTimeoutMs,
|
|
601
|
+
approvePlans: await this.#plansAreAutoApproved(record, item),
|
|
602
|
+
onAgentEnd: () => this.#roleSuperseded(record, decision.role),
|
|
603
|
+
label: "Factory skill run",
|
|
604
|
+
runStillActive
|
|
605
|
+
});
|
|
606
|
+
const sendKickoff = async () => {
|
|
607
|
+
const settled = await session.sendSignal({
|
|
608
|
+
id: deliveryId,
|
|
609
|
+
type: "user",
|
|
610
|
+
tagName: "user",
|
|
611
|
+
contents: kickoffContents
|
|
612
|
+
}, {
|
|
613
|
+
requestContext,
|
|
614
|
+
requireDelivery: true
|
|
615
|
+
}).accepted;
|
|
616
|
+
if (settled.action !== "wake" && settled.action !== "deliver") throw new FactoryDispatchError(settled.action ? "notification_delivery_failed" : "skill_delivery_ambiguous", `Factory skill invocation signal did not reach the agent (${String(settled.action)}).`);
|
|
617
|
+
return settled;
|
|
618
|
+
};
|
|
603
619
|
try {
|
|
604
|
-
await
|
|
605
|
-
|
|
606
|
-
|
|
620
|
+
let settled = await sendKickoff();
|
|
621
|
+
if (settled.action === "deliver") {
|
|
622
|
+
if (!(await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) {
|
|
623
|
+
if (!await run.wait()) throw new FactoryDispatchError("run_terminal_event_missing", "Factory skill invocation is waiting on a run whose terminal event was not observed.");
|
|
624
|
+
run.arm();
|
|
625
|
+
settled = await sendKickoff();
|
|
626
|
+
if (settled.action !== "wake") throw new FactoryDispatchError("skill_delivery_ambiguous", "Factory skill invocation was queued onto an ending run and never reached the agent.");
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
await this.#recordRunStart(session, binding, deliveryId, record.approvedBy ?? void 0, run.endReason, item?.title);
|
|
630
|
+
try {
|
|
631
|
+
await this.#withOpenRunHeartbeat(session, deliveryId, () => run.settle());
|
|
632
|
+
} catch (error) {
|
|
633
|
+
if (!(await run.supersededAtEnd() ?? await this.#roleSuperseded(record, decision.role))) throw error;
|
|
634
|
+
}
|
|
635
|
+
} finally {
|
|
636
|
+
run.close();
|
|
607
637
|
}
|
|
608
|
-
}
|
|
609
|
-
run.close();
|
|
610
|
-
}
|
|
638
|
+
});
|
|
611
639
|
return;
|
|
612
640
|
}
|
|
613
641
|
case "sendMessage": {
|
|
@@ -680,24 +708,42 @@ var FactoryDecisionDispatcher = class {
|
|
|
680
708
|
factoryProjectId: record.factoryProjectId,
|
|
681
709
|
decision
|
|
682
710
|
}) ?? null;
|
|
683
|
-
let result
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
711
|
+
let result;
|
|
712
|
+
try {
|
|
713
|
+
result = await this.#storage.upsert({
|
|
714
|
+
orgId: record.orgId,
|
|
715
|
+
userId: "factory-rule-dispatcher",
|
|
716
|
+
factoryProjectId: record.factoryProjectId,
|
|
717
|
+
input: {
|
|
718
|
+
externalSource: externalSourceForDecision(decision),
|
|
719
|
+
...decision.claimKey ? { claimKey: decision.claimKey } : {},
|
|
720
|
+
parentWorkItemId,
|
|
721
|
+
title: decision.title,
|
|
722
|
+
board: decision.board,
|
|
723
|
+
stages: [initialPhase],
|
|
724
|
+
sessions: {},
|
|
725
|
+
metadata: {
|
|
726
|
+
...decision.metadata,
|
|
727
|
+
[FACTORY_RULE_MATERIALIZATION_KEY]: record.idempotencyKey
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
reuseMode: "preserve"
|
|
731
|
+
});
|
|
732
|
+
} catch (error) {
|
|
733
|
+
if (error instanceof WorkItemClaimConflictError) return;
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
if (!result.created && decision.claimKey && !result.item.claimKey) {
|
|
737
|
+
const claimed = await this.#storage.claimWorkItem({
|
|
738
|
+
orgId: record.orgId,
|
|
739
|
+
id: result.item.id,
|
|
740
|
+
claimKey: decision.claimKey
|
|
741
|
+
});
|
|
742
|
+
if (claimed) result = {
|
|
743
|
+
...result,
|
|
744
|
+
item: claimed
|
|
745
|
+
};
|
|
746
|
+
}
|
|
701
747
|
const itemBoard = boardForWorkItem(result.item);
|
|
702
748
|
if (itemBoard !== decision.board) throw new Error(`The work item belongs to board "${itemBoard}", not "${decision.board}".`);
|
|
703
749
|
if (!result.item.parentWorkItemId && parentWorkItemId && parentWorkItemId !== result.item.id) {
|
|
@@ -821,6 +867,11 @@ var FactoryDecisionDispatcher = class {
|
|
|
821
867
|
const bindings = await this.#storage.listRunBindings(record.orgId, record.factoryProjectId, record.workItemId);
|
|
822
868
|
const own = bindings.filter((candidate) => candidate.role === role);
|
|
823
869
|
if (own.some((candidate) => candidate.status === "active")) return false;
|
|
870
|
+
const item = await this.#storage.get({
|
|
871
|
+
orgId: record.orgId,
|
|
872
|
+
id: record.workItemId
|
|
873
|
+
}).catch(() => null);
|
|
874
|
+
if (item && workItemPhaseSemantics(this.#boards, item)?.kind === "terminal") return true;
|
|
824
875
|
return own.some((revoked) => revoked.revokedAt !== null && revoked.revokedAt.getTime() >= record.createdAt.getTime() && bindings.some((successor) => successor.role !== role && successor.status === "active" && successor.resourceId === revoked.resourceId && successor.sessionId === revoked.sessionId && successor.threadId === revoked.threadId && successor.createdAt.getTime() >= revoked.revokedAt.getTime()));
|
|
825
876
|
}
|
|
826
877
|
async #requireOrPrepareBinding(record, role) {
|
|
@@ -864,6 +915,15 @@ var FactoryDecisionDispatcher = class {
|
|
|
864
915
|
if (session.thread.requireId() === binding.threadId) return;
|
|
865
916
|
await session.thread.switch({ threadId: binding.threadId });
|
|
866
917
|
}
|
|
918
|
+
async #withBindingSkillRun(bindingId, effect) {
|
|
919
|
+
const run = (this.#bindingSkillRuns.get(bindingId) ?? Promise.resolve()).catch(() => {}).then(effect);
|
|
920
|
+
this.#bindingSkillRuns.set(bindingId, run);
|
|
921
|
+
try {
|
|
922
|
+
await run;
|
|
923
|
+
} finally {
|
|
924
|
+
if (this.#bindingSkillRuns.get(bindingId) === run) this.#bindingSkillRuns.delete(bindingId);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
867
927
|
async #withLease(renew, effect) {
|
|
868
928
|
let renewalFailure;
|
|
869
929
|
let renewal = Promise.resolve();
|
|
@@ -886,6 +946,17 @@ var FactoryDecisionDispatcher = class {
|
|
|
886
946
|
await renewal;
|
|
887
947
|
}
|
|
888
948
|
}
|
|
949
|
+
/** Keep this process's claim on an open run fresh for as long as it is watching the run. */
|
|
950
|
+
async #withOpenRunHeartbeat(session, kickoffId, effect) {
|
|
951
|
+
if (!this.#audit) return effect();
|
|
952
|
+
const timer = setInterval(() => void heartbeatSessionOpenRun(session, kickoffId), Math.floor(FACTORY_OPEN_RUN_STALE_MS / 3));
|
|
953
|
+
timer.unref?.();
|
|
954
|
+
try {
|
|
955
|
+
return await effect();
|
|
956
|
+
} finally {
|
|
957
|
+
clearInterval(timer);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
889
960
|
async #recordRunStart(session, binding, kickoffId, approvedBy, observedEnd, workItemName) {
|
|
890
961
|
if (!this.#audit) return;
|
|
891
962
|
const humanApproved = isHumanActorId(approvedBy);
|
|
@@ -895,6 +966,8 @@ var FactoryDecisionDispatcher = class {
|
|
|
895
966
|
observedEnd,
|
|
896
967
|
run: {
|
|
897
968
|
kickoffId,
|
|
969
|
+
ownerId: this.#ownerId,
|
|
970
|
+
heartbeatAt: Date.now(),
|
|
898
971
|
bindingId: binding.id,
|
|
899
972
|
role: binding.role,
|
|
900
973
|
startedBy: humanApproved ? approvedBy : "factory-rule-dispatcher",
|
|
@@ -987,6 +1060,7 @@ const FACTORY_DISPATCH_CONSTANTS = {
|
|
|
987
1060
|
maxBackoffMs: MAX_BACKOFF_MS,
|
|
988
1061
|
skillCompletionObservationTimeoutMs: SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS,
|
|
989
1062
|
runRegistryHeartbeatMs: RUN_REGISTRY_HEARTBEAT_MS,
|
|
1063
|
+
openRunStaleMs: FACTORY_OPEN_RUN_STALE_MS,
|
|
990
1064
|
maxInFlight: MAX_IN_FLIGHT,
|
|
991
1065
|
stages: FACTORY_RULE_STAGES
|
|
992
1066
|
};
|