@mastra/factory 0.24.0-alpha.2 → 0.24.0-alpha.4

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.
Files changed (113) hide show
  1. package/README.md +23 -0
  2. package/dist/auth.d.ts +6 -0
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +31 -9
  5. package/dist/auth.js.map +1 -1
  6. package/dist/factory.d.ts +8 -0
  7. package/dist/factory.d.ts.map +1 -1
  8. package/dist/factory.js +50 -31
  9. package/dist/factory.js.map +1 -1
  10. package/dist/integrations/github/default-rules.d.ts.map +1 -1
  11. package/dist/integrations/github/default-rules.js +1 -16
  12. package/dist/integrations/github/default-rules.js.map +1 -1
  13. package/dist/integrations/github/sandbox-release.d.ts +15 -3
  14. package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
  15. package/dist/integrations/github/sandbox-release.js +20 -3
  16. package/dist/integrations/github/sandbox-release.js.map +1 -1
  17. package/dist/integrations/github/webhook.d.ts +4 -2
  18. package/dist/integrations/github/webhook.d.ts.map +1 -1
  19. package/dist/integrations/github/webhook.js +59 -36
  20. package/dist/integrations/github/webhook.js.map +1 -1
  21. package/dist/integrations/gitlab/api.d.ts +1 -1
  22. package/dist/integrations/gitlab/api.d.ts.map +1 -1
  23. package/dist/integrations/gitlab/api.js.map +1 -1
  24. package/dist/integrations/incidentio/intake.d.ts +1 -1
  25. package/dist/integrations/incidentio/intake.d.ts.map +1 -1
  26. package/dist/integrations/incidentio/intake.js.map +1 -1
  27. package/dist/integrations/platform/connect/routes.d.ts +1 -1
  28. package/dist/integrations/platform/connect/routes.d.ts.map +1 -1
  29. package/dist/integrations/platform/connect/routes.js.map +1 -1
  30. package/dist/integrations/platform/incidentio/integration.d.ts +1 -1
  31. package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
  32. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  33. package/dist/integrations/platform/jira/integration.d.ts +1 -1
  34. package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
  35. package/dist/integrations/platform/jira/integration.js.map +1 -1
  36. package/dist/integrations/platform/linear/event-worker.d.ts +1 -1
  37. package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
  38. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  39. package/dist/mcp/disconnect.d.ts +10 -0
  40. package/dist/mcp/disconnect.d.ts.map +1 -0
  41. package/dist/mcp/disconnect.js +44 -0
  42. package/dist/mcp/disconnect.js.map +1 -0
  43. package/dist/mcp/routes.d.ts +46 -0
  44. package/dist/mcp/routes.d.ts.map +1 -0
  45. package/dist/mcp/routes.js +243 -0
  46. package/dist/mcp/routes.js.map +1 -0
  47. package/dist/review-verdict.d.ts +11 -0
  48. package/dist/review-verdict.d.ts.map +1 -1
  49. package/dist/review-verdict.js +16 -1
  50. package/dist/review-verdict.js.map +1 -1
  51. package/dist/routes/source-control-sessions.d.ts +7 -0
  52. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  53. package/dist/routes/source-control-sessions.js +121 -3
  54. package/dist/routes/source-control-sessions.js.map +1 -1
  55. package/dist/routes/surface.d.ts +2 -2
  56. package/dist/routes/surface.d.ts.map +1 -1
  57. package/dist/routes/surface.js +5 -3
  58. package/dist/routes/surface.js.map +1 -1
  59. package/dist/routes/work-items.js +6 -6
  60. package/dist/rules/dispatcher.d.ts.map +1 -1
  61. package/dist/rules/dispatcher.js +12 -3
  62. package/dist/rules/dispatcher.js.map +1 -1
  63. package/dist/rules/types.d.ts +0 -4
  64. package/dist/rules/types.d.ts.map +1 -1
  65. package/dist/rules/types.js +1 -4
  66. package/dist/rules/types.js.map +1 -1
  67. package/dist/sandbox/session-retirement.d.ts +27 -3
  68. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  69. package/dist/sandbox/session-retirement.js +158 -20
  70. package/dist/sandbox/session-retirement.js.map +1 -1
  71. package/dist/session/credential-policy.d.ts +1 -1
  72. package/dist/session/credential-policy.d.ts.map +1 -1
  73. package/dist/session/credential-policy.js.map +1 -1
  74. package/dist/session/factory-session.d.ts +1 -1
  75. package/dist/session/factory-session.d.ts.map +1 -1
  76. package/dist/session/factory-session.js.map +1 -1
  77. package/dist/session/work-item-repository.d.ts +1 -1
  78. package/dist/session/work-item-repository.d.ts.map +1 -1
  79. package/dist/session/work-item-repository.js.map +1 -1
  80. package/dist/storage/domains/audit/domain.js +1 -1
  81. package/dist/storage/domains/filesystem/base.d.ts +4 -0
  82. package/dist/storage/domains/filesystem/base.d.ts.map +1 -1
  83. package/dist/storage/domains/filesystem/base.js +5 -0
  84. package/dist/storage/domains/filesystem/base.js.map +1 -1
  85. package/dist/storage/domains/work-items/base.d.ts +7 -5
  86. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  87. package/dist/storage/domains/work-items/base.js +5 -5
  88. package/dist/storage/domains/work-items/base.js.map +1 -1
  89. package/dist/timing.d.ts +23 -0
  90. package/dist/timing.d.ts.map +1 -1
  91. package/dist/timing.js +85 -6
  92. package/dist/timing.js.map +1 -1
  93. package/dist/tools/annotations.d.ts +7 -0
  94. package/dist/tools/annotations.d.ts.map +1 -0
  95. package/dist/tools/annotations.js +102 -0
  96. package/dist/tools/annotations.js.map +1 -0
  97. package/dist/tools/reports.d.ts +11 -0
  98. package/dist/tools/reports.d.ts.map +1 -1
  99. package/dist/tools/reports.js +20 -1
  100. package/dist/tools/reports.js.map +1 -1
  101. package/dist/tools/run-watch.d.ts +1 -1
  102. package/dist/tools/run-watch.d.ts.map +1 -1
  103. package/dist/tools/run-watch.js.map +1 -1
  104. package/dist/tools/session-tools.d.ts +1 -1
  105. package/dist/tools/session-tools.d.ts.map +1 -1
  106. package/dist/tools/session-tools.js +3 -3
  107. package/dist/tools/session-tools.js.map +1 -1
  108. package/dist/tools/work-item-tools.js +2 -2
  109. package/dist/workspace.d.ts +1 -1
  110. package/dist/workspace.d.ts.map +1 -1
  111. package/dist/workspace.js +10 -4
  112. package/dist/workspace.js.map +1 -1
  113. package/package.json +14 -14
package/dist/timing.d.ts CHANGED
@@ -5,6 +5,29 @@
5
5
  * grepped (`[factory:timing]`) and diffed across runs to localize slow boot
6
6
  * phases and slow per-request dependencies.
7
7
  */
8
+ import type { Context } from 'hono';
9
+ /**
10
+ * Verbose per-operation timing (HTTP requests, storage calls, sandbox
11
+ * commands). Off by default; enable per deployment with `FACTORY_TIMING=1`.
12
+ */
13
+ export declare const factoryTimingEnabled: boolean;
14
+ /** Run `fn`, logging its duration only when `FACTORY_TIMING` is enabled. */
15
+ export declare function timed<T>(phase: string, fn: () => Promise<T>): Promise<T>;
16
+ /** Hono middleware logging method, path, status, and duration of each request. */
17
+ export declare function createRequestTimingMiddleware(): (c: Context, next: () => Promise<void>) => Promise<void>;
18
+ /**
19
+ * Patch `methods` on `target` in place so each call logs `<label>.<method>`
20
+ * duration. In-place (not a Proxy) so object identity is preserved.
21
+ */
22
+ export declare function instrumentMethods<T extends object>(target: T, label: string, methods: readonly string[]): T;
23
+ /**
24
+ * Wrap every async method on `target` so each call logs `<label>.<method>`
25
+ * duration. Domains passed to a `registerDomain` method are instrumented in
26
+ * place as `<label>.<domain name>`, so the handle it returns and later
27
+ * `getDomain` lookups are the same timed object. Returns `target` unchanged
28
+ * when timing is disabled.
29
+ */
30
+ export declare function instrumentAsyncMethods<T extends object>(target: T, label: string): T;
8
31
  /** Run `fn`, always logging its duration under `phase`. Boot-phase use. */
9
32
  export declare function timedPhase<T>(phase: string, fn: () => Promise<T>): Promise<T>;
10
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,wBAAsB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAUjH"}
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,SAA2D,CAAC;AAM7F,4EAA4E;AAC5E,wBAAsB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQ9E;AAED,kFAAkF;AAClF,wBAAgB,6BAA6B,KAC7B,GAAG,OAAO,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAQpE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,CAgB3G;AAmBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAyBpF;AAED,2EAA2E;AAC3E,wBAAsB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOnF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAUjH"}
package/dist/timing.js CHANGED
@@ -1,11 +1,90 @@
1
1
  //#region src/timing.ts
2
2
  /**
3
- * Lightweight phase timing for factory boot and hot request paths.
4
- *
5
- * Deliberately not a metrics framework: structured stderr lines that can be
6
- * grepped (`[factory:timing]`) and diffed across runs to localize slow boot
7
- * phases and slow per-request dependencies.
3
+ * Verbose per-operation timing (HTTP requests, storage calls, sandbox
4
+ * commands). Off by default; enable per deployment with `FACTORY_TIMING=1`.
8
5
  */
