@memberjunction/ai-agent-manager 5.51.0 → 6.1.0-edge.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +88 -0
- package/LICENSE +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/workflow-agent-writer.d.ts +48 -0
- package/dist/workflow-agent-writer.d.ts.map +1 -0
- package/dist/workflow-agent-writer.js +95 -0
- package/dist/workflow-agent-writer.js.map +1 -0
- package/package.json +17 -17
- package/src/index.ts +1 -0
- package/src/workflow-agent-writer.ts +115 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @memberjunction/ai-agent-manager
|
|
2
2
|
|
|
3
|
+
## 6.1.0-edge.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 394d276: Phase 6 (Track E) — **`WorkflowSpec`: one object binding WHAT runs to WHEN it runs.**
|
|
8
|
+
|
|
9
|
+
`TaskGraphSpec` answered _what_ a workflow does; the scheduling and entity-action substrates answered _when_ something fires. Nothing expressed both at once, so "a workflow" was not a thing anyone could hand over — it was a graph plus a separately-configured trigger that only a human knew were related.
|
|
10
|
+
|
|
11
|
+
**`graph` is `TaskGraphSpec` verbatim, not a copy.** That is why this composes rather than translates: a graph authored on the canvas, emitted by an agent, or promoted from a past run is _already_ this shape. A parallel graph type would have re-created the drift Phase 4 spent itself removing.
|
|
12
|
+
|
|
13
|
+
**No new storage, and that is the design.** There is no `Workflow` table. A workflow's WHAT is a Flow agent; its WHEN is a Scheduled Job. `WorkflowSpecSync` **reconciles** those, following the pattern `MJRecordProcessEntityServer.Save()` already proved — resolve the type, find the rows this definition owns, upsert or disable. Inventing a `Workflow` row would create a second definition of "a scheduled thing" and give the scheduler two masters that can disagree.
|
|
14
|
+
|
|
15
|
+
Rows are owned by a marker inside their own `Configuration`, not by name, so **renaming a workflow cannot orphan its schedule** and leave a second one firing beside the new row. A trigger the spec no longer names is **disabled, not deleted** — the row carries run counts, last-run and next-run, which are the only record it ever fired.
|
|
16
|
+
|
|
17
|
+
**Order is load-bearing.** The agent persists _before_ triggers reconcile, because a Scheduled Job needs its ID to point at. Reversed, you get a job referencing an agent that does not exist — a schedule that fires forever and does nothing, with no error anyone sees. Validation runs before either, so a rejected save leaves no orphan agent behind.
|
|
18
|
+
|
|
19
|
+
**Two operations, because drafting and committing are different acts.** `Workflow.Validate` writes nothing, so an agent can iterate a draft before anything reaches the scheduler — the draft-then-confirm shape dry-run and Plan Mode established. `Workflow.Save` commits. Both run the identical validator, so a workflow that validates cannot be rejected on save for a different reason. Together they close the "agents cannot schedule anything" hole: today `Create Scheduled Job` cannot even set `Configuration`.
|
|
20
|
+
|
|
21
|
+
**Agent persistence crosses a seam.** `AgentSpecSync` is the one place that writes an agent; importing it into the execution substrate would invert the dependency, so the host registers a writer instead. A host without one gets an honest failure rather than a half-saved workflow. The writer reuses Phase 4's `ConvertTaskGraphToAgentSpec` unchanged — "save a runtime graph as a workflow" and "persist a workflow's graph" turn out to be the same operation, which is the practical payoff of the convergence.
|
|
22
|
+
|
|
23
|
+
**A discovery worth recording:** `AgentScheduledJobDriver` has existed since the scheduling engine shipped, and `ScheduledJobType.DriverClass` is UNIQUE — so the `Agent` job type was already seeded. The substrate for scheduling an agent was there all along; only the authoring surface was missing. TG12 now pins that seed, because without it a scheduled workflow throws at the moment a user is least able to interpret it.
|
|
24
|
+
|
|
25
|
+
**Draft is the default status**, not Active. Every authoring surface — the canvas, a chat card, an agent's MCP call — produces something the author has not yet watched run against real data.
|
|
26
|
+
|
|
27
|
+
**Entity-change triggers reconcile too.** My first pass deferred these to Track D on the belief that entity-action invocation was not wired. AN-BC challenged that and was right: `HandleEntityActions` has fired entity actions from the save pipeline all along — validate, before/after save, before/after delete — and `Execute Agent` exists as the dispatch target, written for exactly this. Nothing was missing but the **binding row**. `WorkflowSpecSync` now creates the three rows that express "when an Invoice is updated, run Execute Agent with this agent": the `EntityAction`, the `EntityActionInvocation`, and an `EntityActionParam` carrying the agent. Idempotent by lookup rather than delete-and-recreate, because re-saving a workflow must not detach and re-attach a live trigger — a change landing in that window would be missed.
|
|
28
|
+
|
|
29
|
+
40 new unit tests (29 validator, 11 sync) plus integration checks TG11 and TG12. IT71 runs 12/12.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [394d276]
|
|
34
|
+
- Updated dependencies [394d276]
|
|
35
|
+
- Updated dependencies [394d276]
|
|
36
|
+
- Updated dependencies [394d276]
|
|
37
|
+
- Updated dependencies [394d276]
|
|
38
|
+
- Updated dependencies [394d276]
|
|
39
|
+
- Updated dependencies [394d276]
|
|
40
|
+
- Updated dependencies [394d276]
|
|
41
|
+
- Updated dependencies [394d276]
|
|
42
|
+
- Updated dependencies [394d276]
|
|
43
|
+
- Updated dependencies [394d276]
|
|
44
|
+
- Updated dependencies [394d276]
|
|
45
|
+
- Updated dependencies [394d276]
|
|
46
|
+
- Updated dependencies [394d276]
|
|
47
|
+
- @memberjunction/core@6.1.0-edge.1
|
|
48
|
+
- @memberjunction/core-entities@6.1.0-edge.1
|
|
49
|
+
- @memberjunction/ai-agents@6.1.0-edge.1
|
|
50
|
+
- @memberjunction/ai-core-plus@6.1.0-edge.1
|
|
51
|
+
- @memberjunction/aiengine@6.1.0-edge.1
|
|
52
|
+
- @memberjunction/ai-engine-base@6.1.0-edge.1
|
|
53
|
+
- @memberjunction/templates@6.1.0-edge.1
|
|
54
|
+
- @memberjunction/global@6.1.0-edge.1
|
|
55
|
+
|
|
56
|
+
## 6.1.0-edge.0
|
|
57
|
+
|
|
58
|
+
### Patch Changes
|
|
59
|
+
|
|
60
|
+
- Updated dependencies [2412415]
|
|
61
|
+
- Updated dependencies [9699d0e]
|
|
62
|
+
- Updated dependencies [052b4c7]
|
|
63
|
+
- Updated dependencies [9a905e8]
|
|
64
|
+
- Updated dependencies [841e6ea]
|
|
65
|
+
- Updated dependencies [1d88e00]
|
|
66
|
+
- Updated dependencies [27e4d09]
|
|
67
|
+
- Updated dependencies [1100077]
|
|
68
|
+
- @memberjunction/core-entities@6.1.0-edge.0
|
|
69
|
+
- @memberjunction/core@6.1.0-edge.0
|
|
70
|
+
- @memberjunction/aiengine@6.1.0-edge.0
|
|
71
|
+
- @memberjunction/ai-agents@6.1.0-edge.0
|
|
72
|
+
- @memberjunction/ai-engine-base@6.1.0-edge.0
|
|
73
|
+
- @memberjunction/ai-core-plus@6.1.0-edge.0
|
|
74
|
+
- @memberjunction/templates@6.1.0-edge.0
|
|
75
|
+
- @memberjunction/global@6.1.0-edge.0
|
|
76
|
+
|
|
77
|
+
## 6.0.0
|
|
78
|
+
|
|
79
|
+
### Patch Changes
|
|
80
|
+
|
|
81
|
+
- Updated dependencies [a2670a9]
|
|
82
|
+
- @memberjunction/core@6.0.0
|
|
83
|
+
- @memberjunction/ai-agents@6.0.0
|
|
84
|
+
- @memberjunction/ai-engine-base@6.0.0
|
|
85
|
+
- @memberjunction/ai-core-plus@6.0.0
|
|
86
|
+
- @memberjunction/aiengine@6.0.0
|
|
87
|
+
- @memberjunction/core-entities@6.0.0
|
|
88
|
+
- @memberjunction/templates@6.0.0
|
|
89
|
+
- @memberjunction/global@6.0.0
|
|
90
|
+
|
|
3
91
|
## 5.51.0
|
|
4
92
|
|
|
5
93
|
### Patch Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 MemberJunction
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,kCAAkC,CAAC;AAEjD,cAAc,mBAAmB,CAAC;AAGlC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,kCAAkC,CAAC;AAEjD,cAAc,mBAAmB,CAAC;AAGlC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC;AACjD,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,5 @@ export * from './agent-spec-sync.js';
|
|
|
10
10
|
export * from './agents/architect-agent.js';
|
|
11
11
|
export * from './agents/builder-agent.js';
|
|
12
12
|
export * from './agents/planning-designer-agent.js';
|
|
13
|
+
export * from './workflow-agent-writer.js';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAwB;AACxB,cAAc,kCAAkC,CAAC;AAEjD,cAAc,mBAAmB,CAAC;AAElC,+BAA+B;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAwB;AACxB,cAAc,kCAAkC,CAAC;AAEjD,cAAc,mBAAmB,CAAC;AAElC,+BAA+B;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC;AACjD,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Persists a workflow's graph as a Flow agent, via `AgentSpecSync`.
|
|
3
|
+
*
|
|
4
|
+
* This is the host side of the seam `WorkflowSpecSync` declares. It lives here, in the agent-manager,
|
|
5
|
+
* because `AgentSpecSync` is the **one place that writes an agent** — it already owns atomic
|
|
6
|
+
* multi-entity writes and the mutation audit, and a second writer would be a second set of rules
|
|
7
|
+
* about what a valid agent record is.
|
|
8
|
+
*
|
|
9
|
+
* The conversion itself is `ConvertTaskGraphToAgentSpec`, shipped in Phase 4. That it is reusable
|
|
10
|
+
* here without modification is the practical payoff of the convergence: "save a runtime graph as a
|
|
11
|
+
* workflow" and "persist a workflow's graph" turn out to be the same operation, because after Phase 4
|
|
12
|
+
* they are the same model.
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/ai-agent-manager
|
|
15
|
+
*/
|
|
16
|
+
import { UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
17
|
+
import { type WorkflowSpec } from '@memberjunction/ai-core-plus';
|
|
18
|
+
/** ClassFactory key under which hosts resolve the writer. */
|
|
19
|
+
export declare const WORKFLOW_AGENT_WRITER_KEY = "WorkflowAgentWriter";
|
|
20
|
+
/** Base so hosts can resolve an implementation without importing this package directly. */
|
|
21
|
+
export declare abstract class WorkflowAgentWriterBase {
|
|
22
|
+
abstract PersistFlowAgent(spec: WorkflowSpec, context: {
|
|
23
|
+
ContextUser: UserInfo;
|
|
24
|
+
Provider: IMetadataProvider;
|
|
25
|
+
}): Promise<string>;
|
|
26
|
+
}
|
|
27
|
+
export declare class WorkflowAgentWriter extends WorkflowAgentWriterBase {
|
|
28
|
+
/**
|
|
29
|
+
* Converts the workflow's graph to a Flow `AgentSpec` and persists it.
|
|
30
|
+
*
|
|
31
|
+
* **Losses are logged, not swallowed.** The converter reports what it could not carry across —
|
|
32
|
+
* human steps, unresolvable agents, run-specific inputs. Dropping that silently would hand
|
|
33
|
+
* someone a workflow missing an approval they believed they had saved, and they would only find
|
|
34
|
+
* out by running it. The save still proceeds: a workflow that is 90% right and says so is more
|
|
35
|
+
* useful than a refusal, and the losses are surfaced to the caller by the operation above.
|
|
36
|
+
*/
|
|
37
|
+
PersistFlowAgent(spec: WorkflowSpec, context: {
|
|
38
|
+
ContextUser: UserInfo;
|
|
39
|
+
Provider: IMetadataProvider;
|
|
40
|
+
}): Promise<string>;
|
|
41
|
+
/** Resolves the Flow agent type, so the persisted agent is a Flow rather than a Loop. */
|
|
42
|
+
private resolveFlowAgentTypeID;
|
|
43
|
+
/** Name → ID for every agent, lowercased so a spec's human-entered name still resolves. */
|
|
44
|
+
private buildAgentNameIndex;
|
|
45
|
+
}
|
|
46
|
+
/** Prevents tree-shaking of the registered writer. */
|
|
47
|
+
export declare function LoadWorkflowAgentWriter(): void;
|
|
48
|
+
//# sourceMappingURL=workflow-agent-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-agent-writer.d.ts","sourceRoot":"","sources":["../src/workflow-agent-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAqB,QAAQ,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAGH,KAAK,YAAY,EACpB,MAAM,8BAA8B,CAAC;AAGtC,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D,2FAA2F;AAC3F,8BAAsB,uBAAuB;aACzB,gBAAgB,CAC5B,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE;QAAE,WAAW,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,iBAAiB,CAAA;KAAE,GAChE,OAAO,CAAC,MAAM,CAAC;CACrB;AAED,qBACa,mBAAoB,SAAQ,uBAAuB;IAC5D;;;;;;;;OAQG;IACU,gBAAgB,CACzB,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE;QAAE,WAAW,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,iBAAiB,CAAA;KAAE,GAChE,OAAO,CAAC,MAAM,CAAC;IAuClB,yFAAyF;YAC3E,sBAAsB;IAUpC,2FAA2F;YAC7E,mBAAmB;CASpC;AAED,sDAAsD;AACtD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview Persists a workflow's graph as a Flow agent, via `AgentSpecSync`.
|
|
9
|
+
*
|
|
10
|
+
* This is the host side of the seam `WorkflowSpecSync` declares. It lives here, in the agent-manager,
|
|
11
|
+
* because `AgentSpecSync` is the **one place that writes an agent** — it already owns atomic
|
|
12
|
+
* multi-entity writes and the mutation audit, and a second writer would be a second set of rules
|
|
13
|
+
* about what a valid agent record is.
|
|
14
|
+
*
|
|
15
|
+
* The conversion itself is `ConvertTaskGraphToAgentSpec`, shipped in Phase 4. That it is reusable
|
|
16
|
+
* here without modification is the practical payoff of the convergence: "save a runtime graph as a
|
|
17
|
+
* workflow" and "persist a workflow's graph" turn out to be the same operation, because after Phase 4
|
|
18
|
+
* they are the same model.
|
|
19
|
+
*
|
|
20
|
+
* @module @memberjunction/ai-agent-manager
|
|
21
|
+
*/
|
|
22
|
+
import { LogError, RunView } from '@memberjunction/core';
|
|
23
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
24
|
+
import { ConvertTaskGraphToAgentSpec, FormatSaveAsWorkflowLosses, } from '@memberjunction/ai-core-plus';
|
|
25
|
+
import { AgentSpecSync } from './agent-spec-sync.js';
|
|
26
|
+
/** ClassFactory key under which hosts resolve the writer. */
|
|
27
|
+
export const WORKFLOW_AGENT_WRITER_KEY = 'WorkflowAgentWriter';
|
|
28
|
+
/** Base so hosts can resolve an implementation without importing this package directly. */
|
|
29
|
+
export class WorkflowAgentWriterBase {
|
|
30
|
+
}
|
|
31
|
+
let WorkflowAgentWriter = class WorkflowAgentWriter extends WorkflowAgentWriterBase {
|
|
32
|
+
/**
|
|
33
|
+
* Converts the workflow's graph to a Flow `AgentSpec` and persists it.
|
|
34
|
+
*
|
|
35
|
+
* **Losses are logged, not swallowed.** The converter reports what it could not carry across —
|
|
36
|
+
* human steps, unresolvable agents, run-specific inputs. Dropping that silently would hand
|
|
37
|
+
* someone a workflow missing an approval they believed they had saved, and they would only find
|
|
38
|
+
* out by running it. The save still proceeds: a workflow that is 90% right and says so is more
|
|
39
|
+
* useful than a refusal, and the losses are surfaced to the caller by the operation above.
|
|
40
|
+
*/
|
|
41
|
+
async PersistFlowAgent(spec, context) {
|
|
42
|
+
const flowTypeID = await this.resolveFlowAgentTypeID(context);
|
|
43
|
+
const agentIDsByName = await this.buildAgentNameIndex(context);
|
|
44
|
+
let counter = 0;
|
|
45
|
+
const result = ConvertTaskGraphToAgentSpec(spec.graph, {
|
|
46
|
+
// Deterministic within one call, and unique because AgentSpecSync assigns real keys on
|
|
47
|
+
// insert — these only have to correlate steps to paths inside this payload.
|
|
48
|
+
AgentID: `workflow-${Date.now()}-${counter}`,
|
|
49
|
+
NextID: () => `wf-node-${++counter}`,
|
|
50
|
+
ResolveAgentID: (name) => agentIDsByName.get(name.trim().toLowerCase()) ?? null,
|
|
51
|
+
FlowAgentTypeID: flowTypeID,
|
|
52
|
+
Name: spec.name,
|
|
53
|
+
});
|
|
54
|
+
if (!result.Success || !result.Spec) {
|
|
55
|
+
throw new Error(result.ErrorMessage ?? 'The workflow graph could not be converted to an agent.');
|
|
56
|
+
}
|
|
57
|
+
if (result.Losses.length > 0) {
|
|
58
|
+
LogError(`[WorkflowAgentWriter] "${spec.name}" saved with losses:\n${FormatSaveAsWorkflowLosses(result.Losses)}`);
|
|
59
|
+
}
|
|
60
|
+
result.Spec.Description = spec.description ?? result.Spec.Description;
|
|
61
|
+
// A Draft or Paused workflow persists as an Inactive agent, so nothing can invoke it before
|
|
62
|
+
// its author has turned it on — the schedule side makes the same choice for the same reason.
|
|
63
|
+
result.Spec.Status = spec.status === 'Active' ? 'Active' : 'Inactive';
|
|
64
|
+
const sync = AgentSpecSync.FromRawSpec(result.Spec, context.ContextUser, context.Provider);
|
|
65
|
+
// AgentSpecSyncResult uses camelCase — it predates the PascalCase-public convention and is
|
|
66
|
+
// consumed by MCP tools, so it is left alone rather than renamed under this change.
|
|
67
|
+
const saved = await sync.SaveToDatabase();
|
|
68
|
+
if (!saved?.success) {
|
|
69
|
+
throw new Error(`Could not save the workflow's agent "${spec.name}".`);
|
|
70
|
+
}
|
|
71
|
+
return saved.agentId;
|
|
72
|
+
}
|
|
73
|
+
/** Resolves the Flow agent type, so the persisted agent is a Flow rather than a Loop. */
|
|
74
|
+
async resolveFlowAgentTypeID(context) {
|
|
75
|
+
const result = await RunView.FromMetadataProvider(context.Provider).RunView({ EntityName: 'MJ: AI Agent Types', ExtraFilter: `Name='Flow'`, Fields: ['ID'], ResultType: 'simple' }, context.ContextUser);
|
|
76
|
+
const id = result.Results?.[0]?.ID;
|
|
77
|
+
if (!id)
|
|
78
|
+
throw new Error("The 'Flow' agent type was not found — has the metadata seed been pushed?");
|
|
79
|
+
return id;
|
|
80
|
+
}
|
|
81
|
+
/** Name → ID for every agent, lowercased so a spec's human-entered name still resolves. */
|
|
82
|
+
async buildAgentNameIndex(context) {
|
|
83
|
+
const result = await RunView.FromMetadataProvider(context.Provider).RunView({ EntityName: 'MJ: AI Agents', Fields: ['ID', 'Name'], ResultType: 'simple' }, context.ContextUser);
|
|
84
|
+
return new Map((result.Results ?? []).map((a) => [a.Name.trim().toLowerCase(), a.ID]));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
WorkflowAgentWriter = __decorate([
|
|
88
|
+
RegisterClass(WorkflowAgentWriterBase, WORKFLOW_AGENT_WRITER_KEY)
|
|
89
|
+
], WorkflowAgentWriter);
|
|
90
|
+
export { WorkflowAgentWriter };
|
|
91
|
+
/** Prevents tree-shaking of the registered writer. */
|
|
92
|
+
export function LoadWorkflowAgentWriter() {
|
|
93
|
+
void WorkflowAgentWriter;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=workflow-agent-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-agent-writer.js","sourceRoot":"","sources":["../src/workflow-agent-writer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ai-agent-manager",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.1.0-edge.1",
|
|
5
5
|
"description": "Core interfaces and types for the MemberJunction Agent Manager system",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsc && tsc-alias -f",
|
|
10
|
-
"watch": "tsc -w",
|
|
11
|
-
"clean": "rimraf dist",
|
|
12
|
-
"test": "vitest run",
|
|
13
|
-
"test:watch": "vitest"
|
|
14
|
-
},
|
|
15
8
|
"keywords": [
|
|
16
9
|
"memberjunction",
|
|
17
10
|
"agent-manager",
|
|
@@ -21,14 +14,14 @@
|
|
|
21
14
|
"author": "MemberJunction",
|
|
22
15
|
"license": "ISC",
|
|
23
16
|
"dependencies": {
|
|
24
|
-
"@memberjunction/ai-agents": "
|
|
25
|
-
"@memberjunction/ai-core-plus": "
|
|
26
|
-
"@memberjunction/ai-engine-base": "
|
|
27
|
-
"@memberjunction/aiengine": "
|
|
28
|
-
"@memberjunction/core": "
|
|
29
|
-
"@memberjunction/core-entities": "
|
|
30
|
-
"@memberjunction/global": "
|
|
31
|
-
"@memberjunction/templates": "
|
|
17
|
+
"@memberjunction/ai-agents": "6.1.0-edge.1",
|
|
18
|
+
"@memberjunction/ai-core-plus": "6.1.0-edge.1",
|
|
19
|
+
"@memberjunction/ai-engine-base": "6.1.0-edge.1",
|
|
20
|
+
"@memberjunction/aiengine": "6.1.0-edge.1",
|
|
21
|
+
"@memberjunction/core": "6.1.0-edge.1",
|
|
22
|
+
"@memberjunction/core-entities": "6.1.0-edge.1",
|
|
23
|
+
"@memberjunction/global": "6.1.0-edge.1",
|
|
24
|
+
"@memberjunction/templates": "6.1.0-edge.1"
|
|
32
25
|
},
|
|
33
26
|
"devDependencies": {
|
|
34
27
|
"@types/node": "^24.10.11",
|
|
@@ -38,5 +31,12 @@
|
|
|
38
31
|
"repository": {
|
|
39
32
|
"type": "git",
|
|
40
33
|
"url": "https://github.com/MemberJunction/MJ"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc && tsc-alias -f",
|
|
37
|
+
"watch": "tsc -w",
|
|
38
|
+
"clean": "rimraf dist",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest"
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Persists a workflow's graph as a Flow agent, via `AgentSpecSync`.
|
|
3
|
+
*
|
|
4
|
+
* This is the host side of the seam `WorkflowSpecSync` declares. It lives here, in the agent-manager,
|
|
5
|
+
* because `AgentSpecSync` is the **one place that writes an agent** — it already owns atomic
|
|
6
|
+
* multi-entity writes and the mutation audit, and a second writer would be a second set of rules
|
|
7
|
+
* about what a valid agent record is.
|
|
8
|
+
*
|
|
9
|
+
* The conversion itself is `ConvertTaskGraphToAgentSpec`, shipped in Phase 4. That it is reusable
|
|
10
|
+
* here without modification is the practical payoff of the convergence: "save a runtime graph as a
|
|
11
|
+
* workflow" and "persist a workflow's graph" turn out to be the same operation, because after Phase 4
|
|
12
|
+
* they are the same model.
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/ai-agent-manager
|
|
15
|
+
*/
|
|
16
|
+
import { LogError, RunView, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
17
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
18
|
+
import {
|
|
19
|
+
ConvertTaskGraphToAgentSpec,
|
|
20
|
+
FormatSaveAsWorkflowLosses,
|
|
21
|
+
type WorkflowSpec,
|
|
22
|
+
} from '@memberjunction/ai-core-plus';
|
|
23
|
+
import { AgentSpecSync } from './agent-spec-sync';
|
|
24
|
+
|
|
25
|
+
/** ClassFactory key under which hosts resolve the writer. */
|
|
26
|
+
export const WORKFLOW_AGENT_WRITER_KEY = 'WorkflowAgentWriter';
|
|
27
|
+
|
|
28
|
+
/** Base so hosts can resolve an implementation without importing this package directly. */
|
|
29
|
+
export abstract class WorkflowAgentWriterBase {
|
|
30
|
+
public abstract PersistFlowAgent(
|
|
31
|
+
spec: WorkflowSpec,
|
|
32
|
+
context: { ContextUser: UserInfo; Provider: IMetadataProvider },
|
|
33
|
+
): Promise<string>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@RegisterClass(WorkflowAgentWriterBase, WORKFLOW_AGENT_WRITER_KEY)
|
|
37
|
+
export class WorkflowAgentWriter extends WorkflowAgentWriterBase {
|
|
38
|
+
/**
|
|
39
|
+
* Converts the workflow's graph to a Flow `AgentSpec` and persists it.
|
|
40
|
+
*
|
|
41
|
+
* **Losses are logged, not swallowed.** The converter reports what it could not carry across —
|
|
42
|
+
* human steps, unresolvable agents, run-specific inputs. Dropping that silently would hand
|
|
43
|
+
* someone a workflow missing an approval they believed they had saved, and they would only find
|
|
44
|
+
* out by running it. The save still proceeds: a workflow that is 90% right and says so is more
|
|
45
|
+
* useful than a refusal, and the losses are surfaced to the caller by the operation above.
|
|
46
|
+
*/
|
|
47
|
+
public async PersistFlowAgent(
|
|
48
|
+
spec: WorkflowSpec,
|
|
49
|
+
context: { ContextUser: UserInfo; Provider: IMetadataProvider },
|
|
50
|
+
): Promise<string> {
|
|
51
|
+
const flowTypeID = await this.resolveFlowAgentTypeID(context);
|
|
52
|
+
const agentIDsByName = await this.buildAgentNameIndex(context);
|
|
53
|
+
|
|
54
|
+
let counter = 0;
|
|
55
|
+
const result = ConvertTaskGraphToAgentSpec(spec.graph, {
|
|
56
|
+
// Deterministic within one call, and unique because AgentSpecSync assigns real keys on
|
|
57
|
+
// insert — these only have to correlate steps to paths inside this payload.
|
|
58
|
+
AgentID: `workflow-${Date.now()}-${counter}`,
|
|
59
|
+
NextID: () => `wf-node-${++counter}`,
|
|
60
|
+
ResolveAgentID: (name) => agentIDsByName.get(name.trim().toLowerCase()) ?? null,
|
|
61
|
+
FlowAgentTypeID: flowTypeID,
|
|
62
|
+
Name: spec.name,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!result.Success || !result.Spec) {
|
|
66
|
+
throw new Error(result.ErrorMessage ?? 'The workflow graph could not be converted to an agent.');
|
|
67
|
+
}
|
|
68
|
+
if (result.Losses.length > 0) {
|
|
69
|
+
LogError(
|
|
70
|
+
`[WorkflowAgentWriter] "${spec.name}" saved with losses:\n${FormatSaveAsWorkflowLosses(result.Losses)}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
result.Spec.Description = spec.description ?? result.Spec.Description;
|
|
75
|
+
// A Draft or Paused workflow persists as an Inactive agent, so nothing can invoke it before
|
|
76
|
+
// its author has turned it on — the schedule side makes the same choice for the same reason.
|
|
77
|
+
result.Spec.Status = spec.status === 'Active' ? 'Active' : 'Inactive';
|
|
78
|
+
|
|
79
|
+
const sync = AgentSpecSync.FromRawSpec(result.Spec, context.ContextUser, context.Provider);
|
|
80
|
+
// AgentSpecSyncResult uses camelCase — it predates the PascalCase-public convention and is
|
|
81
|
+
// consumed by MCP tools, so it is left alone rather than renamed under this change.
|
|
82
|
+
const saved = await sync.SaveToDatabase();
|
|
83
|
+
if (!saved?.success) {
|
|
84
|
+
throw new Error(`Could not save the workflow's agent "${spec.name}".`);
|
|
85
|
+
}
|
|
86
|
+
return saved.agentId;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Resolves the Flow agent type, so the persisted agent is a Flow rather than a Loop. */
|
|
90
|
+
private async resolveFlowAgentTypeID(context: { ContextUser: UserInfo; Provider: IMetadataProvider }): Promise<string> {
|
|
91
|
+
const result = await RunView.FromMetadataProvider(context.Provider).RunView<{ ID: string }>(
|
|
92
|
+
{ EntityName: 'MJ: AI Agent Types', ExtraFilter: `Name='Flow'`, Fields: ['ID'], ResultType: 'simple' },
|
|
93
|
+
context.ContextUser,
|
|
94
|
+
);
|
|
95
|
+
const id = result.Results?.[0]?.ID;
|
|
96
|
+
if (!id) throw new Error("The 'Flow' agent type was not found — has the metadata seed been pushed?");
|
|
97
|
+
return id;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Name → ID for every agent, lowercased so a spec's human-entered name still resolves. */
|
|
101
|
+
private async buildAgentNameIndex(
|
|
102
|
+
context: { ContextUser: UserInfo; Provider: IMetadataProvider },
|
|
103
|
+
): Promise<Map<string, string>> {
|
|
104
|
+
const result = await RunView.FromMetadataProvider(context.Provider).RunView<{ ID: string; Name: string }>(
|
|
105
|
+
{ EntityName: 'MJ: AI Agents', Fields: ['ID', 'Name'], ResultType: 'simple' },
|
|
106
|
+
context.ContextUser,
|
|
107
|
+
);
|
|
108
|
+
return new Map((result.Results ?? []).map((a) => [a.Name.trim().toLowerCase(), a.ID]));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Prevents tree-shaking of the registered writer. */
|
|
113
|
+
export function LoadWorkflowAgentWriter(): void {
|
|
114
|
+
void WorkflowAgentWriter;
|
|
115
|
+
}
|