@codemation/core-nodes 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/LICENSE +37 -1
- package/dist/index.cjs +301 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +904 -692
- package/dist/index.d.ts +904 -692
- package/dist/index.js +302 -9
- package/dist/index.js.map +1 -1
- package/dist/metadata.json +7 -7
- package/package.json +3 -3
- package/src/chatModels/CodemationChatModelConfig.ts +1 -1
- package/src/index.ts +1 -0
- package/src/nodes/AIAgentNode.ts +159 -7
- package/src/nodes/AgentLoopCheckpoint.types.ts +23 -0
- package/src/nodes/AgentToolExecutionCoordinator.ts +91 -2
- package/src/nodes/InboxApprovalNode.types.ts +87 -0
- package/src/nodes/aiAgentSupport.types.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @codemation/core-nodes
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#167](https://github.com/MadeRelevant/codemation/pull/167) [`3044474`](https://github.com/MadeRelevant/codemation/commit/3044474495525490735510ff74500b53761284b6) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(hitl): Human-in-the-Loop — engine suspend/resume, inbox approval node + channels (local + control-plane), agent-as-tool, decision/timeout handling, inbox decision UX (toast + node status icons + "waiting for approval"), plus the consolidated dev/canvas/host fixes shipped alongside.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`3044474`](https://github.com/MadeRelevant/codemation/commit/3044474495525490735510ff74500b53761284b6)]:
|
|
12
|
+
- @codemation/core@0.12.0
|
|
13
|
+
|
|
14
|
+
## 0.8.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#157](https://github.com/MadeRelevant/codemation/pull/157) [`3025b86`](https://github.com/MadeRelevant/codemation/commit/3025b8685b0d7ad60c506b5a0f21967e681a25ea) Thanks [@cblokland90](https://github.com/cblokland90)! - Shrink workspace-host Docker image by decoupling CLI from next-host at runtime.
|
|
19
|
+
|
|
20
|
+
`@codemation/cli`: demote `@codemation/next-host` from `dependencies` to `devDependencies`. The CLI's
|
|
21
|
+
non-headless serve path resolves the next-host package at runtime via `require.resolve()`; the
|
|
22
|
+
headless path (used by workspace-host pods) never touches it. Consumers that install `@codemation/cli`
|
|
23
|
+
from the registry and need the UI shell must add `@codemation/next-host` as a direct dependency.
|
|
24
|
+
|
|
25
|
+
`@codemation/core-nodes`: demote `lucide-react` from `dependencies` to `devDependencies`. The package
|
|
26
|
+
only references lucide icon names as strings (e.g. `"lucide:bot"`); it never imports the react library
|
|
27
|
+
at runtime. This removes ~46 MB from runtime installs of `@codemation/core-nodes`.
|
|
28
|
+
|
|
29
|
+
`@codemation/host`: promote `execa` and `dotenv` from `devDependencies` to `dependencies`. Both are
|
|
30
|
+
required at Dockerfile build time by `scripts/generate-prisma-clients.mjs` (imports `execaSync` from
|
|
31
|
+
`execa`) and `prisma.config.ts` (imports `dotenv/config`). These files run during `prisma:generate`
|
|
32
|
+
which executes in the production builder stage with `--prod` install (no devDeps available).
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [[`e0933eb`](https://github.com/MadeRelevant/codemation/commit/e0933ebc51806a9593f94758860c591b8346a7a5)]:
|
|
35
|
+
- @codemation/core@0.11.1
|
|
36
|
+
|
|
3
37
|
## 0.8.0
|
|
4
38
|
|
|
5
39
|
### Minor Changes
|
package/LICENSE
CHANGED
|
@@ -1 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
Codemation Pre-Stable License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Made Relevant B.V. All rights reserved.
|
|
4
|
+
|
|
5
|
+
1. Definitions
|
|
6
|
+
|
|
7
|
+
"Software" means the Codemation source code, documentation, and artifacts in this repository and any published npm packages in the Codemation monorepo.
|
|
8
|
+
|
|
9
|
+
"Stable Version" means the first published release of the package `@codemation/core` on the public npm registry with version 1.0.0 or higher.
|
|
10
|
+
|
|
11
|
+
2. Permitted use (before Stable Version)
|
|
12
|
+
|
|
13
|
+
Until a Stable Version exists, you may use, copy, modify, and distribute the Software only for non-commercial purposes, including personal learning, research, evaluation, and internal use within your organization that does not charge third parties for access to the Software or a product or service whose primary value is the Software.
|
|
14
|
+
|
|
15
|
+
3. Restrictions (before Stable Version)
|
|
16
|
+
|
|
17
|
+
Until a Stable Version exists, you must not:
|
|
18
|
+
|
|
19
|
+
a) Sell, rent, lease, or sublicense the Software or a derivative work for a fee;
|
|
20
|
+
|
|
21
|
+
b) Offer the Software or a derivative work as part of a paid product or service (including hosting, support, or consulting) where the Software is a material part of the offering;
|
|
22
|
+
|
|
23
|
+
c) Use the Software or a derivative work primarily to generate revenue or commercial advantage for you or others.
|
|
24
|
+
|
|
25
|
+
These restrictions apply to all versions published before a Stable Version, even if a later Stable Version is released under different terms.
|
|
26
|
+
|
|
27
|
+
4. After Stable Version
|
|
28
|
+
|
|
29
|
+
The maintainers may publish a Stable Version under different license terms. If they do, those terms apply only to that Stable Version and subsequent releases they designate; they do not automatically apply to earlier pre-stable versions.
|
|
30
|
+
|
|
31
|
+
5. No warranty
|
|
32
|
+
|
|
33
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
34
|
+
|
|
35
|
+
6. Third-party components
|
|
36
|
+
|
|
37
|
+
The Software may include third-party components under their own licenses. Those licenses govern those components.
|
package/dist/index.cjs
CHANGED
|
@@ -1309,6 +1309,7 @@ const string$1 = (params) => {
|
|
|
1309
1309
|
};
|
|
1310
1310
|
const integer = /^-?\d+$/;
|
|
1311
1311
|
const number$1 = /^-?\d+(?:\.\d+)?$/;
|
|
1312
|
+
const boolean$1 = /^(?:true|false)$/i;
|
|
1312
1313
|
const lowercase = /^[^A-Z]*$/;
|
|
1313
1314
|
const uppercase = /^[^a-z]*$/;
|
|
1314
1315
|
|
|
@@ -2087,6 +2088,24 @@ const $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst,
|
|
|
2087
2088
|
$ZodCheckNumberFormat.init(inst, def);
|
|
2088
2089
|
$ZodNumber.init(inst, def);
|
|
2089
2090
|
});
|
|
2091
|
+
const $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
2092
|
+
$ZodType.init(inst, def);
|
|
2093
|
+
inst._zod.pattern = boolean$1;
|
|
2094
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
2095
|
+
if (def.coerce) try {
|
|
2096
|
+
payload.value = Boolean(payload.value);
|
|
2097
|
+
} catch (_) {}
|
|
2098
|
+
const input = payload.value;
|
|
2099
|
+
if (typeof input === "boolean") return payload;
|
|
2100
|
+
payload.issues.push({
|
|
2101
|
+
expected: "boolean",
|
|
2102
|
+
code: "invalid_type",
|
|
2103
|
+
input,
|
|
2104
|
+
inst
|
|
2105
|
+
});
|
|
2106
|
+
return payload;
|
|
2107
|
+
};
|
|
2108
|
+
});
|
|
2090
2109
|
const $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
2091
2110
|
$ZodType.init(inst, def);
|
|
2092
2111
|
inst._zod.parse = (payload) => payload;
|
|
@@ -3163,6 +3182,13 @@ function _int(Class, params) {
|
|
|
3163
3182
|
});
|
|
3164
3183
|
}
|
|
3165
3184
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3185
|
+
function _boolean(Class, params) {
|
|
3186
|
+
return new Class({
|
|
3187
|
+
type: "boolean",
|
|
3188
|
+
...normalizeParams(params)
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3166
3192
|
function _unknown(Class) {
|
|
3167
3193
|
return new Class({ type: "unknown" });
|
|
3168
3194
|
}
|
|
@@ -3329,6 +3355,17 @@ function _array(Class, element, params) {
|
|
|
3329
3355
|
});
|
|
3330
3356
|
}
|
|
3331
3357
|
/* @__NO_SIDE_EFFECTS__ */
|
|
3358
|
+
function _custom(Class, fn, _params) {
|
|
3359
|
+
const norm = normalizeParams(_params);
|
|
3360
|
+
norm.abort ?? (norm.abort = true);
|
|
3361
|
+
return new Class({
|
|
3362
|
+
type: "custom",
|
|
3363
|
+
check: "custom",
|
|
3364
|
+
fn,
|
|
3365
|
+
...norm
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
3332
3369
|
function _refine(Class, fn, _params) {
|
|
3333
3370
|
return new Class({
|
|
3334
3371
|
type: "custom",
|
|
@@ -4925,6 +4962,14 @@ const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, d
|
|
|
4925
4962
|
function int(params) {
|
|
4926
4963
|
return _int(ZodNumberFormat, params);
|
|
4927
4964
|
}
|
|
4965
|
+
const ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
4966
|
+
$ZodBoolean.init(inst, def);
|
|
4967
|
+
ZodType.init(inst, def);
|
|
4968
|
+
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
4969
|
+
});
|
|
4970
|
+
function boolean(params) {
|
|
4971
|
+
return _boolean(ZodBoolean, params);
|
|
4972
|
+
}
|
|
4928
4973
|
const ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
4929
4974
|
$ZodUnknown.init(inst, def);
|
|
4930
4975
|
ZodType.init(inst, def);
|
|
@@ -5236,6 +5281,9 @@ const ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
|
|
|
5236
5281
|
ZodType.init(inst, def);
|
|
5237
5282
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
5238
5283
|
});
|
|
5284
|
+
function custom(fn, _params) {
|
|
5285
|
+
return _custom(ZodCustom, fn ?? (() => true), _params);
|
|
5286
|
+
}
|
|
5239
5287
|
function refine(fn, _params = {}) {
|
|
5240
5288
|
return _refine(ZodCustom, fn, _params);
|
|
5241
5289
|
}
|
|
@@ -5485,12 +5533,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5485
5533
|
this.repairPolicy = repairPolicy;
|
|
5486
5534
|
}
|
|
5487
5535
|
async execute(args) {
|
|
5536
|
+
if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
|
|
5537
|
+
toolName: c.binding.config.name,
|
|
5538
|
+
toolCallId: c.toolCall.id ?? c.binding.config.name,
|
|
5539
|
+
result: { error: c.binding.humanApproval !== void 0 ? `HITL tool '${c.binding.config.name}' cannot be called alongside other tools in the same turn; call it alone.` : `deferred: a HITL tool in the same turn blocked execution. Retry this tool alone in the next turn.` },
|
|
5540
|
+
serialized: JSON.stringify({ error: c.binding.humanApproval !== void 0 ? `HITL tool '${c.binding.config.name}' cannot be called alongside other tools in the same turn; call it alone.` : `deferred: a HITL tool in the same turn blocked execution. Retry this tool alone in the next turn.` })
|
|
5541
|
+
}));
|
|
5488
5542
|
const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
|
|
5489
5543
|
...args,
|
|
5490
5544
|
plannedToolCall
|
|
5491
5545
|
})));
|
|
5492
5546
|
const rejected = results.find((result) => result.status === "rejected");
|
|
5493
|
-
if (rejected?.status === "rejected")
|
|
5547
|
+
if (rejected?.status === "rejected") {
|
|
5548
|
+
const reason = rejected.reason;
|
|
5549
|
+
if (reason instanceof __codemation_core.SuspensionRequest) throw reason;
|
|
5550
|
+
throw reason instanceof Error ? reason : new Error(String(reason));
|
|
5551
|
+
}
|
|
5494
5552
|
return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
5495
5553
|
}
|
|
5496
5554
|
async executePlannedToolCall(args) {
|
|
@@ -5575,6 +5633,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5575
5633
|
result
|
|
5576
5634
|
};
|
|
5577
5635
|
} catch (error) {
|
|
5636
|
+
if (error instanceof __codemation_core.SuspensionRequest) {
|
|
5637
|
+
const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
|
|
5638
|
+
const checkpoint = {
|
|
5639
|
+
conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
|
|
5640
|
+
turnCount: args.turnCount ?? 0,
|
|
5641
|
+
toolCallCount: args.toolCallCount ?? 0,
|
|
5642
|
+
pendingToolCallId,
|
|
5643
|
+
agentName: args.agentName,
|
|
5644
|
+
modelId: args.modelId ?? ""
|
|
5645
|
+
};
|
|
5646
|
+
const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
|
|
5647
|
+
const augmented = new __codemation_core.SuspensionRequest({
|
|
5648
|
+
...error.request,
|
|
5649
|
+
metadata: {
|
|
5650
|
+
...error.request.metadata,
|
|
5651
|
+
agentCheckpoint: checkpoint,
|
|
5652
|
+
pendingToolCallId,
|
|
5653
|
+
agentReasoning,
|
|
5654
|
+
onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
|
|
5655
|
+
}
|
|
5656
|
+
});
|
|
5657
|
+
await span.end({
|
|
5658
|
+
status: "error",
|
|
5659
|
+
statusMessage: "suspended",
|
|
5660
|
+
endedAt: /* @__PURE__ */ new Date()
|
|
5661
|
+
});
|
|
5662
|
+
throw augmented;
|
|
5663
|
+
}
|
|
5578
5664
|
const classification = this.errorClassifier.classify({
|
|
5579
5665
|
error,
|
|
5580
5666
|
toolName: plannedToolCall.binding.config.name,
|
|
@@ -5750,6 +5836,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
|
|
|
5750
5836
|
extractErrorDetails(error) {
|
|
5751
5837
|
return error.details;
|
|
5752
5838
|
}
|
|
5839
|
+
/**
|
|
5840
|
+
* Extracts the text content from the last assistant message in the conversation snapshot.
|
|
5841
|
+
* Used to populate `agentReasoning` in the HITL suspension metadata.
|
|
5842
|
+
*/
|
|
5843
|
+
extractLastAssistantText(conversation) {
|
|
5844
|
+
for (let i = conversation.length - 1; i >= 0; i--) {
|
|
5845
|
+
const msg = conversation[i];
|
|
5846
|
+
if (msg?.role !== "assistant") continue;
|
|
5847
|
+
const content = msg.content;
|
|
5848
|
+
if (typeof content === "string") return content;
|
|
5849
|
+
if (Array.isArray(content)) {
|
|
5850
|
+
const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
|
|
5851
|
+
if (textParts.length > 0) return textParts.join("");
|
|
5852
|
+
}
|
|
5853
|
+
break;
|
|
5854
|
+
}
|
|
5855
|
+
return "";
|
|
5856
|
+
}
|
|
5753
5857
|
serializeIssue(issue$1) {
|
|
5754
5858
|
const result = {
|
|
5755
5859
|
path: [...issue$1.path],
|
|
@@ -6103,6 +6207,7 @@ var AgentItemPortMap = class {
|
|
|
6103
6207
|
//#endregion
|
|
6104
6208
|
//#region src/nodes/AIAgentNode.ts
|
|
6105
6209
|
var _ref, _ref2, _ref3, _ref4, _ref5;
|
|
6210
|
+
const HITL_SOLO_CONSTRAINT_SENTENCE = "This tool requires human approval and may take time. Call it alone — do not invoke other tools in the same turn. Your turn will be paused until a decision is made.";
|
|
6106
6211
|
let AIAgentNode = class AIAgentNode$1 {
|
|
6107
6212
|
kind = "node";
|
|
6108
6213
|
outputPorts = ["main"];
|
|
@@ -6120,13 +6225,90 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6120
6225
|
this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
|
|
6121
6226
|
}
|
|
6122
6227
|
async execute(args) {
|
|
6123
|
-
const
|
|
6228
|
+
const { ctx } = args;
|
|
6229
|
+
if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
|
|
6230
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
6124
6231
|
const itemWithMappedJson = {
|
|
6125
6232
|
...args.item,
|
|
6126
6233
|
json: args.input
|
|
6127
6234
|
};
|
|
6128
6235
|
return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
6129
6236
|
}
|
|
6237
|
+
/**
|
|
6238
|
+
* Resume path: re-enters the agent loop after a HITL suspension.
|
|
6239
|
+
* Reconstructs the conversation from the checkpoint, injects the human decision
|
|
6240
|
+
* as a tool_result, and continues the loop from where it suspended.
|
|
6241
|
+
*/
|
|
6242
|
+
async executeResumed(args, resumeContext) {
|
|
6243
|
+
const { ctx } = args;
|
|
6244
|
+
const taskMetadata = resumeContext.task.metadata ?? {};
|
|
6245
|
+
const checkpoint = taskMetadata["agentCheckpoint"];
|
|
6246
|
+
const onRejected = taskMetadata["onRejected"] ?? "return";
|
|
6247
|
+
if (!checkpoint) {
|
|
6248
|
+
const prepared$1 = await this.getOrPrepareExecution(ctx);
|
|
6249
|
+
const itemWithMappedJson = {
|
|
6250
|
+
...args.item,
|
|
6251
|
+
json: args.input
|
|
6252
|
+
};
|
|
6253
|
+
return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
|
|
6254
|
+
}
|
|
6255
|
+
if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
|
|
6256
|
+
const decision = this.normalizeDecision(resumeContext);
|
|
6257
|
+
if (decision.status === "rejected" && onRejected === "halt") return;
|
|
6258
|
+
const prepared = await this.getOrPrepareExecution(ctx);
|
|
6259
|
+
const item = args.item;
|
|
6260
|
+
const itemInputsByPort = AgentItemPortMap.fromItem(item);
|
|
6261
|
+
const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
|
|
6262
|
+
const toolResultEntry = {
|
|
6263
|
+
toolName: checkpoint.pendingToolCallId,
|
|
6264
|
+
toolCallId: checkpoint.pendingToolCallId,
|
|
6265
|
+
result: decision,
|
|
6266
|
+
serialized: JSON.stringify(decision)
|
|
6267
|
+
};
|
|
6268
|
+
const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
|
|
6269
|
+
const loopResult = await this.runTurnLoopUntilFinalAnswer({
|
|
6270
|
+
prepared,
|
|
6271
|
+
itemInputsByPort,
|
|
6272
|
+
itemScopedTools,
|
|
6273
|
+
conversation,
|
|
6274
|
+
resumedTurnCount: checkpoint.turnCount,
|
|
6275
|
+
resumedToolCallCount: checkpoint.toolCallCount
|
|
6276
|
+
});
|
|
6277
|
+
await ctx.telemetry.recordMetric({
|
|
6278
|
+
name: __codemation_core.CodemationTelemetryMetricNames.agentTurns,
|
|
6279
|
+
value: loopResult.turnCount
|
|
6280
|
+
});
|
|
6281
|
+
await ctx.telemetry.recordMetric({
|
|
6282
|
+
name: __codemation_core.CodemationTelemetryMetricNames.agentToolCalls,
|
|
6283
|
+
value: loopResult.toolCallCount
|
|
6284
|
+
});
|
|
6285
|
+
const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
|
|
6286
|
+
return this.buildOutputItem(item, outputJson).json;
|
|
6287
|
+
}
|
|
6288
|
+
/**
|
|
6289
|
+
* Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
|
|
6290
|
+
* suitable for injection as a tool_result content.
|
|
6291
|
+
*/
|
|
6292
|
+
normalizeDecision(resumeContext) {
|
|
6293
|
+
const { decision } = resumeContext;
|
|
6294
|
+
if (decision.kind === "decided") {
|
|
6295
|
+
const value = decision.value;
|
|
6296
|
+
return {
|
|
6297
|
+
status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
|
|
6298
|
+
value: decision.value,
|
|
6299
|
+
actor: decision.actor,
|
|
6300
|
+
decidedAt: decision.decidedAt.toISOString()
|
|
6301
|
+
};
|
|
6302
|
+
}
|
|
6303
|
+
if (decision.kind === "timed_out") return {
|
|
6304
|
+
status: "timed_out",
|
|
6305
|
+
at: decision.at.toISOString()
|
|
6306
|
+
};
|
|
6307
|
+
return {
|
|
6308
|
+
status: "auto_accepted",
|
|
6309
|
+
at: decision.at.toISOString()
|
|
6310
|
+
};
|
|
6311
|
+
}
|
|
6130
6312
|
async getOrPrepareExecution(ctx) {
|
|
6131
6313
|
let pending = this.preparedByExecutionContext.get(ctx);
|
|
6132
6314
|
if (!pending) {
|
|
@@ -6247,7 +6429,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6247
6429
|
const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
|
|
6248
6430
|
const { ctx, guardrails, toolLoadingStrategy } = prepared;
|
|
6249
6431
|
let finalText = "";
|
|
6250
|
-
let toolCallCount = 0;
|
|
6432
|
+
let toolCallCount = args.resumedToolCallCount ?? 0;
|
|
6251
6433
|
let turnCount = 0;
|
|
6252
6434
|
const repairAttemptsByToolName = /* @__PURE__ */ new Map();
|
|
6253
6435
|
/** Tool IDs surfaced by find_tools across all prior turns in this item run. */
|
|
@@ -6288,11 +6470,17 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6288
6470
|
const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
|
|
6289
6471
|
toolCallCount += plannedToolCalls.length;
|
|
6290
6472
|
await this.markQueuedTools(plannedToolCalls, ctx);
|
|
6473
|
+
const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
|
|
6474
|
+
const conversationWithAssistant = [...conversation, assistantMsg];
|
|
6291
6475
|
const executed = await this.toolExecutionCoordinator.execute({
|
|
6292
6476
|
plannedToolCalls,
|
|
6293
6477
|
ctx,
|
|
6294
6478
|
agentName: this.getAgentDisplayName(ctx),
|
|
6295
|
-
repairAttemptsByToolName
|
|
6479
|
+
repairAttemptsByToolName,
|
|
6480
|
+
conversationSnapshot: conversationWithAssistant,
|
|
6481
|
+
turnCount,
|
|
6482
|
+
toolCallCount,
|
|
6483
|
+
modelId: this.resolveChatModelName(ctx.config.chatModel)
|
|
6296
6484
|
});
|
|
6297
6485
|
coordinatorExecutedCalls.push(...executed);
|
|
6298
6486
|
}
|
|
@@ -6354,6 +6542,7 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6354
6542
|
connectionNodeId: __codemation_core.ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
|
|
6355
6543
|
getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
|
|
6356
6544
|
});
|
|
6545
|
+
const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
|
|
6357
6546
|
return {
|
|
6358
6547
|
config: entry.config,
|
|
6359
6548
|
inputSchema: entry.runtime.inputSchema,
|
|
@@ -6368,11 +6557,22 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6368
6557
|
items,
|
|
6369
6558
|
hooks
|
|
6370
6559
|
});
|
|
6371
|
-
}
|
|
6560
|
+
},
|
|
6561
|
+
...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
|
|
6372
6562
|
};
|
|
6373
6563
|
});
|
|
6374
6564
|
}
|
|
6375
6565
|
/**
|
|
6566
|
+
* Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
|
|
6567
|
+
* and returns the HITL behavior config, or `undefined` when not a HITL tool.
|
|
6568
|
+
*/
|
|
6569
|
+
resolveHumanApprovalBehavior(config$1) {
|
|
6570
|
+
if (!this.isNodeBackedToolConfig(config$1)) return void 0;
|
|
6571
|
+
const marker = config$1.node.humanApprovalToolBehavior;
|
|
6572
|
+
if (marker === void 0) return void 0;
|
|
6573
|
+
return { onRejected: marker.onRejected ?? "return" };
|
|
6574
|
+
}
|
|
6575
|
+
/**
|
|
6376
6576
|
* Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
|
|
6377
6577
|
* and strategy tools (find_tools + discovered MCP tools).
|
|
6378
6578
|
* Strategy tools take precedence for names that overlap.
|
|
@@ -6402,6 +6602,8 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6402
6602
|
/**
|
|
6403
6603
|
* Builds a ToolSet from resolved tools for strategy initialization.
|
|
6404
6604
|
* The strategy uses this for its "always-included" node-backed tool descriptions.
|
|
6605
|
+
* HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
|
|
6606
|
+
* appended to their description.
|
|
6405
6607
|
*/
|
|
6406
6608
|
buildToolSetFromResolved(resolvedTools) {
|
|
6407
6609
|
if (resolvedTools.length === 0) return {};
|
|
@@ -6411,8 +6613,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6411
6613
|
schemaName: entry.config.name,
|
|
6412
6614
|
requireObjectRoot: true
|
|
6413
6615
|
});
|
|
6616
|
+
const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
|
|
6617
|
+
const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
|
|
6414
6618
|
toolSet[entry.config.name] = {
|
|
6415
|
-
description
|
|
6619
|
+
description,
|
|
6416
6620
|
inputSchema: (0, ai.jsonSchema)(schemaRecord)
|
|
6417
6621
|
};
|
|
6418
6622
|
}
|
|
@@ -6440,8 +6644,10 @@ let AIAgentNode = class AIAgentNode$1 {
|
|
|
6440
6644
|
schemaName: entry.config.name,
|
|
6441
6645
|
requireObjectRoot: true
|
|
6442
6646
|
});
|
|
6647
|
+
const baseDescription = entry.config.description;
|
|
6648
|
+
const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
|
|
6443
6649
|
toolSet[entry.config.name] = {
|
|
6444
|
-
description
|
|
6650
|
+
description,
|
|
6445
6651
|
inputSchema: (0, ai.jsonSchema)(schemaRecord)
|
|
6446
6652
|
};
|
|
6447
6653
|
}
|
|
@@ -8758,6 +8964,93 @@ const collectionDeleteNode = (0, __codemation_core.defineNode)({
|
|
|
8758
8964
|
}
|
|
8759
8965
|
});
|
|
8760
8966
|
|
|
8967
|
+
//#endregion
|
|
8968
|
+
//#region src/nodes/InboxApprovalNode.types.ts
|
|
8969
|
+
function resolveSubjectField(field, item) {
|
|
8970
|
+
return typeof field === "function" ? field({ item }) : field;
|
|
8971
|
+
}
|
|
8972
|
+
/**
|
|
8973
|
+
* Auto-detecting inbox approval node.
|
|
8974
|
+
*
|
|
8975
|
+
* Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
|
|
8976
|
+
* at runtime:
|
|
8977
|
+
* - In managed mode (PairingConfig present): routes to the control-plane inbox.
|
|
8978
|
+
* - Otherwise: routes to the local inbox.
|
|
8979
|
+
*
|
|
8980
|
+
* Authors use this node directly; no extra wiring needed per deployment mode.
|
|
8981
|
+
*/
|
|
8982
|
+
const inboxApproval = (0, __codemation_core.defineHumanApprovalNode)({
|
|
8983
|
+
key: "inbox.approval",
|
|
8984
|
+
title: "Inbox Approval",
|
|
8985
|
+
description: "Suspend and wait for a human reviewer to approve or reject.",
|
|
8986
|
+
icon: "lucide:inbox",
|
|
8987
|
+
channel: "inbox",
|
|
8988
|
+
configSchema: object({
|
|
8989
|
+
title: custom((v) => typeof v === "string" || typeof v === "function"),
|
|
8990
|
+
body: custom((v) => typeof v === "string" || typeof v === "function"),
|
|
8991
|
+
priority: _enum([
|
|
8992
|
+
"low",
|
|
8993
|
+
"normal",
|
|
8994
|
+
"high"
|
|
8995
|
+
]).default("normal"),
|
|
8996
|
+
timeout: string().default("24h"),
|
|
8997
|
+
onTimeout: _enum(["halt", "auto-accept"]).default("halt")
|
|
8998
|
+
}),
|
|
8999
|
+
decisionSchema: object({
|
|
9000
|
+
approved: boolean(),
|
|
9001
|
+
note: string().optional()
|
|
9002
|
+
}),
|
|
9003
|
+
defaultTimeout: "24h",
|
|
9004
|
+
defaultOnTimeout: "halt",
|
|
9005
|
+
async deliver({ task, config: config$1, item }, ctx) {
|
|
9006
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
9007
|
+
if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
|
|
9008
|
+
const { channel, workspaceId } = resolver.resolve();
|
|
9009
|
+
const subject = {
|
|
9010
|
+
title: resolveSubjectField(config$1.title, item),
|
|
9011
|
+
summary: resolveSubjectField(config$1.body, item),
|
|
9012
|
+
attributes: {
|
|
9013
|
+
workflowId: ctx.workflowId,
|
|
9014
|
+
item: item.json
|
|
9015
|
+
}
|
|
9016
|
+
};
|
|
9017
|
+
const delivery = await channel.deliver({
|
|
9018
|
+
task,
|
|
9019
|
+
subject,
|
|
9020
|
+
priority: config$1.priority,
|
|
9021
|
+
item,
|
|
9022
|
+
workspaceId
|
|
9023
|
+
});
|
|
9024
|
+
ctx.telemetry.addSpanEvent({
|
|
9025
|
+
name: "hitl.task.delivered",
|
|
9026
|
+
attributes: {
|
|
9027
|
+
taskId: task.taskId,
|
|
9028
|
+
channel: channel.kind
|
|
9029
|
+
}
|
|
9030
|
+
});
|
|
9031
|
+
return delivery;
|
|
9032
|
+
},
|
|
9033
|
+
async onDecision({ decision, actor, delivery }, ctx) {
|
|
9034
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
9035
|
+
if (!resolver) return;
|
|
9036
|
+
const { channel } = resolver.resolve();
|
|
9037
|
+
await channel.updateOnDecision?.({
|
|
9038
|
+
delivery,
|
|
9039
|
+
decision,
|
|
9040
|
+
actor
|
|
9041
|
+
});
|
|
9042
|
+
},
|
|
9043
|
+
async onTimeout({ delivery, policy }, ctx) {
|
|
9044
|
+
const resolver = ctx.resolve(__codemation_core.InboxChannelResolverToken);
|
|
9045
|
+
if (!resolver) return;
|
|
9046
|
+
const { channel } = resolver.resolve();
|
|
9047
|
+
await channel.updateOnTimeout?.({
|
|
9048
|
+
delivery,
|
|
9049
|
+
policy
|
|
9050
|
+
});
|
|
9051
|
+
}
|
|
9052
|
+
});
|
|
9053
|
+
|
|
8761
9054
|
//#endregion
|
|
8762
9055
|
exports.AIAgent = AIAgent;
|
|
8763
9056
|
exports.AIAgentConnectionWorkflowExpander = AIAgentConnectionWorkflowExpander;
|
|
@@ -8994,6 +9287,7 @@ exports.collectionListNode = collectionListNode;
|
|
|
8994
9287
|
exports.collectionUpdateNode = collectionUpdateNode;
|
|
8995
9288
|
exports.createWorkflowBuilder = createWorkflowBuilder;
|
|
8996
9289
|
exports.defineRestNode = defineRestNode;
|
|
9290
|
+
exports.inboxApproval = inboxApproval;
|
|
8997
9291
|
exports.oauth2ClientCredentialsType = oauth2ClientCredentialsType;
|
|
8998
9292
|
exports.openAiChatModelPresets = openAiChatModelPresets;
|
|
8999
9293
|
exports.registerCoreNodes = registerCoreNodes;
|