@mastra/factory 0.9.0 → 0.10.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/CHANGELOG.md +179 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +24 -8
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +1 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/provenance.d.ts +7 -2
- package/dist/integrations/github/provenance.d.ts.map +1 -1
- package/dist/integrations/github/provenance.js +16 -2
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +3 -0
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +165 -6
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +193 -127
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +50 -0
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +1 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/attention.d.ts +17 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +305 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +11 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/surface.d.ts +2 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +61 -43
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +35 -14
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.js +5 -5
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts +13 -0
- package/dist/rules/dispatch-errors.d.ts.map +1 -0
- package/dist/rules/dispatch-errors.js +77 -0
- package/dist/rules/dispatch-errors.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +9 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +118 -45
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +30 -14
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/terminal-cleanup.d.ts +1 -1
- package/dist/rules/terminal-cleanup.d.ts.map +1 -1
- package/dist/rules/terminal-cleanup.js +2 -2
- package/dist/rules/terminal-cleanup.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +1 -1
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -0
- 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/secret-encryption.d.ts +25 -0
- package/dist/secret-encryption.d.ts.map +1 -0
- package/dist/secret-encryption.js +96 -0
- package/dist/secret-encryption.js.map +1 -0
- package/dist/session/factory-session.d.ts +13 -3
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +15 -6
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +16 -10
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +74 -14
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/session/first-message-capture.d.ts +14 -10
- package/dist/session/first-message-capture.d.ts.map +1 -1
- package/dist/session/first-message-capture.js +17 -11
- package/dist/session/first-message-capture.js.map +1 -1
- package/dist/session/session-title.d.ts +7 -0
- package/dist/session/session-title.d.ts.map +1 -0
- package/dist/session/session-title.js +14 -0
- package/dist/session/session-title.js.map +1 -0
- package/dist/session/thread-title-mirror.d.ts +35 -0
- package/dist/session/thread-title-mirror.d.ts.map +1 -0
- package/dist/session/thread-title-mirror.js +51 -0
- package/dist/session/thread-title-mirror.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +3 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +33 -15
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.d.ts +3 -1
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +45 -25
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +6 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +21 -13
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +3 -1
- package/dist/storage/domains/integrations/base.d.ts.map +1 -1
- package/dist/storage/domains/integrations/base.js +28 -12
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +8 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +14 -2
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +7 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +115 -12
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +497 -43
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/work-item-branch.d.ts +23 -0
- package/dist/work-item-branch.d.ts.map +1 -0
- package/dist/work-item-branch.js +39 -0
- package/dist/work-item-branch.js.map +1 -0
- package/factory-skills/factory-rereview/SKILL.md +2 -0
- package/factory-skills/factory-review/SKILL.md +2 -0
- package/package.json +7 -7
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//#region src/rules/dispatch-errors.ts
|
|
2
|
+
const FAILURE_METADATA = {
|
|
3
|
+
session_unavailable: {
|
|
4
|
+
canRetry: true,
|
|
5
|
+
label: "Factory session unavailable"
|
|
6
|
+
},
|
|
7
|
+
source_control_missing: {
|
|
8
|
+
canRetry: true,
|
|
9
|
+
label: "Source-control connection unavailable"
|
|
10
|
+
},
|
|
11
|
+
source_repository_missing: {
|
|
12
|
+
canRetry: true,
|
|
13
|
+
label: "Source repository unavailable"
|
|
14
|
+
},
|
|
15
|
+
unsupported_provider_item: {
|
|
16
|
+
canRetry: false,
|
|
17
|
+
label: "Unsupported provider work item"
|
|
18
|
+
},
|
|
19
|
+
notification_delivery_failed: {
|
|
20
|
+
canRetry: true,
|
|
21
|
+
label: "Factory message delivery failed"
|
|
22
|
+
},
|
|
23
|
+
repository_git_missing: {
|
|
24
|
+
canRetry: false,
|
|
25
|
+
label: "Git is unavailable in the workspace"
|
|
26
|
+
},
|
|
27
|
+
repository_egress_blocked: {
|
|
28
|
+
canRetry: false,
|
|
29
|
+
label: "Repository network access is blocked"
|
|
30
|
+
},
|
|
31
|
+
repository_clone_failed: {
|
|
32
|
+
canRetry: true,
|
|
33
|
+
label: "Repository clone failed"
|
|
34
|
+
},
|
|
35
|
+
repository_pull_failed: {
|
|
36
|
+
canRetry: true,
|
|
37
|
+
label: "Repository update failed"
|
|
38
|
+
},
|
|
39
|
+
repository_push_failed: {
|
|
40
|
+
canRetry: true,
|
|
41
|
+
label: "Repository push failed"
|
|
42
|
+
},
|
|
43
|
+
repository_commit_failed: {
|
|
44
|
+
canRetry: true,
|
|
45
|
+
label: "Repository commit failed"
|
|
46
|
+
},
|
|
47
|
+
repository_cli_missing: {
|
|
48
|
+
canRetry: false,
|
|
49
|
+
label: "GitHub CLI is unavailable in the workspace"
|
|
50
|
+
},
|
|
51
|
+
repository_pr_failed: {
|
|
52
|
+
canRetry: true,
|
|
53
|
+
label: "Pull request creation failed"
|
|
54
|
+
},
|
|
55
|
+
unknown: {
|
|
56
|
+
canRetry: true,
|
|
57
|
+
label: "Factory automation failed"
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var FactoryDispatchError = class extends Error {
|
|
61
|
+
code;
|
|
62
|
+
constructor(code, message, options) {
|
|
63
|
+
super(message, options);
|
|
64
|
+
this.code = code;
|
|
65
|
+
this.name = "FactoryDispatchError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function factoryDispatchFailureCode(error) {
|
|
69
|
+
return error instanceof FactoryDispatchError ? error.code : "unknown";
|
|
70
|
+
}
|
|
71
|
+
function factoryDispatchFailureMetadata(code) {
|
|
72
|
+
return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { FactoryDispatchError, factoryDispatchFailureCode, factoryDispatchFailureMetadata };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=dispatch-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type { FactoryDispatchFailureCode } from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n} satisfies Record<FactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: FactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAOA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;AAChE;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
@@ -2,6 +2,7 @@ import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
|
2
2
|
import type { AgentController } from '@mastra/core/agent-controller';
|
|
3
3
|
import type { FactoryDeferredDecisionRecord, WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
4
4
|
import type { FactoryTransitionService } from './transition-service.js';
|
|
5
|
+
import type { FactoryCommitDecision } from './types.js';
|
|
5
6
|
type FactoryController = Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;
|
|
6
7
|
export interface FactoryBindingPreparationInput {
|
|
7
8
|
record: FactoryDeferredDecisionRecord;
|
|
@@ -24,6 +25,12 @@ export interface FactoryDecisionDispatcherOptions {
|
|
|
24
25
|
orgId: string;
|
|
25
26
|
userId: string;
|
|
26
27
|
}) => Promise<void>;
|
|
28
|
+
resolveLinkedWorkItemParentId?: (input: {
|
|
29
|
+
orgId: string;
|
|
30
|
+
decision: Extract<FactoryCommitDecision, {
|
|
31
|
+
type: 'upsertLinkedWorkItem';
|
|
32
|
+
}>;
|
|
33
|
+
}) => Promise<string | null>;
|
|
27
34
|
maxInFlight?: number;
|
|
28
35
|
/** How often the stale-binding sweep runs. Defaults to 10 minutes. */
|
|
29
36
|
staleBindingSweepIntervalMs?: number;
|
|
@@ -31,6 +38,8 @@ export interface FactoryDecisionDispatcherOptions {
|
|
|
31
38
|
staleBindingTtlMs?: number;
|
|
32
39
|
/** How often the bound-thread reconcile walk runs. Defaults to 30 seconds. */
|
|
33
40
|
reconcileIntervalMs?: number;
|
|
41
|
+
/** How long to wait for a run's terminal event before failing for retry. Defaults to 10 minutes. */
|
|
42
|
+
skillCompletionObservationTimeoutMs?: number;
|
|
34
43
|
}
|
|
35
44
|
export declare class FactoryDecisionDispatcher {
|
|
36
45
|
#private;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAyC,MAAM,+BAA+B,CAAC;AAK5G,OAAO,KAAK,EACV,6BAA6B,EAG7B,WAAW,EACX,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,qBAAqB,EAA4C,MAAM,YAAY,CAAC;AA+DlG,KAAK,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAGxF,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,6BAA6B,CAAC;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,iBAAiB,CAAC;IAC9B,iBAAiB,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChE,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,gBAAgB,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5F,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,8BAA8B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,6BAA6B,CAAC,EAAE,CAAC,KAAK,EAAE;QACtC,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC,qBAAqB,EAAE;YAAE,IAAI,EAAE,sBAAsB,CAAA;SAAE,CAAC,CAAC;KAC5E,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sFAAsF;IACtF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oGAAoG;IACpG,mCAAmC,CAAC,EAAE,MAAM,CAAC;CAC9C;AA+FD,qBAAa,yBAAyB;;gBAsBxB,OAAO,EAAE,gCAAgC;IAwBrD,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrB,OAAO,CAAC,GAAG,OAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAmuB/C;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;CAU7B,CAAC"}
|
package/dist/rules/dispatcher.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FACTORY_RULE_STAGES } from "./types.js";
|
|
2
2
|
import { validateFactoryRuleDecision } from "./validation.js";
|
|
3
|
-
import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
|
|
4
3
|
import { FACTORY_RULE_MATERIALIZATION_KEY } from "../storage/domains/work-items/base.js";
|
|
4
|
+
import { FactoryDispatchError, factoryDispatchFailureCode } from "./dispatch-errors.js";
|
|
5
|
+
import { resolvePromptInvocation, resolveSkillInvocation } from "../skills/service.js";
|
|
5
6
|
import { randomUUID } from "crypto";
|
|
6
7
|
import { RequestContext } from "@mastra/core/request-context";
|
|
7
8
|
//#region src/rules/dispatcher.ts
|
|
@@ -16,9 +17,9 @@ const MAX_IN_FLIGHT = 25;
|
|
|
16
17
|
const STALE_BINDING_SWEEP_INTERVAL_MS = 10 * 6e4;
|
|
17
18
|
const STALE_BINDING_TTL_MS = 1440 * 6e4;
|
|
18
19
|
const RECONCILE_INTERVAL_MS = 3e4;
|
|
19
|
-
function waitForAgentEndOrTimeout(agentEnd) {
|
|
20
|
+
function waitForAgentEndOrTimeout(agentEnd, timeoutMs) {
|
|
20
21
|
return new Promise((resolve) => {
|
|
21
|
-
const timeout = setTimeout(() => resolve(false),
|
|
22
|
+
const timeout = setTimeout(() => resolve(false), timeoutMs);
|
|
22
23
|
timeout.unref?.();
|
|
23
24
|
agentEnd.then(() => {
|
|
24
25
|
clearTimeout(timeout);
|
|
@@ -65,19 +66,26 @@ function leaseIdentity(record, ownerId) {
|
|
|
65
66
|
ownerId
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
|
-
async function awaitNotification(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
async function awaitNotification(send, requireDelivery = false) {
|
|
70
|
+
try {
|
|
71
|
+
const notification = await send();
|
|
72
|
+
const [, accepted] = await Promise.all([notification.persisted, notification.accepted]);
|
|
73
|
+
if (!accepted) {
|
|
74
|
+
if (requireDelivery) throw new FactoryDispatchError("notification_delivery_failed", "Factory notification was persisted without agent delivery.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!requireDelivery) return accepted;
|
|
78
|
+
if (accepted.action === "wake") {
|
|
79
|
+
if (!accepted.output) throw new FactoryDispatchError("notification_delivery_failed", "Factory notification wake had no output.");
|
|
80
|
+
await accepted.output.consumeStream();
|
|
81
|
+
return accepted;
|
|
82
|
+
}
|
|
83
|
+
if (accepted.action !== "deliver") throw new FactoryDispatchError("notification_delivery_failed", `Factory notification did not reach the agent (${String(accepted.action)}).`);
|
|
84
|
+
return accepted;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof FactoryDispatchError) throw error;
|
|
87
|
+
throw new FactoryDispatchError("notification_delivery_failed", `Factory notification delivery failed: ${sanitizeDispatchError(error)}`, { cause: error });
|
|
79
88
|
}
|
|
80
|
-
if (accepted.action !== "deliver") throw new Error(`Factory notification did not reach the agent (${String(accepted.action)}).`);
|
|
81
89
|
}
|
|
82
90
|
var FactoryDecisionDispatcher = class {
|
|
83
91
|
#controller;
|
|
@@ -88,11 +96,13 @@ var FactoryDecisionDispatcher = class {
|
|
|
88
96
|
#reconcileToolResults;
|
|
89
97
|
#prepareBinding;
|
|
90
98
|
#primeCredentials;
|
|
99
|
+
#resolveLinkedWorkItemParentId;
|
|
91
100
|
#maxInFlight;
|
|
92
101
|
#staleBindingSweepIntervalMs;
|
|
93
102
|
#staleBindingTtlMs;
|
|
94
103
|
#lastStaleBindingSweepAt;
|
|
95
104
|
#reconcileIntervalMs;
|
|
105
|
+
#skillCompletionObservationTimeoutMs;
|
|
96
106
|
#lastReconcileAt;
|
|
97
107
|
#reconcileInFlight;
|
|
98
108
|
#timer;
|
|
@@ -107,11 +117,13 @@ var FactoryDecisionDispatcher = class {
|
|
|
107
117
|
this.#reconcileToolResults = options.reconcileToolResults;
|
|
108
118
|
this.#prepareBinding = options.prepareBinding;
|
|
109
119
|
this.#primeCredentials = options.primeCredentials;
|
|
120
|
+
this.#resolveLinkedWorkItemParentId = options.resolveLinkedWorkItemParentId;
|
|
110
121
|
const maxInFlight = options.maxInFlight ?? MAX_IN_FLIGHT;
|
|
111
122
|
this.#maxInFlight = Number.isFinite(maxInFlight) && maxInFlight > 0 ? Math.floor(maxInFlight) : MAX_IN_FLIGHT;
|
|
112
123
|
this.#staleBindingSweepIntervalMs = positiveMs(options.staleBindingSweepIntervalMs, STALE_BINDING_SWEEP_INTERVAL_MS);
|
|
113
124
|
this.#staleBindingTtlMs = positiveMs(options.staleBindingTtlMs, STALE_BINDING_TTL_MS);
|
|
114
125
|
this.#reconcileIntervalMs = positiveMs(options.reconcileIntervalMs, RECONCILE_INTERVAL_MS);
|
|
126
|
+
this.#skillCompletionObservationTimeoutMs = positiveMs(options.skillCompletionObservationTimeoutMs, SKILL_COMPLETION_OBSERVATION_TIMEOUT_MS);
|
|
115
127
|
}
|
|
116
128
|
start() {
|
|
117
129
|
if (this.#timer) return;
|
|
@@ -213,6 +225,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
213
225
|
}
|
|
214
226
|
}
|
|
215
227
|
async #dispatchDecision(record, now) {
|
|
228
|
+
let executionCompleted = false;
|
|
216
229
|
try {
|
|
217
230
|
const decision = validateFactoryRuleDecision(record.decision, record.causalChain.length);
|
|
218
231
|
if (decision.type === "reject") throw new Error("Deferred Factory decisions cannot reject.");
|
|
@@ -222,6 +235,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
222
235
|
}
|
|
223
236
|
await this.#supersedeProposals(record, decision);
|
|
224
237
|
await this.#withLease(async (leaseExpiresAt) => this.#storage.renewDeferredDecisionLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt), async () => this.#executeDecision(record, decision));
|
|
238
|
+
executionCompleted = true;
|
|
225
239
|
if (!await this.#storage.completeDeferredDecision(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory decision lease was lost before completion.");
|
|
226
240
|
} catch (error) {
|
|
227
241
|
const terminal = record.attempts >= MAX_ATTEMPTS;
|
|
@@ -230,7 +244,9 @@ var FactoryDecisionDispatcher = class {
|
|
|
230
244
|
now: /* @__PURE__ */ new Date(),
|
|
231
245
|
availableAt: retryAt(now, record.attempts),
|
|
232
246
|
lastError: sanitizeDispatchError(error),
|
|
233
|
-
|
|
247
|
+
failureCode: factoryDispatchFailureCode(error),
|
|
248
|
+
terminal,
|
|
249
|
+
advanceDeliveryGeneration: !executionCompleted
|
|
234
250
|
});
|
|
235
251
|
}
|
|
236
252
|
}
|
|
@@ -244,12 +260,12 @@ var FactoryDecisionDispatcher = class {
|
|
|
244
260
|
async #supersedeProposals(record, decision) {
|
|
245
261
|
if (decision.type !== "invokeSkill" || !record.workItemId) return;
|
|
246
262
|
try {
|
|
247
|
-
await this.#storage.
|
|
263
|
+
await this.#storage.supersedeDecisionsForWorkItem({
|
|
248
264
|
orgId: record.orgId,
|
|
249
265
|
factoryProjectId: record.factoryProjectId,
|
|
250
266
|
workItemId: record.workItemId,
|
|
251
267
|
role: decision.role,
|
|
252
|
-
|
|
268
|
+
supersededAt: /* @__PURE__ */ new Date()
|
|
253
269
|
});
|
|
254
270
|
} catch (error) {
|
|
255
271
|
console.error("Factory proposal supersede failed", sanitizeDispatchError(error));
|
|
@@ -296,8 +312,9 @@ var FactoryDecisionDispatcher = class {
|
|
|
296
312
|
...decision.reenter ? { reenter: true } : {}
|
|
297
313
|
});
|
|
298
314
|
if (result.status === "rejected") throw new Error(`${result.code}: ${result.reason}`);
|
|
299
|
-
|
|
300
|
-
|
|
315
|
+
const transitionMessage = decision.message;
|
|
316
|
+
if (!transitionMessage) return;
|
|
317
|
+
const binding = await this.#findBinding(record, transitionMessage.role);
|
|
301
318
|
if (!binding) return;
|
|
302
319
|
const startedBy = item.sessions[binding.role]?.startedBy;
|
|
303
320
|
if (!startedBy) return;
|
|
@@ -310,12 +327,14 @@ var FactoryDecisionDispatcher = class {
|
|
|
310
327
|
workosId: startedBy,
|
|
311
328
|
organizationId: record.orgId
|
|
312
329
|
});
|
|
313
|
-
|
|
330
|
+
const session = await this.#findSession(binding);
|
|
331
|
+
if (!session) return;
|
|
332
|
+
await awaitNotification(() => session.sendNotificationSignal({
|
|
314
333
|
source: "factory",
|
|
315
334
|
kind: "rule-message",
|
|
316
|
-
summary:
|
|
335
|
+
summary: transitionMessage.text,
|
|
317
336
|
priority: "high",
|
|
318
|
-
payload: { message:
|
|
337
|
+
payload: { message: transitionMessage.text },
|
|
319
338
|
sourceId: record.id,
|
|
320
339
|
dedupeKey: record.idempotencyKey
|
|
321
340
|
}, {
|
|
@@ -354,14 +373,16 @@ var FactoryDecisionDispatcher = class {
|
|
|
354
373
|
});
|
|
355
374
|
const session = resolved.session;
|
|
356
375
|
await this.#switchThread(session, binding);
|
|
357
|
-
|
|
376
|
+
const deliveryId = record.deliveryGeneration === 0 ? record.id : `${record.id}:retry:${record.deliveryGeneration}`;
|
|
377
|
+
if ((await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) return;
|
|
358
378
|
if (decision.cancelInFlight) session.abort();
|
|
359
|
-
|
|
379
|
+
const precedingMessage = decision.precedingMessage;
|
|
380
|
+
if (precedingMessage) await awaitNotification(() => session.sendNotificationSignal({
|
|
360
381
|
source: "factory",
|
|
361
382
|
kind: "stage-transition",
|
|
362
|
-
summary:
|
|
383
|
+
summary: precedingMessage,
|
|
363
384
|
priority: "medium",
|
|
364
|
-
payload: { message:
|
|
385
|
+
payload: { message: precedingMessage },
|
|
365
386
|
sourceId: `${record.id}:stage-transition`,
|
|
366
387
|
dedupeKey: `${record.idempotencyKey}:stage-transition`
|
|
367
388
|
}, {
|
|
@@ -387,7 +408,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
387
408
|
});
|
|
388
409
|
const sendKickoff = async () => {
|
|
389
410
|
const settled = await session.sendSignal({
|
|
390
|
-
id:
|
|
411
|
+
id: deliveryId,
|
|
391
412
|
type: "user",
|
|
392
413
|
tagName: "user",
|
|
393
414
|
contents: resolved.message
|
|
@@ -401,17 +422,14 @@ var FactoryDecisionDispatcher = class {
|
|
|
401
422
|
try {
|
|
402
423
|
let settled = await sendKickoff();
|
|
403
424
|
if (settled.action === "deliver") {
|
|
404
|
-
if (!(await session.thread.listActiveMessages()).some((message) => message.id ===
|
|
405
|
-
if (!await waitForAgentEndOrTimeout(agentEnd)) throw new Error("Factory skill invocation is waiting on a run that has not ended.");
|
|
425
|
+
if (!(await session.thread.listActiveMessages()).some((message) => message.id === deliveryId)) {
|
|
426
|
+
if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory skill invocation is waiting on a run that has not ended.");
|
|
406
427
|
armAgentEnd();
|
|
407
428
|
settled = await sendKickoff();
|
|
408
429
|
if (settled.action !== "wake") throw new Error("Factory skill invocation was queued onto an ending run and never reached the agent.");
|
|
409
430
|
}
|
|
410
431
|
}
|
|
411
|
-
if (!await waitForAgentEndOrTimeout(agentEnd))
|
|
412
|
-
decisionId: record.id,
|
|
413
|
-
runId: settled.action === "wake" ? settled.runId : void 0
|
|
414
|
-
});
|
|
432
|
+
if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory skill run terminal event was not observed before timeout.");
|
|
415
433
|
else if (endReason === "error") throw new Error("Factory skill run ended in error.");
|
|
416
434
|
else if (endReason === "aborted") throw new Error("Factory skill run was aborted before it finished.");
|
|
417
435
|
} finally {
|
|
@@ -435,7 +453,8 @@ var FactoryDecisionDispatcher = class {
|
|
|
435
453
|
workosId: startedBy,
|
|
436
454
|
organizationId: record.orgId
|
|
437
455
|
});
|
|
438
|
-
|
|
456
|
+
const session = await this.#requireSession(binding);
|
|
457
|
+
await awaitNotification(() => session.sendNotificationSignal({
|
|
439
458
|
source: "factory",
|
|
440
459
|
kind: "rule-message",
|
|
441
460
|
summary: decision.message,
|
|
@@ -452,7 +471,8 @@ var FactoryDecisionDispatcher = class {
|
|
|
452
471
|
}
|
|
453
472
|
case "notify": {
|
|
454
473
|
const binding = await this.#requireBinding(record);
|
|
455
|
-
|
|
474
|
+
const session = await this.#requireSession(binding);
|
|
475
|
+
await awaitNotification(() => session.sendNotificationSignal({
|
|
456
476
|
source: "factory",
|
|
457
477
|
kind: "rule-notification",
|
|
458
478
|
summary: decision.title,
|
|
@@ -467,13 +487,17 @@ var FactoryDecisionDispatcher = class {
|
|
|
467
487
|
}
|
|
468
488
|
}
|
|
469
489
|
async #upsertLinkedItem(record, decision, causalChain) {
|
|
470
|
-
const
|
|
490
|
+
const parentWorkItemId = record.workItemId ?? await this.#resolveLinkedWorkItemParentId?.({
|
|
491
|
+
orgId: record.orgId,
|
|
492
|
+
decision
|
|
493
|
+
}) ?? null;
|
|
494
|
+
let result = await this.#storage.upsert({
|
|
471
495
|
orgId: record.orgId,
|
|
472
496
|
userId: "factory-rule-dispatcher",
|
|
473
497
|
factoryProjectId: record.factoryProjectId,
|
|
474
498
|
input: {
|
|
475
499
|
externalSource: externalSourceForDecision(decision),
|
|
476
|
-
parentWorkItemId
|
|
500
|
+
parentWorkItemId,
|
|
477
501
|
title: decision.title,
|
|
478
502
|
stages: ["intake"],
|
|
479
503
|
sessions: {},
|
|
@@ -484,6 +508,18 @@ var FactoryDecisionDispatcher = class {
|
|
|
484
508
|
},
|
|
485
509
|
reuseMode: "preserve"
|
|
486
510
|
});
|
|
511
|
+
if (!result.item.parentWorkItemId && parentWorkItemId) {
|
|
512
|
+
const item = await this.#storage.setParentWorkItemIfMissing({
|
|
513
|
+
orgId: record.orgId,
|
|
514
|
+
id: result.item.id,
|
|
515
|
+
userId: "factory-rule-dispatcher",
|
|
516
|
+
parentWorkItemId
|
|
517
|
+
});
|
|
518
|
+
if (item) result = {
|
|
519
|
+
...result,
|
|
520
|
+
item
|
|
521
|
+
};
|
|
522
|
+
}
|
|
487
523
|
const materializedByDecision = result.item.metadata?.[FACTORY_RULE_MATERIALIZATION_KEY] === record.idempotencyKey;
|
|
488
524
|
if (!materializedByDecision && (decision.stage === "intake" || !result.item.stages.includes("intake"))) return;
|
|
489
525
|
const board = decision.board;
|
|
@@ -554,7 +590,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
554
590
|
}
|
|
555
591
|
async #requireBinding(record, role) {
|
|
556
592
|
const binding = await this.#findBinding(record, role);
|
|
557
|
-
if (!binding) throw new
|
|
593
|
+
if (!binding) throw new FactoryDispatchError("session_unavailable", role ? `No active Factory binding for role ${role}.` : "No active Factory binding.");
|
|
558
594
|
return binding;
|
|
559
595
|
}
|
|
560
596
|
async #requireOrPrepareBinding(record, role) {
|
|
@@ -562,7 +598,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
562
598
|
if (binding) {
|
|
563
599
|
if (await this.#controller.getSessionByResource(binding.resourceId)) return binding;
|
|
564
600
|
}
|
|
565
|
-
if (!this.#prepareBinding) throw new
|
|
601
|
+
if (!this.#prepareBinding) throw new FactoryDispatchError("session_unavailable", binding ? "Bound Factory session not found." : `No active Factory binding for role ${role}.`);
|
|
566
602
|
const item = await this.#requireItem(record);
|
|
567
603
|
await this.#prepareBinding({
|
|
568
604
|
record,
|
|
@@ -571,12 +607,17 @@ var FactoryDecisionDispatcher = class {
|
|
|
571
607
|
});
|
|
572
608
|
return this.#requireBinding(record, role);
|
|
573
609
|
}
|
|
574
|
-
async #
|
|
610
|
+
async #findSession(binding) {
|
|
575
611
|
const session = await this.#controller.getSessionByResource(binding.resourceId);
|
|
576
|
-
if (!session)
|
|
612
|
+
if (!session) return void 0;
|
|
577
613
|
await this.#switchThread(session, binding);
|
|
578
614
|
return session;
|
|
579
615
|
}
|
|
616
|
+
async #requireSession(binding) {
|
|
617
|
+
const session = await this.#findSession(binding);
|
|
618
|
+
if (!session) throw new FactoryDispatchError("session_unavailable", "Bound Factory session not found.");
|
|
619
|
+
return session;
|
|
620
|
+
}
|
|
580
621
|
async #switchThread(session, binding) {
|
|
581
622
|
await session.thread.switch({ threadId: binding.threadId });
|
|
582
623
|
}
|
|
@@ -607,7 +648,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
607
648
|
await this.#withLease(async (leaseExpiresAt) => this.#storage.renewPendingStartLease(leaseIdentity(record, this.#ownerId), leaseExpiresAt), async () => {
|
|
608
649
|
if (record.message === null) return;
|
|
609
650
|
const binding = (await this.#storage.listRunBindings(record.orgId, record.factoryProjectId)).find((candidate) => candidate.id === record.bindingId && candidate.status === "active");
|
|
610
|
-
if (!binding) throw new
|
|
651
|
+
if (!binding) throw new FactoryDispatchError("session_unavailable", "Prepared Factory binding is unavailable or revoked.");
|
|
611
652
|
const startedBy = (await this.#storage.get({
|
|
612
653
|
orgId: record.orgId,
|
|
613
654
|
id: binding.workItemId
|
|
@@ -622,19 +663,50 @@ var FactoryDecisionDispatcher = class {
|
|
|
622
663
|
workosId: startedBy,
|
|
623
664
|
organizationId: record.orgId
|
|
624
665
|
});
|
|
625
|
-
|
|
666
|
+
const session = await this.#requireSession(binding);
|
|
667
|
+
let resolveAgentEnd;
|
|
668
|
+
let agentEnd;
|
|
669
|
+
let endReason;
|
|
670
|
+
const armAgentEnd = () => {
|
|
671
|
+
endReason = void 0;
|
|
672
|
+
agentEnd = new Promise((resolve) => {
|
|
673
|
+
resolveAgentEnd = resolve;
|
|
674
|
+
});
|
|
675
|
+
};
|
|
676
|
+
armAgentEnd();
|
|
677
|
+
const unsubscribe = session.subscribe((event) => {
|
|
678
|
+
if (event.type === "agent_end") {
|
|
679
|
+
endReason = event.reason;
|
|
680
|
+
resolveAgentEnd();
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
const sendKickoff = (dedupeKey) => awaitNotification(() => session.sendNotificationSignal({
|
|
626
684
|
source: "factory",
|
|
627
685
|
kind: "run-kickoff",
|
|
628
686
|
summary: record.message,
|
|
629
687
|
priority: "high",
|
|
630
688
|
payload: { message: record.message },
|
|
631
689
|
sourceId: record.id,
|
|
632
|
-
dedupeKey
|
|
690
|
+
dedupeKey
|
|
633
691
|
}, {
|
|
634
692
|
ifActive: { behavior: "deliver" },
|
|
635
693
|
ifIdle: { behavior: "wake" },
|
|
636
694
|
requestContext
|
|
637
695
|
}), true);
|
|
696
|
+
try {
|
|
697
|
+
let settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}`);
|
|
698
|
+
if (settled?.action === "deliver") {
|
|
699
|
+
if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory kickoff is waiting on a run that has not ended.");
|
|
700
|
+
armAgentEnd();
|
|
701
|
+
settled = await sendKickoff(`factory-kickoff:${record.kickoffKey}:retry:${record.attempts}`);
|
|
702
|
+
if (settled?.action !== "wake") throw new Error("Factory kickoff was queued onto an ending run and never reached the agent.");
|
|
703
|
+
}
|
|
704
|
+
if (!await waitForAgentEndOrTimeout(agentEnd, this.#skillCompletionObservationTimeoutMs)) throw new Error("Factory kickoff run terminal event was not observed before timeout.");
|
|
705
|
+
else if (endReason === "error") throw new Error("Factory kickoff run ended in error.");
|
|
706
|
+
else if (endReason === "aborted") throw new Error("Factory kickoff run was aborted before it finished.");
|
|
707
|
+
} finally {
|
|
708
|
+
unsubscribe();
|
|
709
|
+
}
|
|
638
710
|
});
|
|
639
711
|
if (!await this.#storage.completePendingStart(leaseIdentity(record, this.#ownerId), /* @__PURE__ */ new Date())) throw new Error("Factory kickoff lease was lost before completion.");
|
|
640
712
|
} catch (error) {
|
|
@@ -643,6 +715,7 @@ var FactoryDecisionDispatcher = class {
|
|
|
643
715
|
now: /* @__PURE__ */ new Date(),
|
|
644
716
|
availableAt: retryAt(now, record.attempts),
|
|
645
717
|
lastError: sanitizeDispatchError(error),
|
|
718
|
+
failureCode: factoryDispatchFailureCode(error),
|
|
646
719
|
terminal: record.attempts >= MAX_ATTEMPTS
|
|
647
720
|
});
|
|
648
721
|
}
|