6
+ const factoryTimingEnabled = /^(1|true|yes)$/i.test(process.env.FACTORY_TIMING ?? "");
7
+ function logTiming(line) {
8
+ process.stderr.write(`[factory:timing] ${line}\n`);
9
+ }
10
+ /** Run `fn`, logging its duration only when `FACTORY_TIMING` is enabled. */
11
+ async function timed(phase, fn) {
12
+ if (!factoryTimingEnabled) return fn();
13
+ const start = performance.now();
14
+ try {
15
+ return await fn();
16
+ } finally {
17
+ logTiming(`${phase} ${Math.round(performance.now() - start)}ms`);
18
+ }
19
+ }
20
+ /** Hono middleware logging method, path, status, and duration of each request. */
21
+ function createRequestTimingMiddleware() {
22
+ return async (c, next) => {
23
+ const start = performance.now();
24
+ try {
25
+ await next();
26
+ } finally {
27
+ logTiming(`http ${c.req.method} ${c.req.path} ${c.res.status} ${Math.round(performance.now() - start)}ms`);
28
+ }
29
+ };
30
+ }
31
+ /**
32
+ * Patch `methods` on `target` in place so each call logs `<label>.<method>`
33
+ * duration. In-place (not a Proxy) so object identity is preserved.
34
+ */
35
+ function instrumentMethods(target, label, methods) {
36
+ if (!factoryTimingEnabled) return target;
37
+ const record = target;
38
+ for (const name of methods) {
39
+ const original = record[name];
40
+ if (typeof original !== "function") continue;
41
+ record[name] = (...args) => {
42
+ const start = performance.now();
43
+ const result = original.apply(target, args);
44
+ if (result instanceof Promise) return result.finally(() => logTiming(`${label}.${name} ${Math.round(performance.now() - start)}ms`));
45
+ return result;
46
+ };
47
+ }
48
+ return target;
49
+ }
50
+ /** Every method name on `target`'s prototype chain, excluding `Object.prototype`. */
51
+ function prototypeMethodNames(target) {
52
+ const names = /* @__PURE__ */ new Set();
53
+ for (let proto = Object.getPrototypeOf(target); proto && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) for (const name of Object.getOwnPropertyNames(proto)) {
54
+ if (name === "constructor") continue;
55
+ if (typeof Object.getOwnPropertyDescriptor(proto, name)?.value === "function") names.add(name);
56
+ }
57
+ return [...names];
58
+ }
59
+ /**
60
+ * Wrap every async method on `target` so each call logs `<label>.<method>`
61
+ * duration. Domains passed to a `registerDomain` method are instrumented in
62
+ * place as `<label>.<domain name>`, so the handle it returns and later
63
+ * `getDomain` lookups are the same timed object. Returns `target` unchanged
64
+ * when timing is disabled.
65
+ */
66
+ function instrumentAsyncMethods(target, label) {
67
+ if (!factoryTimingEnabled) return target;
68
+ const wrapped = /* @__PURE__ */ new Map();
69
+ return new Proxy(target, { get(obj, prop, receiver) {
70
+ const value = Reflect.get(obj, prop, receiver);
71
+ if (typeof value !== "function" || typeof prop === "symbol" || prop === "constructor") return value;
72
+ const cached = wrapped.get(prop);
73
+ if (cached) return cached;
74
+ const fn = (...args) => {
75
+ const start = performance.now();
76
+ const result = value.apply(obj, args);
77
+ if (prop === "registerDomain" && result && typeof result === "object" && !(result instanceof Promise)) {
78
+ const name = result.name;
79
+ return instrumentMethods(result, `${label}.${String(name)}`, prototypeMethodNames(result));
80
+ }
81
+ if (result instanceof Promise) return result.finally(() => logTiming(`${label}.${prop} ${Math.round(performance.now() - start)}ms`));
82
+ return result;
83
+ };
84
+ wrapped.set(prop, fn);
85
+ return fn;
86
+ } });
87
+ }
9
88
  /** Run `fn`, always logging its duration under `phase`. Boot-phase use. */
10
89
  async function timedPhase(phase, fn) {
11
90
  const start = performance.now();
@@ -29,6 +108,6 @@ async function timedAboveThreshold(phase, thresholdMs, fn) {
29
108
  }
30
109
  }
31
110
  //#endregion
32
- export { timedAboveThreshold, timedPhase };
111
+ export { createRequestTimingMiddleware, factoryTimingEnabled, instrumentAsyncMethods, instrumentMethods, timed, timedAboveThreshold, timedPhase };
33
112
 
34
113
  //# sourceMappingURL=timing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timing.js","names":[],"sources":["../src/timing.ts"],"sourcesContent":["/**\n * Lightweight phase timing for factory boot and hot request paths.\n *\n * Deliberately not a metrics framework: structured stderr lines that can be\n * grepped (`[factory:timing]`) and diffed across runs to localize slow boot\n * phases and slow per-request dependencies.\n */\n\n/** Run `fn`, always logging its duration under `phase`. Boot-phase use. */\nexport async function timedPhase<T>(phase: string, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n process.stderr.write(`[factory:timing] ${phase} ${Math.round(performance.now() - start)}ms\\n`);\n }\n}\n\n/**\n * Run `fn`, logging only when it exceeds `thresholdMs`. Request-path use,\n * where per-request logging would be noise but slow outliers must surface.\n */\nexport async function timedAboveThreshold<T>(phase: string, thresholdMs: number, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const elapsed = performance.now() - start;\n if (elapsed > thresholdMs) {\n process.stderr.write(`[factory:timing] slow ${phase} ${Math.round(elapsed)}ms\\n`);\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,WAAc,OAAe,IAAkC;CACnF,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,UAAU;EACR,QAAQ,OAAO,MAAM,oBAAoB,MAAM,GAAG,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,KAAK;CAC/F;AACF;;;;;AAMA,eAAsB,oBAAuB,OAAe,aAAqB,IAAkC;CACjH,MAAM,QAAQ,YAAY,IAAI;CAC9B,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,UAAU;EACR,MAAM,UAAU,YAAY,IAAI,IAAI;EACpC,IAAI,UAAU,aACZ,QAAQ,OAAO,MAAM,yBAAyB,MAAM,GAAG,KAAK,MAAM,OAAO,EAAE,KAAK;CAEpF;AACF"}
1
+ {"version":3,"file":"timing.js","names":[],"sources":["../src/timing.ts"],"sourcesContent":["/**\n * Lightweight phase timing for factory boot and hot request paths.\n *\n * Deliberately not a metrics framework: structured stderr lines that can be\n * grepped (`[factory:timing]`) and diffed across runs to localize slow boot\n * phases and slow per-request dependencies.\n */\n\nimport type { Context } from 'hono';\n\n/**\n * Verbose per-operation timing (HTTP requests, storage calls, sandbox\n * commands). Off by default; enable per deployment with `FACTORY_TIMING=1`.\n */\nexport const factoryTimingEnabled = /^(1|true|yes)$/i.test(process.env.FACTORY_TIMING ?? '');\n\nfunction logTiming(line: string): void {\n process.stderr.write(`[factory:timing] ${line}\\n`);\n}\n\n/** Run `fn`, logging its duration only when `FACTORY_TIMING` is enabled. */\nexport async function timed<T>(phase: string, fn: () => Promise<T>): Promise<T> {\n if (!factoryTimingEnabled) return fn();\n const start = performance.now();\n try {\n return await fn();\n } finally {\n logTiming(`${phase} ${Math.round(performance.now() - start)}ms`);\n }\n}\n\n/** Hono middleware logging method, path, status, and duration of each request. */\nexport function createRequestTimingMiddleware() {\n return async (c: Context, next: () => Promise<void>): Promise<void> => {\n const start = performance.now();\n try {\n await next();\n } finally {\n logTiming(`http ${c.req.method} ${c.req.path} ${c.res.status} ${Math.round(performance.now() - start)}ms`);\n }\n };\n}\n\n/**\n * Patch `methods` on `target` in place so each call logs `<label>.<method>`\n * duration. In-place (not a Proxy) so object identity is preserved.\n */\nexport function instrumentMethods<T extends object>(target: T, label: string, methods: readonly string[]): T {\n if (!factoryTimingEnabled) return target;\n const record = target as Record<string, unknown>;\n for (const name of methods) {\n const original = record[name];\n if (typeof original !== 'function') continue;\n record[name] = (...args: unknown[]) => {\n const start = performance.now();\n const result = original.apply(target, args);\n if (result instanceof Promise) {\n return result.finally(() => logTiming(`${label}.${name} ${Math.round(performance.now() - start)}ms`));\n }\n return result;\n };\n }\n return target;\n}\n\n/** Every method name on `target`'s prototype chain, excluding `Object.prototype`. */\nfunction prototypeMethodNames(target: object): string[] {\n const names = new Set<string>();\n for (\n let proto = Object.getPrototypeOf(target);\n proto && proto !== Object.prototype;\n proto = Object.getPrototypeOf(proto)\n ) {\n for (const name of Object.getOwnPropertyNames(proto)) {\n if (name === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(proto, name);\n if (typeof descriptor?.value === 'function') names.add(name);\n }\n }\n return [...names];\n}\n\n/**\n * Wrap every async method on `target` so each call logs `<label>.<method>`\n * duration. Domains passed to a `registerDomain` method are instrumented in\n * place as `<label>.<domain name>`, so the handle it returns and later\n * `getDomain` lookups are the same timed object. Returns `target` unchanged\n * when timing is disabled.\n */\nexport function instrumentAsyncMethods<T extends object>(target: T, label: string): T {\n if (!factoryTimingEnabled) return target;\n const wrapped = new Map<PropertyKey, unknown>();\n return new Proxy(target, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (typeof value !== 'function' || typeof prop === 'symbol' || prop === 'constructor') return value;\n const cached = wrapped.get(prop);\n if (cached) return cached;\n const fn = (...args: unknown[]) => {\n const start = performance.now();\n const result = value.apply(obj, args);\n if (prop === 'registerDomain' && result && typeof result === 'object' && !(result instanceof Promise)) {\n const name = (result as { name?: unknown }).name;\n return instrumentMethods(result, `${label}.${String(name)}`, prototypeMethodNames(result));\n }\n if (result instanceof Promise) {\n return result.finally(() => logTiming(`${label}.${prop} ${Math.round(performance.now() - start)}ms`));\n }\n return result;\n };\n wrapped.set(prop, fn);\n return fn;\n },\n });\n}\n\n/** Run `fn`, always logging its duration under `phase`. Boot-phase use. */\nexport async function timedPhase<T>(phase: string, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n process.stderr.write(`[factory:timing] ${phase} ${Math.round(performance.now() - start)}ms\\n`);\n }\n}\n\n/**\n * Run `fn`, logging only when it exceeds `thresholdMs`. Request-path use,\n * where per-request logging would be noise but slow outliers must surface.\n */\nexport async function timedAboveThreshold<T>(phase: string, thresholdMs: number, fn: () => Promise<T>): Promise<T> {\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const elapsed = performance.now() - start;\n if (elapsed > thresholdMs) {\n process.stderr.write(`[factory:timing] slow ${phase} ${Math.round(elapsed)}ms\\n`);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ToolAnnotations } from '@mastra/core/tools';
2
+ /**
3
+ * MCP hints for each Factory tool, so clients like ChatGPT can tell lookups
4
+ * from writes and ask before a tool changes or discards state.
5
+ */
6
+ export declare const FACTORY_TOOL_ANNOTATIONS: Record<string, ToolAnnotations>;
7
+ //# sourceMappingURL=annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/tools/annotations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAY1D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAkBpE,CAAC"}
@@ -0,0 +1,102 @@
1
+ //#region src/tools/annotations.ts
2
+ const read = {
3
+ readOnlyHint: true,
4
+ destructiveHint: false,
5
+ idempotentHint: true,
6
+ openWorldHint: false
7
+ };
8
+ const additive = {
9
+ readOnlyHint: false,
10
+ destructiveHint: false,
11
+ openWorldHint: false
12
+ };
13
+ const toggle = {
14
+ ...additive,
15
+ idempotentHint: true
16
+ };
17
+ const changes = {
18
+ readOnlyHint: false,
19
+ destructiveHint: true,
20
+ openWorldHint: false
21
+ };
22
+ /**
23
+ * MCP hints for each Factory tool, so clients like ChatGPT can tell lookups
24
+ * from writes and ask before a tool changes or discards state.
25
+ */
26
+ const FACTORY_TOOL_ANNOTATIONS = {
27
+ factory_list_work_items: {
28
+ ...read,
29
+ title: "List work items"
30
+ },
31
+ factory_inspect_work_item: {
32
+ ...read,
33
+ title: "Inspect a work item"
34
+ },
35
+ factory_list_attention: {
36
+ ...read,
37
+ title: "List items that need attention"
38
+ },
39
+ factory_create_work_item: {
40
+ ...additive,
41
+ title: "Create a work item"
42
+ },
43
+ factory_update_work_item: {
44
+ ...changes,
45
+ title: "Update a work item"
46
+ },
47
+ factory_transition_work_item: {
48
+ ...changes,
49
+ title: "Move a work item to another stage"
50
+ },
51
+ factory_retry_decision: {
52
+ ...additive,
53
+ title: "Retry a failed decision"
54
+ },
55
+ factory_dismiss_decision: {
56
+ ...changes,
57
+ title: "Dismiss a decision"
58
+ },
59
+ factory_resolve_proposal: {
60
+ ...changes,
61
+ title: "Approve or reject a proposal"
62
+ },
63
+ factory_subscribe_work_item: {
64
+ ...toggle,
65
+ title: "Follow a work item"
66
+ },
67
+ factory_unsubscribe_work_item: {
68
+ ...toggle,
69
+ title: "Stop following a work item"
70
+ },
71
+ factory_start_session: {
72
+ ...additive,
73
+ openWorldHint: true,
74
+ title: "Start an agent session"
75
+ },
76
+ factory_signal_session: {
77
+ ...additive,
78
+ openWorldHint: true,
79
+ title: "Message a running session"
80
+ },
81
+ factory_message_origin: {
82
+ ...additive,
83
+ title: "Reply to the session that started this one"
84
+ },
85
+ factory_answer_approval: {
86
+ ...changes,
87
+ openWorldHint: true,
88
+ title: "Answer a session approval request"
89
+ },
90
+ search: {
91
+ ...read,
92
+ title: "Search work items"
93
+ },
94
+ fetch: {
95
+ ...read,
96
+ title: "Fetch a work item"
97
+ }
98
+ };
99
+ //#endregion
100
+ export { FACTORY_TOOL_ANNOTATIONS };
101
+
102
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.js","names":[],"sources":["../../src/tools/annotations.ts"],"sourcesContent":["import type { ToolAnnotations } from '@mastra/core/tools';\n\nconst read: ToolAnnotations = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n};\nconst additive: ToolAnnotations = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };\nconst toggle: ToolAnnotations = { ...additive, idempotentHint: true };\nconst changes: ToolAnnotations = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };\n\n/**\n * MCP hints for each Factory tool, so clients like ChatGPT can tell lookups\n * from writes and ask before a tool changes or discards state.\n */\nexport const FACTORY_TOOL_ANNOTATIONS: Record<string, ToolAnnotations> = {\n factory_list_work_items: { ...read, title: 'List work items' },\n factory_inspect_work_item: { ...read, title: 'Inspect a work item' },\n factory_list_attention: { ...read, title: 'List items that need attention' },\n factory_create_work_item: { ...additive, title: 'Create a work item' },\n factory_update_work_item: { ...changes, title: 'Update a work item' },\n factory_transition_work_item: { ...changes, title: 'Move a work item to another stage' },\n factory_retry_decision: { ...additive, title: 'Retry a failed decision' },\n factory_dismiss_decision: { ...changes, title: 'Dismiss a decision' },\n factory_resolve_proposal: { ...changes, title: 'Approve or reject a proposal' },\n factory_subscribe_work_item: { ...toggle, title: 'Follow a work item' },\n factory_unsubscribe_work_item: { ...toggle, title: 'Stop following a work item' },\n factory_start_session: { ...additive, openWorldHint: true, title: 'Start an agent session' },\n factory_signal_session: { ...additive, openWorldHint: true, title: 'Message a running session' },\n factory_message_origin: { ...additive, title: 'Reply to the session that started this one' },\n factory_answer_approval: { ...changes, openWorldHint: true, title: 'Answer a session approval request' },\n search: { ...read, title: 'Search work items' },\n fetch: { ...read, title: 'Fetch a work item' },\n};\n"],"mappings":";AAEA,MAAM,OAAwB;CAC5B,cAAc;CACd,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;AACjB;AACA,MAAM,WAA4B;CAAE,cAAc;CAAO,iBAAiB;CAAO,eAAe;AAAM;AACtG,MAAM,SAA0B;CAAE,GAAG;CAAU,gBAAgB;AAAK;AACpE,MAAM,UAA2B;CAAE,cAAc;CAAO,iBAAiB;CAAM,eAAe;AAAM;;;;;AAMpG,MAAa,2BAA4D;CACvE,yBAAyB;EAAE,GAAG;EAAM,OAAO;CAAkB;CAC7D,2BAA2B;EAAE,GAAG;EAAM,OAAO;CAAsB;CACnE,wBAAwB;EAAE,GAAG;EAAM,OAAO;CAAiC;CAC3E,0BAA0B;EAAE,GAAG;EAAU,OAAO;CAAqB;CACrE,0BAA0B;EAAE,GAAG;EAAS,OAAO;CAAqB;CACpE,8BAA8B;EAAE,GAAG;EAAS,OAAO;CAAoC;CACvF,wBAAwB;EAAE,GAAG;EAAU,OAAO;CAA0B;CACxE,0BAA0B;EAAE,GAAG;EAAS,OAAO;CAAqB;CACpE,0BAA0B;EAAE,GAAG;EAAS,OAAO;CAA+B;CAC9E,6BAA6B;EAAE,GAAG;EAAQ,OAAO;CAAqB;CACtE,+BAA+B;EAAE,GAAG;EAAQ,OAAO;CAA6B;CAChF,uBAAuB;EAAE,GAAG;EAAU,eAAe;EAAM,OAAO;CAAyB;CAC3F,wBAAwB;EAAE,GAAG;EAAU,eAAe;EAAM,OAAO;CAA4B;CAC/F,wBAAwB;EAAE,GAAG;EAAU,OAAO;CAA6C;CAC3F,yBAAyB;EAAE,GAAG;EAAS,eAAe;EAAM,OAAO;CAAoC;CACvG,QAAQ;EAAE,GAAG;EAAM,OAAO;CAAoB;CAC9C,OAAO;EAAE,GAAG;EAAM,OAAO;CAAoB;AAC/C"}
@@ -42,6 +42,17 @@ export declare function hasFinalReport(notifications: FactoryReporting['notifica
42
42
  /** Ignore reports Factory sent on the session's behalf (the backstop). */
43
43
  agentOnly?: boolean;
44
44
  }): Promise<boolean>;
45
+ /**
46
+ * Whether the newest report `senderSessionId` put on the recipient thread is
47
+ * already this exact `failed` backstop. A session stuck failing the same way
48
+ * (a run that dies before the model, restarted over and over) then reports
49
+ * once, not once per attempt; any other report in between resets it.
50
+ */
51
+ export declare function repeatsLastFailure(notifications: FactoryReporting['notifications'], { senderSessionId, recipientThreadId, summary, }: {
52
+ senderSessionId: string;
53
+ recipientThreadId: string;
54
+ summary: string;
55
+ }): Promise<boolean>;
45
56
  export declare function reportNotification({ sender, status, message, attributes, }: {
46
57
  sender: FactoryReportSender;
47
58
  status?: FactoryReportStatus;
@@ -1 +1 @@
1
- {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../src/tools/reports.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../src/tools/reports.ts"],"names":[],"mappings":"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"}
@@ -50,6 +50,20 @@ async function hasFinalReport(notifications, { senderSessionId, recipientThreadI
50
50
  }
51
51
  return false;
52
52
  }
53
+ /**
54
+ * Whether the newest report `senderSessionId` put on the recipient thread is
55
+ * already this exact `failed` backstop. A session stuck failing the same way
56
+ * (a run that dies before the model, restarted over and over) then reports
57
+ * once, not once per attempt; any other report in between resets it.
58
+ */
59
+ async function repeatsLastFailure(notifications, { senderSessionId, recipientThreadId, summary }) {
60
+ const last = (await notifications.listNotifications({
61
+ threadId: recipientThreadId,
62
+ source: FACTORY_SESSION_NOTIFICATION_SOURCE,
63
+ limit: FINAL_REPORT_SCAN
64
+ })).filter((record) => record.attributes?.["from-session"] === senderSessionId).reduce((newest, record) => !newest || record.createdAt > newest.createdAt ? record : newest, void 0);
65
+ return last?.kind === "failed" && last.attributes?.backstop === "true" && last.summary === summary;
66
+ }
53
67
  function reportNotification({ sender, status, message, attributes = {} }) {
54
68
  const form = FACTORY_REPORT_FORMS[status ?? "report"];
55
69
  return {
@@ -144,6 +158,11 @@ async function sendRunEndReport(reporting, { sender, recipients, since, outcome,
144
158
  waiting = true;
145
159
  continue;
146
160
  }
161
+ if (outcome.status === "failed" && await repeatsLastFailure(reporting.notifications, {
162
+ senderSessionId: sender.sessionId,
163
+ recipientThreadId: recipient.threadId,
164
+ summary: message
165
+ })) continue;
147
166
  deliveries.push(await reporting.deliver(recipient, notification));
148
167
  }
149
168
  return {
@@ -347,6 +366,6 @@ function createSessionApprovalNotifier(deps) {
347
366
  };
348
367
  }
349
368
  //#endregion
350
- export { FACTORY_REPORT_FORMS, createSessionApprovalNotifier, createSessionRunEndReporter, createStageChangeNotifier, createWorkItemRunEndReporter, hasFinalReport, reportNotification, sendRunEndReport, workItemReportAttributes, workItemReportRecipients };
369
+ export { FACTORY_REPORT_FORMS, createSessionApprovalNotifier, createSessionRunEndReporter, createStageChangeNotifier, createWorkItemRunEndReporter, hasFinalReport, repeatsLastFailure, reportNotification, sendRunEndReport, workItemReportAttributes, workItemReportRecipients };
351
370
 
352
371
  //# sourceMappingURL=reports.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reports.js","names":[],"sources":["../../src/tools/reports.ts"],"sourcesContent":["/**\n * Reports from sessions doing the work back to whoever follows it: a side\n * session's origin, or every subscriber of a work item. The agent reports with\n * `factory_message_origin`; when a run ends without one, the run-end backstop\n * sends it, so each run produces exactly one final report per recipient.\n */\n\nimport type {\n NotificationPriority,\n NotificationsStorage,\n SendNotificationSignalInput,\n} from '@mastra/core/notifications';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_SESSION_NOTIFICATION_SOURCE } from './deliver.js';\nimport type { FactoryDeliveryResult, FactoryDeliveryTarget } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\n\nexport type FactoryReportStatus = 'progress' | 'blocked' | 'done' | 'failed';\n\n/** How each report status travels. Final reports never coalesce, so a later report can't overwrite them. */\nexport const FACTORY_REPORT_FORMS: Record<\n FactoryReportStatus | 'report',\n { kind: string; priority: NotificationPriority; coalesce: boolean }\n> = {\n failed: { kind: 'failed', priority: 'urgent', coalesce: false },\n blocked: { kind: 'blocked', priority: 'urgent', coalesce: false },\n done: { kind: 'done', priority: 'high', coalesce: false },\n progress: { kind: 'progress', priority: 'low', coalesce: true },\n // Not `message`: only messages can be answered, so a report can't start a reply loop.\n report: { kind: 'report', priority: 'high', coalesce: false },\n};\n\n/** How many recent `factory-session` notifications on a recipient thread the final-report check reads. */\nconst FINAL_REPORT_SCAN = 100;\n\nexport interface FactoryReportSender {\n sessionId: string;\n threadId: string;\n userId: string;\n}\n\nexport type FactoryReportDeliver = (\n target: FactoryDeliveryTarget,\n notification: Omit<SendNotificationSignalInput, 'source'>,\n options?: { onlyIfLive?: boolean },\n) => Promise<FactoryDeliveryResult>;\n\nexport interface FactoryReporting {\n deliver: FactoryReportDeliver;\n notifications: Pick<NotificationsStorage, 'listNotifications'>;\n}\n\n/**\n * Whether `senderSessionId` already put a report of one of `kinds` on the\n * recipient thread since `since`. Reads the durable notifications store, so it\n * holds across restarts. `listNotifications` has no time filter: records come\n * newest-updated first, and the scan stops at the first one last updated\n * before `since` (nothing after it can have been created later).\n */\nexport async function hasFinalReport(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n since,\n kinds,\n agentOnly = false,\n }: {\n senderSessionId: string;\n recipientThreadId: string;\n since: Date;\n kinds: readonly string[];\n /** Ignore reports Factory sent on the session's behalf (the backstop). */\n agentOnly?: boolean;\n },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n for (const record of records) {\n if (record.updatedAt < since) break;\n if (\n record.attributes?.['from-session'] === senderSessionId &&\n !(agentOnly && record.attributes?.backstop === 'true') &&\n // A reply carrying a final status counts like the report itself.\n (kinds.includes(record.kind) ||\n (record.kind === 'reply' &&\n typeof record.attributes?.status === 'string' &&\n kinds.includes(record.attributes.status))) &&\n record.createdAt >= since\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function reportNotification({\n sender,\n status,\n message,\n attributes = {},\n}: {\n sender: FactoryReportSender;\n status?: FactoryReportStatus;\n message: string;\n attributes?: Record<string, string>;\n}): Omit<SendNotificationSignalInput, 'source'> {\n const form = FACTORY_REPORT_FORMS[status ?? 'report'];\n return {\n kind: form.kind,\n priority: form.priority,\n // The whole text: a notification's summary is all the recipient's agent ever sees, even\n // through notification_inbox (which hides payloads), so a cut-off report loses its verdict.\n summary: message,\n payload: { message },\n sourceId: sender.sessionId,\n ...(form.coalesce ? { coalesceKey: `factory-session:${sender.sessionId}:${form.kind}` } : {}),\n attributes: {\n ...attributes,\n ...(status ? { status } : {}),\n // Last, so nothing above can claim another sender.\n 'from-session': sender.sessionId,\n 'from-thread': sender.threadId,\n 'from-user': sender.userId,\n },\n };\n}\n\n/** Everyone following a work item, except the session doing the reporting. */\nexport async function workItemReportRecipients(\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers'>,\n { orgId, workItemId, senderSessionId }: { orgId: string; workItemId: string; senderSessionId: string },\n): Promise<FactoryDeliveryTarget[]> {\n const subscribers = await workItems.listWorkItemSubscribers({ orgId, workItemId });\n return subscribers\n .filter(subscriber => subscriber.sessionId !== senderSessionId)\n .map(subscriber => ({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }));\n}\n\n/** How reports name an item: `#n`, else its quoted title, else its id. */\nasync function workItemReference(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n { orgId, factoryProjectId, workItemId }: { orgId: string; factoryProjectId: string; workItemId: string },\n): Promise<{ label: string; ref: string }> {\n const item = await workItems.getForProject(orgId, factoryProjectId, workItemId);\n const number = item ? workItemNumber(item) : null;\n if (number !== null) return { label: `#${number}`, ref: `#${number}` };\n return { label: item ? `\"${item.title}\"` : workItemId, ref: workItemId };\n}\n\n/** Attributes that say which item and role a worker's report is about. */\nexport async function workItemReportAttributes(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n {\n orgId,\n factoryProjectId,\n workItemId,\n role,\n }: { orgId: string; factoryProjectId: string; workItemId: string; role: string },\n): Promise<{ label: string; attributes: Record<string, string> }> {\n const { label, ref } = await workItemReference(workItems, { orgId, factoryProjectId, workItemId });\n return { label, attributes: { 'work-item': ref, role } };\n}\n\nexport type FactoryRunOutcome =\n | { status: 'done' }\n | { status: 'failed'; reason: string; failureCode?: string }\n | { status: 'blocked'; toolName?: string; approval?: boolean };\n\n/** `waiting`: the session is waiting for an answer, so a later run owes the final report. */\nexport interface FactoryRunEndResult {\n waiting: boolean;\n deliveries?: FactoryDeliveryResult[];\n}\n\n/**\n * The run-end backstop: send `outcome` to each recipient that hasn't had a\n * final report from this sender since the run started. An agent's own `done`\n * or `failed` report wins. A run that completes after the agent's own\n * `blocked` sends nothing: the agent ended its turn to wait for an answer, and\n * its `done` comes from a later run. A `failed` backstop still goes out after an\n * earlier `blocked`.\n */\nexport async function sendRunEndReport(\n reporting: FactoryReporting,\n {\n sender,\n recipients,\n since,\n outcome,\n subject,\n attributes = {},\n }: {\n sender: FactoryReportSender;\n recipients: FactoryDeliveryTarget[];\n since: Date;\n outcome: FactoryRunOutcome;\n /** Who finished, e.g. \"The work session for #12\". */\n subject: string;\n attributes?: Record<string, string>;\n },\n): Promise<FactoryRunEndResult> {\n const kinds = outcome.status === 'blocked' ? ['blocked'] : ['done', 'failed'];\n const message =\n outcome.status === 'done'\n ? `${subject} finished.`\n : outcome.status === 'failed'\n ? `${subject} failed${outcome.failureCode ? ` (${outcome.failureCode})` : ''}: ${outcome.reason}`\n : outcome.approval\n ? `${subject} is waiting for someone to approve ${outcome.toolName ?? 'a tool call'} in that session.`\n : `${subject} is waiting${outcome.toolName ? ` on ${outcome.toolName}` : ''} and needs an answer.`;\n const notification = reportNotification({\n sender,\n status: outcome.status,\n message,\n attributes: {\n ...attributes,\n backstop: 'true',\n ...(outcome.status === 'failed' && outcome.failureCode ? { 'failure-code': outcome.failureCode } : {}),\n ...(outcome.status === 'blocked' && outcome.toolName ? { 'parked-tool': outcome.toolName } : {}),\n ...(outcome.status === 'blocked' && outcome.approval ? { 'waiting-for': 'approval' } : {}),\n },\n });\n const report = (recipient: FactoryDeliveryTarget, reportKinds: string[], agentOnly = false) =>\n hasFinalReport(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n since,\n kinds: reportKinds,\n agentOnly,\n });\n let waiting = outcome.status === 'blocked';\n const deliveries: FactoryDeliveryResult[] = [];\n for (const recipient of recipients) {\n // Only the agent's own reports count: an earlier run's backstop lands after this\n // run's window opened. A reply already told that follower how a finished run went.\n if (await report(recipient, outcome.status === 'done' ? [...kinds, 'reply'] : kinds, true)) continue;\n // Only the agent's own `blocked` means it ended its turn to wait; a backstop `blocked` from a parked run doesn't.\n if (outcome.status === 'done' && (await report(recipient, ['blocked'], true))) {\n waiting = true;\n continue;\n }\n deliveries.push(await reporting.deliver(recipient, notification));\n }\n return { waiting, deliveries };\n}\n\nexport interface FactoryRunEndInput {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n role: string;\n sender: FactoryReportSender;\n since: Date;\n outcome: FactoryRunOutcome;\n}\n\nexport type FactoryRunEndReporter = (input: FactoryRunEndInput) => Promise<FactoryRunEndResult>;\n\n/** The backstop for a work-item run: reports to the item's subscribers. Failures are logged, never thrown. */\nexport function createWorkItemRunEndReporter({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n /** Resolved per report: the controller exists only once the mount is up. */\n reporting: () => Promise<FactoryReporting | null>;\n}): FactoryRunEndReporter {\n return async input => {\n try {\n const resolved = await reporting();\n if (!resolved) return { waiting: false };\n const recipients = await workItemReportRecipients(workItems, {\n orgId: input.orgId,\n workItemId: input.workItemId,\n senderSessionId: input.sender.sessionId,\n });\n if (recipients.length === 0) return { waiting: false };\n const { label, attributes } = await workItemReportAttributes(workItems, input);\n return await sendRunEndReport(resolved, {\n sender: input.sender,\n recipients,\n since: input.since,\n outcome: input.outcome,\n subject: `The ${input.role} session for ${label}`,\n attributes,\n });\n } catch (error) {\n console.warn('[Factory reports] Unable to send the run-end report.', error);\n return { waiting: false };\n }\n };\n}\n\nexport interface FactoryStageChange {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n fromStage: string | undefined;\n toStage: string;\n}\n\n/**\n * Tells a work item's subscribers it moved, at `low` priority. Only sessions\n * already live hear it: a stage change never brings a session online; an\n * offline subscriber sees the stage in its state signal on its next turn.\n */\nexport function createStageChangeNotifier({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n reporting: () => Promise<FactoryReporting | null>;\n}): (change: FactoryStageChange) => Promise<void> {\n return async change => {\n const subscribers = await workItems.listWorkItemSubscribers({\n orgId: change.orgId,\n workItemId: change.workItemId,\n });\n if (subscribers.length === 0) return;\n const resolved = await reporting();\n if (!resolved) return;\n const { label, ref } = await workItemReference(workItems, change);\n const summary = change.fromStage\n ? `${label} moved from ${change.fromStage} to ${change.toStage}.`\n : `${label} moved to ${change.toStage}.`;\n const notification: Omit<SendNotificationSignalInput, 'source'> = {\n kind: 'stage',\n priority: 'low',\n summary,\n payload: { message: summary },\n sourceId: change.workItemId,\n coalesceKey: `factory-stage:${change.workItemId}`,\n attributes: {\n 'work-item': ref,\n ...(change.fromStage ? { 'from-stage': change.fromStage } : {}),\n 'to-stage': change.toStage,\n },\n };\n for (const subscriber of subscribers) {\n try {\n await resolved.deliver({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }, notification, {\n onlyIfLive: true,\n });\n } catch (error) {\n console.warn(`[Factory reports] Unable to tell ${subscriber.sessionId} that ${label} moved.`, error);\n }\n }\n };\n}\n\n/** Reports one run end of a session; the thread is the one the run used. */\nexport type FactorySessionRunEndReporter = (input: {\n sessionId: string;\n threadId: string | null;\n outcome: FactoryRunOutcome;\n since: Date;\n}) => Promise<void>;\n\n/** A tool call a session's run is holding until someone approves or declines it. */\nexport interface FactoryApprovalRequest {\n sessionId: string;\n threadId: string | null;\n toolName: string;\n toolCallId: string;\n args: unknown;\n}\n\nexport type FactoryApprovalNotifier = (request: FactoryApprovalRequest) => Promise<void>;\n\ninterface SessionReportDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<\n WorkItemsStorage,\n 'findActiveRunBindingForSession' | 'findRunBindingBySession' | 'listWorkItemSubscribers' | 'getForProject'\n >;\n reporting: () => Promise<FactoryReporting | null>;\n}\n\n/** How much of a gated call's arguments an approval notice quotes. */\nconst APPROVAL_ARGS_LENGTH = 3000;\n\n/**\n * Who hears from a session: a work session (one with an active run binding)\n * reports to its item's followers, a side session to the session that started\n * it. Any other session (a person's own) has nobody to tell.\n */\nasync function sessionAudience(\n { sessions, workItems }: Omit<SessionReportDependencies, 'reporting'>,\n sessionId: string,\n threadId: string | null,\n /** When the run started: a binding revoked since then (the run moved its card to a finished stage) still counts. */\n since?: Date,\n): Promise<\n | {\n kind: 'work-item';\n binding: NonNullable<Awaited<ReturnType<WorkItemsStorage['findActiveRunBindingForSession']>>>;\n sender: FactoryReportSender;\n }\n | { kind: 'origin'; sender: FactoryReportSender; recipient: FactoryDeliveryTarget; subject: string }\n | null\n> {\n const row = await sessions.getBySessionId(sessionId);\n if (!row?.factoryProjectId) return null;\n let binding = await workItems.findActiveRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId: row.sessionId,\n });\n if (!binding && since && threadId) {\n const latest = await workItems.findRunBindingBySession({\n factoryProjectId: row.factoryProjectId,\n threadId,\n resourceId: row.sessionId,\n sessionId: row.sessionId,\n });\n if (latest?.orgId === row.orgId && latest.revokedAt && latest.revokedAt >= since) binding = latest;\n }\n if (binding) {\n return {\n kind: 'work-item',\n binding,\n sender: { sessionId: row.sessionId, threadId: binding.threadId, userId: row.userId },\n };\n }\n if (!row.originSessionId || !row.originThreadId || !threadId) return null;\n return {\n kind: 'origin',\n sender: { sessionId: row.sessionId, threadId, userId: row.userId },\n recipient: { sessionId: row.originSessionId, threadId: row.originThreadId },\n subject: `The side session \"${row.title ?? row.sessionId}\"`,\n };\n}\n\n/** Reports each run end of a work or side session to whoever follows it. */\nexport function createSessionRunEndReporter(deps: SessionReportDependencies): FactorySessionRunEndReporter {\n const workItemRunEnd = createWorkItemRunEndReporter(deps);\n return async ({ sessionId, threadId, outcome, since }) => {\n const audience = await sessionAudience(deps, sessionId, threadId, since);\n if (!audience) return;\n if (audience.kind === 'work-item') {\n const { binding, sender } = audience;\n await workItemRunEnd({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n role: binding.role,\n sender,\n since,\n outcome,\n });\n return;\n }\n const resolved = await deps.reporting();\n if (!resolved) return;\n await sendRunEndReport(resolved, {\n sender: audience.sender,\n recipients: [audience.recipient],\n since,\n outcome,\n subject: audience.subject,\n });\n };\n}\n\n/**\n * Tells whoever follows a session, urgently, the moment its run holds a tool\n * call for approval. The run stays open on the gate, so no run end would say\n * so; the notice names the session and call so a follower can answer it with\n * `factory_answer_approval`.\n */\nexport function createSessionApprovalNotifier(deps: SessionReportDependencies): FactoryApprovalNotifier {\n return async ({ sessionId, threadId, toolName, toolCallId, args }) => {\n const audience = await sessionAudience(deps, sessionId, threadId);\n if (!audience) return;\n const resolved = await deps.reporting();\n if (!resolved) return;\n let subject: string;\n let recipients: FactoryDeliveryTarget[];\n let attributes: Record<string, string> = {};\n if (audience.kind === 'work-item') {\n const { binding } = audience;\n recipients = await workItemReportRecipients(deps.workItems, {\n orgId: binding.orgId,\n workItemId: binding.workItemId,\n senderSessionId: audience.sender.sessionId,\n });\n const named = await workItemReportAttributes(deps.workItems, binding);\n subject = `The ${binding.role} session for ${named.label}`;\n attributes = named.attributes;\n } else {\n recipients = [audience.recipient];\n subject = audience.subject;\n }\n if (recipients.length === 0) return;\n const argsText = JSON.stringify(args ?? {}) ?? '{}';\n const quoted = argsText.length > APPROVAL_ARGS_LENGTH ? `${argsText.slice(0, APPROVAL_ARGS_LENGTH)}…` : argsText;\n const message = [\n `${subject} is waiting for approval to run ${toolName} with ${quoted}.`,\n `Answer it with factory_answer_approval (sessionId \"${audience.sender.sessionId}\", toolCallId \"${toolCallId}\"), or open that session to approve or decline it there.`,\n ].join(' ');\n const notification = {\n ...reportNotification({\n sender: audience.sender,\n message,\n attributes: { ...attributes, 'waiting-for': 'approval', 'tool-name': toolName, 'tool-call-id': toolCallId },\n }),\n kind: 'approval',\n priority: 'urgent' as const,\n // One notice per held call, even if the gate is announced again.\n dedupeKey: `factory-approval:${toolCallId}`,\n };\n for (const recipient of recipients) await resolved.deliver(recipient, notification);\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reports.js","names":[],"sources":["../../src/tools/reports.ts"],"sourcesContent":["/**\n * Reports from sessions doing the work back to whoever follows it: a side\n * session's origin, or every subscriber of a work item. The agent reports with\n * `factory_message_origin`; when a run ends without one, the run-end backstop\n * sends it, so each run produces exactly one final report per recipient.\n */\n\nimport type {\n NotificationPriority,\n NotificationsStorage,\n SendNotificationSignalInput,\n} from '@mastra/core/notifications';\n\nimport type { SourceControlSessionLookup } from '../session/factory-session.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { FACTORY_SESSION_NOTIFICATION_SOURCE } from './deliver.js';\nimport type { FactoryDeliveryResult, FactoryDeliveryTarget } from './deliver.js';\nimport { workItemNumber } from './read-tools.js';\n\nexport type FactoryReportStatus = 'progress' | 'blocked' | 'done' | 'failed';\n\n/** How each report status travels. Final reports never coalesce, so a later report can't overwrite them. */\nexport const FACTORY_REPORT_FORMS: Record<\n FactoryReportStatus | 'report',\n { kind: string; priority: NotificationPriority; coalesce: boolean }\n> = {\n failed: { kind: 'failed', priority: 'urgent', coalesce: false },\n blocked: { kind: 'blocked', priority: 'urgent', coalesce: false },\n done: { kind: 'done', priority: 'high', coalesce: false },\n progress: { kind: 'progress', priority: 'low', coalesce: true },\n // Not `message`: only messages can be answered, so a report can't start a reply loop.\n report: { kind: 'report', priority: 'high', coalesce: false },\n};\n\n/** How many recent `factory-session` notifications on a recipient thread the final-report check reads. */\nconst FINAL_REPORT_SCAN = 100;\n\nexport interface FactoryReportSender {\n sessionId: string;\n threadId: string;\n userId: string;\n}\n\nexport type FactoryReportDeliver = (\n target: FactoryDeliveryTarget,\n notification: Omit<SendNotificationSignalInput, 'source'>,\n options?: { onlyIfLive?: boolean },\n) => Promise<FactoryDeliveryResult>;\n\nexport interface FactoryReporting {\n deliver: FactoryReportDeliver;\n notifications: Pick<NotificationsStorage, 'listNotifications'>;\n}\n\n/**\n * Whether `senderSessionId` already put a report of one of `kinds` on the\n * recipient thread since `since`. Reads the durable notifications store, so it\n * holds across restarts. `listNotifications` has no time filter: records come\n * newest-updated first, and the scan stops at the first one last updated\n * before `since` (nothing after it can have been created later).\n */\nexport async function hasFinalReport(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n since,\n kinds,\n agentOnly = false,\n }: {\n senderSessionId: string;\n recipientThreadId: string;\n since: Date;\n kinds: readonly string[];\n /** Ignore reports Factory sent on the session's behalf (the backstop). */\n agentOnly?: boolean;\n },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n for (const record of records) {\n if (record.updatedAt < since) break;\n if (\n record.attributes?.['from-session'] === senderSessionId &&\n !(agentOnly && record.attributes?.backstop === 'true') &&\n // A reply carrying a final status counts like the report itself.\n (kinds.includes(record.kind) ||\n (record.kind === 'reply' &&\n typeof record.attributes?.status === 'string' &&\n kinds.includes(record.attributes.status))) &&\n record.createdAt >= since\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether the newest report `senderSessionId` put on the recipient thread is\n * already this exact `failed` backstop. A session stuck failing the same way\n * (a run that dies before the model, restarted over and over) then reports\n * once, not once per attempt; any other report in between resets it.\n */\nexport async function repeatsLastFailure(\n notifications: FactoryReporting['notifications'],\n {\n senderSessionId,\n recipientThreadId,\n summary,\n }: { senderSessionId: string; recipientThreadId: string; summary: string },\n): Promise<boolean> {\n const records = await notifications.listNotifications({\n threadId: recipientThreadId,\n source: FACTORY_SESSION_NOTIFICATION_SOURCE,\n limit: FINAL_REPORT_SCAN,\n });\n // Storage orders by `updatedAt`, which moves when a report is read; the\n // newest report is the one created last.\n const last = records\n .filter(record => record.attributes?.['from-session'] === senderSessionId)\n .reduce<(typeof records)[number] | undefined>(\n (newest, record) => (!newest || record.createdAt > newest.createdAt ? record : newest),\n undefined,\n );\n return last?.kind === 'failed' && last.attributes?.backstop === 'true' && last.summary === summary;\n}\n\nexport function reportNotification({\n sender,\n status,\n message,\n attributes = {},\n}: {\n sender: FactoryReportSender;\n status?: FactoryReportStatus;\n message: string;\n attributes?: Record<string, string>;\n}): Omit<SendNotificationSignalInput, 'source'> {\n const form = FACTORY_REPORT_FORMS[status ?? 'report'];\n return {\n kind: form.kind,\n priority: form.priority,\n // The whole text: a notification's summary is all the recipient's agent ever sees, even\n // through notification_inbox (which hides payloads), so a cut-off report loses its verdict.\n summary: message,\n payload: { message },\n sourceId: sender.sessionId,\n ...(form.coalesce ? { coalesceKey: `factory-session:${sender.sessionId}:${form.kind}` } : {}),\n attributes: {\n ...attributes,\n ...(status ? { status } : {}),\n // Last, so nothing above can claim another sender.\n 'from-session': sender.sessionId,\n 'from-thread': sender.threadId,\n 'from-user': sender.userId,\n },\n };\n}\n\n/** Everyone following a work item, except the session doing the reporting. */\nexport async function workItemReportRecipients(\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers'>,\n { orgId, workItemId, senderSessionId }: { orgId: string; workItemId: string; senderSessionId: string },\n): Promise<FactoryDeliveryTarget[]> {\n const subscribers = await workItems.listWorkItemSubscribers({ orgId, workItemId });\n return subscribers\n .filter(subscriber => subscriber.sessionId !== senderSessionId)\n .map(subscriber => ({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }));\n}\n\n/** How reports name an item: `#n`, else its quoted title, else its id. */\nasync function workItemReference(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n { orgId, factoryProjectId, workItemId }: { orgId: string; factoryProjectId: string; workItemId: string },\n): Promise<{ label: string; ref: string }> {\n const item = await workItems.getForProject(orgId, factoryProjectId, workItemId);\n const number = item ? workItemNumber(item) : null;\n if (number !== null) return { label: `#${number}`, ref: `#${number}` };\n return { label: item ? `\"${item.title}\"` : workItemId, ref: workItemId };\n}\n\n/** Attributes that say which item and role a worker's report is about. */\nexport async function workItemReportAttributes(\n workItems: Pick<WorkItemsStorage, 'getForProject'>,\n {\n orgId,\n factoryProjectId,\n workItemId,\n role,\n }: { orgId: string; factoryProjectId: string; workItemId: string; role: string },\n): Promise<{ label: string; attributes: Record<string, string> }> {\n const { label, ref } = await workItemReference(workItems, { orgId, factoryProjectId, workItemId });\n return { label, attributes: { 'work-item': ref, role } };\n}\n\nexport type FactoryRunOutcome =\n | { status: 'done' }\n | { status: 'failed'; reason: string; failureCode?: string }\n | { status: 'blocked'; toolName?: string; approval?: boolean };\n\n/** `waiting`: the session is waiting for an answer, so a later run owes the final report. */\nexport interface FactoryRunEndResult {\n waiting: boolean;\n deliveries?: FactoryDeliveryResult[];\n}\n\n/**\n * The run-end backstop: send `outcome` to each recipient that hasn't had a\n * final report from this sender since the run started. An agent's own `done`\n * or `failed` report wins. A run that completes after the agent's own\n * `blocked` sends nothing: the agent ended its turn to wait for an answer, and\n * its `done` comes from a later run. A `failed` backstop still goes out after an\n * earlier `blocked`.\n */\nexport async function sendRunEndReport(\n reporting: FactoryReporting,\n {\n sender,\n recipients,\n since,\n outcome,\n subject,\n attributes = {},\n }: {\n sender: FactoryReportSender;\n recipients: FactoryDeliveryTarget[];\n since: Date;\n outcome: FactoryRunOutcome;\n /** Who finished, e.g. \"The work session for #12\". */\n subject: string;\n attributes?: Record<string, string>;\n },\n): Promise<FactoryRunEndResult> {\n const kinds = outcome.status === 'blocked' ? ['blocked'] : ['done', 'failed'];\n const message =\n outcome.status === 'done'\n ? `${subject} finished.`\n : outcome.status === 'failed'\n ? `${subject} failed${outcome.failureCode ? ` (${outcome.failureCode})` : ''}: ${outcome.reason}`\n : outcome.approval\n ? `${subject} is waiting for someone to approve ${outcome.toolName ?? 'a tool call'} in that session.`\n : `${subject} is waiting${outcome.toolName ? ` on ${outcome.toolName}` : ''} and needs an answer.`;\n const notification = reportNotification({\n sender,\n status: outcome.status,\n message,\n attributes: {\n ...attributes,\n backstop: 'true',\n ...(outcome.status === 'failed' && outcome.failureCode ? { 'failure-code': outcome.failureCode } : {}),\n ...(outcome.status === 'blocked' && outcome.toolName ? { 'parked-tool': outcome.toolName } : {}),\n ...(outcome.status === 'blocked' && outcome.approval ? { 'waiting-for': 'approval' } : {}),\n },\n });\n const report = (recipient: FactoryDeliveryTarget, reportKinds: string[], agentOnly = false) =>\n hasFinalReport(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n since,\n kinds: reportKinds,\n agentOnly,\n });\n let waiting = outcome.status === 'blocked';\n const deliveries: FactoryDeliveryResult[] = [];\n for (const recipient of recipients) {\n // Only the agent's own reports count: an earlier run's backstop lands after this\n // run's window opened. A reply already told that follower how a finished run went.\n if (await report(recipient, outcome.status === 'done' ? [...kinds, 'reply'] : kinds, true)) continue;\n // Only the agent's own `blocked` means it ended its turn to wait; a backstop `blocked` from a parked run doesn't.\n if (outcome.status === 'done' && (await report(recipient, ['blocked'], true))) {\n waiting = true;\n continue;\n }\n if (\n outcome.status === 'failed' &&\n (await repeatsLastFailure(reporting.notifications, {\n senderSessionId: sender.sessionId,\n recipientThreadId: recipient.threadId,\n summary: message,\n }))\n ) {\n continue;\n }\n deliveries.push(await reporting.deliver(recipient, notification));\n }\n return { waiting, deliveries };\n}\n\nexport interface FactoryRunEndInput {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n role: string;\n sender: FactoryReportSender;\n since: Date;\n outcome: FactoryRunOutcome;\n}\n\nexport type FactoryRunEndReporter = (input: FactoryRunEndInput) => Promise<FactoryRunEndResult>;\n\n/** The backstop for a work-item run: reports to the item's subscribers. Failures are logged, never thrown. */\nexport function createWorkItemRunEndReporter({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n /** Resolved per report: the controller exists only once the mount is up. */\n reporting: () => Promise<FactoryReporting | null>;\n}): FactoryRunEndReporter {\n return async input => {\n try {\n const resolved = await reporting();\n if (!resolved) return { waiting: false };\n const recipients = await workItemReportRecipients(workItems, {\n orgId: input.orgId,\n workItemId: input.workItemId,\n senderSessionId: input.sender.sessionId,\n });\n if (recipients.length === 0) return { waiting: false };\n const { label, attributes } = await workItemReportAttributes(workItems, input);\n return await sendRunEndReport(resolved, {\n sender: input.sender,\n recipients,\n since: input.since,\n outcome: input.outcome,\n subject: `The ${input.role} session for ${label}`,\n attributes,\n });\n } catch (error) {\n console.warn('[Factory reports] Unable to send the run-end report.', error);\n return { waiting: false };\n }\n };\n}\n\nexport interface FactoryStageChange {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n fromStage: string | undefined;\n toStage: string;\n}\n\n/**\n * Tells a work item's subscribers it moved, at `low` priority. Only sessions\n * already live hear it: a stage change never brings a session online; an\n * offline subscriber sees the stage in its state signal on its next turn.\n */\nexport function createStageChangeNotifier({\n workItems,\n reporting,\n}: {\n workItems: Pick<WorkItemsStorage, 'listWorkItemSubscribers' | 'getForProject'>;\n reporting: () => Promise<FactoryReporting | null>;\n}): (change: FactoryStageChange) => Promise<void> {\n return async change => {\n const subscribers = await workItems.listWorkItemSubscribers({\n orgId: change.orgId,\n workItemId: change.workItemId,\n });\n if (subscribers.length === 0) return;\n const resolved = await reporting();\n if (!resolved) return;\n const { label, ref } = await workItemReference(workItems, change);\n const summary = change.fromStage\n ? `${label} moved from ${change.fromStage} to ${change.toStage}.`\n : `${label} moved to ${change.toStage}.`;\n const notification: Omit<SendNotificationSignalInput, 'source'> = {\n kind: 'stage',\n priority: 'low',\n summary,\n payload: { message: summary },\n sourceId: change.workItemId,\n coalesceKey: `factory-stage:${change.workItemId}`,\n attributes: {\n 'work-item': ref,\n ...(change.fromStage ? { 'from-stage': change.fromStage } : {}),\n 'to-stage': change.toStage,\n },\n };\n for (const subscriber of subscribers) {\n try {\n await resolved.deliver({ sessionId: subscriber.sessionId, threadId: subscriber.threadId }, notification, {\n onlyIfLive: true,\n });\n } catch (error) {\n console.warn(`[Factory reports] Unable to tell ${subscriber.sessionId} that ${label} moved.`, error);\n }\n }\n };\n}\n\n/** Reports one run end of a session; the thread is the one the run used. */\nexport type FactorySessionRunEndReporter = (input: {\n sessionId: string;\n threadId: string | null;\n outcome: FactoryRunOutcome;\n since: Date;\n}) => Promise<void>;\n\n/** A tool call a session's run is holding until someone approves or declines it. */\nexport interface FactoryApprovalRequest {\n sessionId: string;\n threadId: string | null;\n toolName: string;\n toolCallId: string;\n args: unknown;\n}\n\nexport type FactoryApprovalNotifier = (request: FactoryApprovalRequest) => Promise<void>;\n\ninterface SessionReportDependencies {\n sessions: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<\n WorkItemsStorage,\n 'findActiveRunBindingForSession' | 'findRunBindingBySession' | 'listWorkItemSubscribers' | 'getForProject'\n >;\n reporting: () => Promise<FactoryReporting | null>;\n}\n\n/** How much of a gated call's arguments an approval notice quotes. */\nconst APPROVAL_ARGS_LENGTH = 3000;\n\n/**\n * Who hears from a session: a work session (one with an active run binding)\n * reports to its item's followers, a side session to the session that started\n * it. Any other session (a person's own) has nobody to tell.\n */\nasync function sessionAudience(\n { sessions, workItems }: Omit<SessionReportDependencies, 'reporting'>,\n sessionId: string,\n threadId: string | null,\n /** When the run started: a binding revoked since then (the run moved its card to a finished stage) still counts. */\n since?: Date,\n): Promise<\n | {\n kind: 'work-item';\n binding: NonNullable<Awaited<ReturnType<WorkItemsStorage['findActiveRunBindingForSession']>>>;\n sender: FactoryReportSender;\n }\n | { kind: 'origin'; sender: FactoryReportSender; recipient: FactoryDeliveryTarget; subject: string }\n | null\n> {\n const row = await sessions.getBySessionId(sessionId);\n if (!row?.factoryProjectId) return null;\n let binding = await workItems.findActiveRunBindingForSession({\n orgId: row.orgId,\n factoryProjectId: row.factoryProjectId,\n sessionId: row.sessionId,\n });\n if (!binding && since && threadId) {\n const latest = await workItems.findRunBindingBySession({\n factoryProjectId: row.factoryProjectId,\n threadId,\n resourceId: row.sessionId,\n sessionId: row.sessionId,\n });\n if (latest?.orgId === row.orgId && latest.revokedAt && latest.revokedAt >= since) binding = latest;\n }\n if (binding) {\n return {\n kind: 'work-item',\n binding,\n sender: { sessionId: row.sessionId, threadId: binding.threadId, userId: row.userId },\n };\n }\n if (!row.originSessionId || !row.originThreadId || !threadId) return null;\n return {\n kind: 'origin',\n sender: { sessionId: row.sessionId, threadId, userId: row.userId },\n recipient: { sessionId: row.originSessionId, threadId: row.originThreadId },\n subject: `The side session \"${row.title ?? row.sessionId}\"`,\n };\n}\n\n/** Reports each run end of a work or side session to whoever follows it. */\nexport function createSessionRunEndReporter(deps: SessionReportDependencies): FactorySessionRunEndReporter {\n const workItemRunEnd = createWorkItemRunEndReporter(deps);\n return async ({ sessionId, threadId, outcome, since }) => {\n const audience = await sessionAudience(deps, sessionId, threadId, since);\n if (!audience) return;\n if (audience.kind === 'work-item') {\n const { binding, sender } = audience;\n await workItemRunEnd({\n orgId: binding.orgId,\n factoryProjectId: binding.factoryProjectId,\n workItemId: binding.workItemId,\n role: binding.role,\n sender,\n since,\n outcome,\n });\n return;\n }\n const resolved = await deps.reporting();\n if (!resolved) return;\n await sendRunEndReport(resolved, {\n sender: audience.sender,\n recipients: [audience.recipient],\n since,\n outcome,\n subject: audience.subject,\n });\n };\n}\n\n/**\n * Tells whoever follows a session, urgently, the moment its run holds a tool\n * call for approval. The run stays open on the gate, so no run end would say\n * so; the notice names the session and call so a follower can answer it with\n * `factory_answer_approval`.\n */\nexport function createSessionApprovalNotifier(deps: SessionReportDependencies): FactoryApprovalNotifier {\n return async ({ sessionId, threadId, toolName, toolCallId, args }) => {\n const audience = await sessionAudience(deps, sessionId, threadId);\n if (!audience) return;\n const resolved = await deps.reporting();\n if (!resolved) return;\n let subject: string;\n let recipients: FactoryDeliveryTarget[];\n let attributes: Record<string, string> = {};\n if (audience.kind === 'work-item') {\n const { binding } = audience;\n recipients = await workItemReportRecipients(deps.workItems, {\n orgId: binding.orgId,\n workItemId: binding.workItemId,\n senderSessionId: audience.sender.sessionId,\n });\n const named = await workItemReportAttributes(deps.workItems, binding);\n subject = `The ${binding.role} session for ${named.label}`;\n attributes = named.attributes;\n } else {\n recipients = [audience.recipient];\n subject = audience.subject;\n }\n if (recipients.length === 0) return;\n const argsText = JSON.stringify(args ?? {}) ?? '{}';\n const quoted = argsText.length > APPROVAL_ARGS_LENGTH ? `${argsText.slice(0, APPROVAL_ARGS_LENGTH)}…` : argsText;\n const message = [\n `${subject} is waiting for approval to run ${toolName} with ${quoted}.`,\n `Answer it with factory_answer_approval (sessionId \"${audience.sender.sessionId}\", toolCallId \"${toolCallId}\"), or open that session to approve or decline it there.`,\n ].join(' ');\n const notification = {\n ...reportNotification({\n sender: audience.sender,\n message,\n attributes: { ...attributes, 'waiting-for': 'approval', 'tool-name': toolName, 'tool-call-id': toolCallId },\n }),\n kind: 'approval',\n priority: 'urgent' as const,\n // One notice per held call, even if the gate is announced again.\n dedupeKey: `factory-approval:${toolCallId}`,\n };\n for (const recipient of recipients) await resolved.deliver(recipient, notification);\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { AgentControllerEvent } from '@mastra/core/agent-controller';
2
- import { type RunEndCaptureSession } from '../session/run-audit.js';
2
+ import type { RunEndCaptureSession } from '../session/run-audit.js';
3
3
  import type { FactoryRunOutcome } from './reports.js';
4
4
  type RunEndReason = Extract<AgentControllerEvent, {
5
5
  type: 'agent_end';
@@ -1 +1 @@
1
- {"version":3,"file":"run-watch.d.ts","sourceRoot":"","sources":["../../src/tools/run-watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAE1E,OAAO,EAAsD,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACxH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEnF,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7F,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAKxH;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,mBAAmB,EAC3B,EAAE,mBAAmB,EAAE,GAAE;IAAE,mBAAmB,CAAC,EAAE,qBAAqB,CAAA;CAAO,GAC5E,MAAM,IAAI,CAwCZ;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,GAChB;IAAE,SAAS,EAAE,MAAM,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAqBrD;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG,SAAS,EAC7D,YAAY,CAAC,EAAE,MAAM,GACpB,iBAAiB,GAAG,IAAI,CAqB1B"}
1
+ {"version":3,"file":"run-watch.d.ts","sourceRoot":"","sources":["../../src/tools/run-watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAG1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,KAAK,YAAY,GAAG,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEnF,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACxE;AAED,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7F,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAKxH;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,mBAAmB,EAC3B,EAAE,mBAAmB,EAAE,GAAE;IAAE,mBAAmB,CAAC,EAAE,qBAAqB,CAAA;CAAO,GAC5E,MAAM,IAAI,CAwCZ;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,oBAAoB,EAC7B,SAAS,EAAE,MAAM,GAChB;IAAE,SAAS,EAAE,MAAM,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAqBrD;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG,SAAS,EAC7D,YAAY,CAAC,EAAE,MAAM,GACpB,iBAAiB,GAAG,IAAI,CAqB1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"run-watch.js","names":[],"sources":["../../src/tools/run-watch.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\n\nimport { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun, type RunEndCaptureSession } from '../session/run-audit.js';\nimport type { FactoryRunOutcome } from './reports.js';\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\n\n/** What a run stopped on: a tool waiting for an answer, or one waiting for approval. */\nexport interface ParkedTool {\n toolName: string;\n toolCallId: string;\n approval: boolean;\n}\n\nexport interface RunReportSession {\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\n/** Reports one run's end; `since` is when the run's window opened. Should not throw. */\nexport type SessionRunEndReport = (outcome: FactoryRunOutcome, since: Date) => Promise<void>;\n\n/** Told when the run holds a tool call for approval; the run stays open meanwhile. */\nexport type SessionApprovalReport = (request: { toolName: string; toolCallId: string; args: unknown }) => Promise<void>;\n\n/** How much of a run's error message a `failed` report quotes. */\nconst ERROR_MESSAGE_LENGTH = 400;\n\n/**\n * Report every run end of a session, however the run started: the board, a\n * typed message, a wake, or a run after a restart. Attached to each Factory\n * session when it is created, so it outlives any one start. A run someone\n * stopped or steered says nothing, and its window carries into the next run,\n * so reports the agent made in it still count.\n */\nexport function observeSessionRunReports(\n session: RunReportSession,\n report: SessionRunEndReport,\n { onApprovalRequested }: { onApprovalRequested?: SessionApprovalReport } = {},\n): () => void {\n let parked: ParkedTool | undefined;\n let lastError: string | undefined;\n let since = new Date();\n const settle = (work: Promise<void>) =>\n void work.catch(error => {\n console.warn('[Factory reports] Unable to report on a session run.', error);\n });\n return session.subscribe(event => {\n if (event.type === 'tool_suspended' || event.type === 'tool_approval_required') {\n parked = {\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n approval: event.type === 'tool_approval_required',\n };\n // An approval gate holds the run open, so it is announced now, not at the run's end.\n if (event.type === 'tool_approval_required' && onApprovalRequested) {\n settle(onApprovalRequested({ toolName: event.toolName, toolCallId: event.toolCallId, args: event.args }));\n }\n return;\n }\n if (event.type === 'error') {\n lastError = event.error?.message;\n return;\n }\n if (event.type === 'tool_suspension_cancelled' && parked?.toolCallId === event.toolCallId) {\n parked = undefined;\n return;\n }\n if (event.type !== 'agent_end') return;\n const outcome = runOutcome(event.reason, parked, lastError);\n parked = undefined;\n lastError = undefined;\n if (!outcome) return;\n // Move the window now, so a run that starts before this report settles is judged on its own;\n // reports made up to the end, including its last millisecond, belong to the run that ended.\n const runSince = since;\n since = new Date(Date.now() + 1);\n settle(report(outcome, runSince));\n });\n}\n\n/**\n * Renew a kicked-off run's open-run entry until the run ends, like the\n * dispatcher's heartbeat, so no dispatcher starts the role again meanwhile.\n * `endReason` is how that run ended, once it has; `stop` is for a kickoff that fails.\n */\nexport function heartbeatOpenRunUntilEnd(\n session: RunEndCaptureSession,\n kickoffId: string,\n): { endReason: () => RunEndReason; stop: () => void } {\n let endReason: RunEndReason;\n let unsubscribe = () => {};\n const timer = setInterval(\n () => void heartbeatSessionOpenRun(session, kickoffId),\n Math.floor(FACTORY_OPEN_RUN_STALE_MS / 3),\n );\n timer.unref?.();\n let stopped = false;\n const stop = () => {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n unsubscribe();\n };\n unsubscribe = session.subscribe(event => {\n if (event.type !== 'agent_end') return;\n endReason ??= event.reason;\n stop();\n });\n return { endReason: () => endReason, stop };\n}\n\n/** `null` for an aborted run: someone stopped or steered it, which is not an outcome. */\nexport function runOutcome(\n reason: RunEndReason,\n parked: Pick<ParkedTool, 'toolName' | 'approval'> | undefined,\n errorMessage?: string,\n): FactoryRunOutcome | null {\n switch (reason) {\n case 'error': {\n const message = errorMessage?.trim();\n return {\n status: 'failed',\n reason: message\n ? `the run ended in error: ${message.slice(0, ERROR_MESSAGE_LENGTH)}`\n : 'the run ended in error.',\n };\n }\n case 'aborted':\n return null;\n case 'suspended':\n return {\n status: 'blocked',\n ...(parked ? { toolName: parked.toolName, ...(parked.approval ? { approval: true } : {}) } : {}),\n };\n default:\n return { status: 'done' };\n }\n}\n"],"mappings":";;;AAyBA,MAAM,uBAAuB;;;;;;;;AAS7B,SAAgB,yBACd,SACA,QACA,EAAE,wBAAyE,CAAC,GAChE;CACZ,IAAI;CACJ,IAAI;CACJ,IAAI,wBAAQ,IAAI,KAAK;CACrB,MAAM,UAAU,SACd,KAAK,KAAK,OAAM,UAAS;EACvB,QAAQ,KAAK,wDAAwD,KAAK;CAC5E,CAAC;CACH,OAAO,QAAQ,WAAU,UAAS;EAChC,IAAI,MAAM,SAAS,oBAAoB,MAAM,SAAS,0BAA0B;GAC9E,SAAS;IACP,UAAU,MAAM;IAChB,YAAY,MAAM;IAClB,UAAU,MAAM,SAAS;GAC3B;GAEA,IAAI,MAAM,SAAS,4BAA4B,qBAC7C,OAAO,oBAAoB;IAAE,UAAU,MAAM;IAAU,YAAY,MAAM;IAAY,MAAM,MAAM;GAAK,CAAC,CAAC;GAE1G;EACF;EACA,IAAI,MAAM,SAAS,SAAS;GAC1B,YAAY,MAAM,OAAO;GACzB;EACF;EACA,IAAI,MAAM,SAAS,+BAA+B,QAAQ,eAAe,MAAM,YAAY;GACzF,SAAS,KAAA;GACT;EACF;EACA,IAAI,MAAM,SAAS,aAAa;EAChC,MAAM,UAAU,WAAW,MAAM,QAAQ,QAAQ,SAAS;EAC1D,SAAS,KAAA;EACT,YAAY,KAAA;EACZ,IAAI,CAAC,SAAS;EAGd,MAAM,WAAW;EACjB,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC;EAC/B,OAAO,OAAO,SAAS,QAAQ,CAAC;CAClC,CAAC;AACH;;;;;;AAOA,SAAgB,yBACd,SACA,WACqD;CACrD,IAAI;CACJ,IAAI,oBAAoB,CAAC;CACzB,MAAM,QAAQ,kBACN,KAAK,wBAAwB,SAAS,SAAS,GACrD,KAAK,MAAM,4BAA4B,CAAC,CAC1C;CACA,MAAM,QAAQ;CACd,IAAI,UAAU;CACd,MAAM,aAAa;EACjB,IAAI,SAAS;EACb,UAAU;EACV,cAAc,KAAK;EACnB,YAAY;CACd;CACA,cAAc,QAAQ,WAAU,UAAS;EACvC,IAAI,MAAM,SAAS,aAAa;EAChC,cAAc,MAAM;EACpB,KAAK;CACP,CAAC;CACD,OAAO;EAAE,iBAAiB;EAAW;CAAK;AAC5C;;AAGA,SAAgB,WACd,QACA,QACA,cAC0B;CAC1B,QAAQ,QAAR;EACE,KAAK,SAAS;GACZ,MAAM,UAAU,cAAc,KAAK;GACnC,OAAO;IACL,QAAQ;IACR,QAAQ,UACJ,2BAA2B,QAAQ,MAAM,GAAG,oBAAoB,MAChE;GACN;EACF;EACA,KAAK,WACH,OAAO;EACT,KAAK,aACH,OAAO;GACL,QAAQ;GACR,GAAI,SAAS;IAAE,UAAU,OAAO;IAAU,GAAI,OAAO,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;GAAG,IAAI,CAAC;EAChG;EACF,SACE,OAAO,EAAE,QAAQ,OAAO;CAC5B;AACF"}
1
+ {"version":3,"file":"run-watch.js","names":[],"sources":["../../src/tools/run-watch.ts"],"sourcesContent":["import type { AgentControllerEvent } from '@mastra/core/agent-controller';\n\nimport { FACTORY_OPEN_RUN_STALE_MS, heartbeatSessionOpenRun } from '../session/run-audit.js';\nimport type { RunEndCaptureSession } from '../session/run-audit.js';\nimport type { FactoryRunOutcome } from './reports.js';\n\ntype RunEndReason = Extract<AgentControllerEvent, { type: 'agent_end' }>['reason'];\n\n/** What a run stopped on: a tool waiting for an answer, or one waiting for approval. */\nexport interface ParkedTool {\n toolName: string;\n toolCallId: string;\n approval: boolean;\n}\n\nexport interface RunReportSession {\n subscribe(listener: (event: AgentControllerEvent) => void): () => void;\n}\n\n/** Reports one run's end; `since` is when the run's window opened. Should not throw. */\nexport type SessionRunEndReport = (outcome: FactoryRunOutcome, since: Date) => Promise<void>;\n\n/** Told when the run holds a tool call for approval; the run stays open meanwhile. */\nexport type SessionApprovalReport = (request: { toolName: string; toolCallId: string; args: unknown }) => Promise<void>;\n\n/** How much of a run's error message a `failed` report quotes. */\nconst ERROR_MESSAGE_LENGTH = 400;\n\n/**\n * Report every run end of a session, however the run started: the board, a\n * typed message, a wake, or a run after a restart. Attached to each Factory\n * session when it is created, so it outlives any one start. A run someone\n * stopped or steered says nothing, and its window carries into the next run,\n * so reports the agent made in it still count.\n */\nexport function observeSessionRunReports(\n session: RunReportSession,\n report: SessionRunEndReport,\n { onApprovalRequested }: { onApprovalRequested?: SessionApprovalReport } = {},\n): () => void {\n let parked: ParkedTool | undefined;\n let lastError: string | undefined;\n let since = new Date();\n const settle = (work: Promise<void>) =>\n void work.catch(error => {\n console.warn('[Factory reports] Unable to report on a session run.', error);\n });\n return session.subscribe(event => {\n if (event.type === 'tool_suspended' || event.type === 'tool_approval_required') {\n parked = {\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n approval: event.type === 'tool_approval_required',\n };\n // An approval gate holds the run open, so it is announced now, not at the run's end.\n if (event.type === 'tool_approval_required' && onApprovalRequested) {\n settle(onApprovalRequested({ toolName: event.toolName, toolCallId: event.toolCallId, args: event.args }));\n }\n return;\n }\n if (event.type === 'error') {\n lastError = event.error?.message;\n return;\n }\n if (event.type === 'tool_suspension_cancelled' && parked?.toolCallId === event.toolCallId) {\n parked = undefined;\n return;\n }\n if (event.type !== 'agent_end') return;\n const outcome = runOutcome(event.reason, parked, lastError);\n parked = undefined;\n lastError = undefined;\n if (!outcome) return;\n // Move the window now, so a run that starts before this report settles is judged on its own;\n // reports made up to the end, including its last millisecond, belong to the run that ended.\n const runSince = since;\n since = new Date(Date.now() + 1);\n settle(report(outcome, runSince));\n });\n}\n\n/**\n * Renew a kicked-off run's open-run entry until the run ends, like the\n * dispatcher's heartbeat, so no dispatcher starts the role again meanwhile.\n * `endReason` is how that run ended, once it has; `stop` is for a kickoff that fails.\n */\nexport function heartbeatOpenRunUntilEnd(\n session: RunEndCaptureSession,\n kickoffId: string,\n): { endReason: () => RunEndReason; stop: () => void } {\n let endReason: RunEndReason;\n let unsubscribe = () => {};\n const timer = setInterval(\n () => void heartbeatSessionOpenRun(session, kickoffId),\n Math.floor(FACTORY_OPEN_RUN_STALE_MS / 3),\n );\n timer.unref?.();\n let stopped = false;\n const stop = () => {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n unsubscribe();\n };\n unsubscribe = session.subscribe(event => {\n if (event.type !== 'agent_end') return;\n endReason ??= event.reason;\n stop();\n });\n return { endReason: () => endReason, stop };\n}\n\n/** `null` for an aborted run: someone stopped or steered it, which is not an outcome. */\nexport function runOutcome(\n reason: RunEndReason,\n parked: Pick<ParkedTool, 'toolName' | 'approval'> | undefined,\n errorMessage?: string,\n): FactoryRunOutcome | null {\n switch (reason) {\n case 'error': {\n const message = errorMessage?.trim();\n return {\n status: 'failed',\n reason: message\n ? `the run ended in error: ${message.slice(0, ERROR_MESSAGE_LENGTH)}`\n : 'the run ended in error.',\n };\n }\n case 'aborted':\n return null;\n case 'suspended':\n return {\n status: 'blocked',\n ...(parked ? { toolName: parked.toolName, ...(parked.approval ? { approval: true } : {}) } : {}),\n };\n default:\n return { status: 'done' };\n }\n}\n"],"mappings":";;;AA0BA,MAAM,uBAAuB;;;;;;;;AAS7B,SAAgB,yBACd,SACA,QACA,EAAE,wBAAyE,CAAC,GAChE;CACZ,IAAI;CACJ,IAAI;CACJ,IAAI,wBAAQ,IAAI,KAAK;CACrB,MAAM,UAAU,SACd,KAAK,KAAK,OAAM,UAAS;EACvB,QAAQ,KAAK,wDAAwD,KAAK;CAC5E,CAAC;CACH,OAAO,QAAQ,WAAU,UAAS;EAChC,IAAI,MAAM,SAAS,oBAAoB,MAAM,SAAS,0BAA0B;GAC9E,SAAS;IACP,UAAU,MAAM;IAChB,YAAY,MAAM;IAClB,UAAU,MAAM,SAAS;GAC3B;GAEA,IAAI,MAAM,SAAS,4BAA4B,qBAC7C,OAAO,oBAAoB;IAAE,UAAU,MAAM;IAAU,YAAY,MAAM;IAAY,MAAM,MAAM;GAAK,CAAC,CAAC;GAE1G;EACF;EACA,IAAI,MAAM,SAAS,SAAS;GAC1B,YAAY,MAAM,OAAO;GACzB;EACF;EACA,IAAI,MAAM,SAAS,+BAA+B,QAAQ,eAAe,MAAM,YAAY;GACzF,SAAS,KAAA;GACT;EACF;EACA,IAAI,MAAM,SAAS,aAAa;EAChC,MAAM,UAAU,WAAW,MAAM,QAAQ,QAAQ,SAAS;EAC1D,SAAS,KAAA;EACT,YAAY,KAAA;EACZ,IAAI,CAAC,SAAS;EAGd,MAAM,WAAW;EACjB,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC;EAC/B,OAAO,OAAO,SAAS,QAAQ,CAAC;CAClC,CAAC;AACH;;;;;;AAOA,SAAgB,yBACd,SACA,WACqD;CACrD,IAAI;CACJ,IAAI,oBAAoB,CAAC;CACzB,MAAM,QAAQ,kBACN,KAAK,wBAAwB,SAAS,SAAS,GACrD,KAAK,MAAM,4BAA4B,CAAC,CAC1C;CACA,MAAM,QAAQ;CACd,IAAI,UAAU;CACd,MAAM,aAAa;EACjB,IAAI,SAAS;EACb,UAAU;EACV,cAAc,KAAK;EACnB,YAAY;CACd;CACA,cAAc,QAAQ,WAAU,UAAS;EACvC,IAAI,MAAM,SAAS,aAAa;EAChC,cAAc,MAAM;EACpB,KAAK;CACP,CAAC;CACD,OAAO;EAAE,iBAAiB;EAAW;CAAK;AAC5C;;AAGA,SAAgB,WACd,QACA,QACA,cAC0B;CAC1B,QAAQ,QAAR;EACE,KAAK,SAAS;GACZ,MAAM,UAAU,cAAc,KAAK;GACnC,OAAO;IACL,QAAQ;IACR,QAAQ,UACJ,2BAA2B,QAAQ,MAAM,GAAG,oBAAoB,MAChE;GACN;EACF;EACA,KAAK,WACH,OAAO;EACT,KAAK,aACH,OAAO;GACL,QAAQ;GACR,GAAI,SAAS;IAAE,UAAU,OAAO;IAAU,GAAI,OAAO,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;GAAG,IAAI,CAAC;EAChG;EACF,SACE,OAAO,EAAE,QAAQ,OAAO;CAC5B;AACF"}
@@ -1,6 +1,6 @@
1
1
  import type { BoardRegistry } from '../boards/registry.js';
2
2
  import type { IntegrationTools } from '../integrations/base.js';
3
- import { type FactoryStartCoordinator } from '../rules/start-coordinator.js';
3
+ import type { FactoryStartCoordinator } from '../rules/start-coordinator.js';
4
4
  import type { SourceControlSessionLookup } from '../session/factory-session.js';
5
5
  import type { AuditRecorder } from '../storage/domains/audit/domain.js';
6
6
  import type { IntakeStorage } from '../storage/domains/intake/base.js';
@@ -1 +1 @@
1
- {"version":3,"file":"session-tools.d.ts","sourceRoot":"","sources":["../../src/tools/session-tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,QAAoD,CAAC;AAExF,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,GAAG,6BAA6B,CAAC,CAAC;IAC7F,YAAY,EAAE;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,aAAa,EAAE,oBAAoB,CAAA;KAAE,CAAC;IACxF,wEAAwE;IACxE,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAClD,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,MAAM,GACN,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,yBAAyB,CAC5B,CAAC;IACF,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAC1C,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC;CAC9E;AAwBD,qFAAqF;AACrF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,8BAA8B,GACnC,gBAAgB,CAuRlB"}
1
+ {"version":3,"file":"session-tools.d.ts","sourceRoot":"","sources":["../../src/tools/session-tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAElF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,sGAAsG;AACtG,eAAO,MAAM,sBAAsB,QAAoD,CAAC;AAExF,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,GAAG,6BAA6B,CAAC,CAAC;IAC7F,YAAY,EAAE;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,aAAa,EAAE,oBAAoB,CAAA;KAAE,CAAC;IACxF,wEAAwE;IACxE,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAClD,SAAS,EAAE,IAAI,CACb,gBAAgB,EACd,MAAM,GACN,eAAe,GACf,iBAAiB,GACjB,qBAAqB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,yBAAyB,CAC5B,CAAC;IACF,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAC1C,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC;CAC9E;AAwBD,qFAAqF;AACrF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,8BAA8B,GACnC,gBAAgB,CAuRlB"}
@@ -3,12 +3,12 @@ import "../boards/index.js";
3
3
  import { primeTenantCredentialsForTenant } from "../routes/tenant-credentials.js";
4
4
  import { effectiveModelScope } from "../storage/domains/projects/base.js";
5
5
  import { ensureFactorySourceSession, hydrateFactorySession, resolveFactorySessionModel } from "../session/factory-session.js";
6
+ import "./deliver.js";
7
+ import { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from "./read-tools.js";
8
+ import { USER_SESSION_BRANCH_PREFIX } from "../routes/source-control-sessions.js";
6
9
  import { resolveWorkItemRepository } from "../session/work-item-repository.js";
7
10
  import { FactoryStartTransitionError, WorkItemRepositoryError } from "../rules/start-coordinator.js";
8
- import { USER_SESSION_BRANCH_PREFIX } from "../routes/source-control-sessions.js";
9
11
  import { recordSessionRunStart } from "../session/run-audit.js";
10
- import "./deliver.js";
11
- import { findWorkItem, workItemNotFound, workItemNumber, workItemRefSchema } from "./read-tools.js";
12
12
  import { heartbeatOpenRunUntilEnd } from "./run-watch.js";
13
13
  import { RequestContext } from "@mastra/core/request-context";
14
14
  import { z } from "zod";