@stigmer/runner 3.5.3 → 3.7.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/dist/.build-fingerprint +1 -1
- package/dist/activities/call-agent.js +85 -10
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/discover-mcp-server.js +9 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -0
- package/dist/activities/execute-cursor/index.js +88 -14
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/model-pricing.d.ts +9 -0
- package/dist/activities/execute-cursor/model-pricing.js +19 -0
- package/dist/activities/execute-cursor/model-pricing.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +7 -0
- package/dist/activities/execute-cursor/prompt-builder.js +9 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/service-tier.d.ts +68 -0
- package/dist/activities/execute-cursor/service-tier.js +187 -0
- package/dist/activities/execute-cursor/service-tier.js.map +1 -0
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +16 -1
- package/dist/activities/execute-cursor/session-lifecycle.js +12 -4
- package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
- package/dist/activities/execute-cursor/usage-accumulator.d.ts +21 -1
- package/dist/activities/execute-cursor/usage-accumulator.js +23 -3
- package/dist/activities/execute-cursor/usage-accumulator.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +6 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +3 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +45 -9
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +32 -1
- package/dist/client/stigmer-client.js +42 -2
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/main.js +18 -0
- package/dist/main.js.map +1 -1
- package/dist/runner.js +48 -0
- package/dist/runner.js.map +1 -1
- package/dist/sandbox-token-renewal.d.ts +65 -0
- package/dist/sandbox-token-renewal.js +169 -0
- package/dist/sandbox-token-renewal.js.map +1 -0
- package/dist/shared/artifact-storage.d.ts +17 -3
- package/dist/shared/artifact-storage.js +22 -4
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +89 -0
- package/dist/shared/caller-identity.js +124 -0
- package/dist/shared/caller-identity.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +85 -0
- package/dist/shared/channel-attachment.js +203 -0
- package/dist/shared/channel-attachment.js.map +1 -0
- package/dist/shared/datastore-attachment.d.ts +2 -25
- package/dist/shared/datastore-attachment.js +1 -28
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/synthesized-attachment.d.ts +51 -0
- package/dist/shared/synthesized-attachment.js +45 -0
- package/dist/shared/synthesized-attachment.js.map +1 -0
- package/dist/workflow-engine/loader.js +99 -2
- package/dist/workflow-engine/loader.js.map +1 -1
- package/dist/workflow-engine/tasks/call-agent.d.ts +0 -2
- package/dist/workflow-engine/tasks/call-agent.js +0 -2
- package/dist/workflow-engine/tasks/call-agent.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +39 -7
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +3 -2
- package/dist/workflows/call-agent-orchestrator.js +8 -2
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-utils__/mock-client.ts +4 -0
- package/src/__tests__/sandbox-token-renewal.test.ts +174 -0
- package/src/activities/__tests__/call-agent-contracts.test.ts +4 -4
- package/src/activities/__tests__/call-agent.test.ts +219 -4
- package/src/activities/__tests__/discover-mcp-server.test.ts +49 -0
- package/src/activities/call-agent.ts +94 -10
- package/src/activities/discover-mcp-server.ts +13 -1
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +20 -0
- package/src/activities/execute-cursor/__tests__/service-tier.test.ts +170 -0
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +87 -1
- package/src/activities/execute-cursor/index.ts +121 -20
- package/src/activities/execute-cursor/model-pricing.ts +23 -0
- package/src/activities/execute-cursor/prompt-builder.ts +19 -0
- package/src/activities/execute-cursor/service-tier.ts +244 -0
- package/src/activities/execute-cursor/session-lifecycle.ts +33 -5
- package/src/activities/execute-cursor/usage-accumulator.ts +35 -3
- package/src/activities/execute-deep-agent/prompt-builder.ts +10 -0
- package/src/activities/execute-deep-agent/setup.ts +66 -10
- package/src/client/stigmer-client.ts +57 -4
- package/src/main.ts +20 -0
- package/src/runner.ts +62 -0
- package/src/sandbox-token-renewal.ts +212 -0
- package/src/shared/__tests__/caller-identity.test.ts +159 -0
- package/src/shared/__tests__/channel-attachment.test.ts +276 -0
- package/src/shared/__tests__/datastore-attachment.test.ts +4 -4
- package/src/shared/artifact-storage.ts +32 -7
- package/src/shared/caller-identity.ts +161 -0
- package/src/shared/channel-attachment.ts +237 -0
- package/src/shared/datastore-attachment.ts +2 -54
- package/src/shared/synthesized-attachment.ts +77 -0
- package/src/workflow-engine/__tests__/golden-execution.test.ts +8 -8
- package/src/workflow-engine/__tests__/loader.test.ts +192 -7
- package/src/workflow-engine/__tests__/tasks/call-agent.test.ts +9 -9
- package/src/workflow-engine/loader.ts +113 -2
- package/src/workflow-engine/tasks/call-agent.ts +0 -2
- package/src/workflow-engine/types.ts +40 -7
- package/src/workflows/call-agent-orchestrator.ts +8 -2
|
@@ -420,12 +420,13 @@ do:
|
|
|
420
420
|
with:
|
|
421
421
|
agent: "acme/support-triage"
|
|
422
422
|
message: "Triage this ticket"
|
|
423
|
-
org: acme
|
|
424
423
|
env:
|
|
425
424
|
API_KEY: "\${.secrets.KEY}"
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
425
|
+
run_config:
|
|
426
|
+
model_name: claude-3-5-sonnet
|
|
427
|
+
max_cost_usd: 0.5
|
|
428
|
+
max_tool_rounds: 15
|
|
429
|
+
service_tier: fast
|
|
429
430
|
output:
|
|
430
431
|
schema:
|
|
431
432
|
type: object
|
|
@@ -444,10 +445,11 @@ do:
|
|
|
444
445
|
if (task.kind === "call:agent") {
|
|
445
446
|
expect(task.with.agent).toBe("acme/support-triage");
|
|
446
447
|
expect(task.with.message).toBe("Triage this ticket");
|
|
447
|
-
expect(task.with.org).toBe("acme");
|
|
448
448
|
expect(task.with.env?.API_KEY).toBe("${.secrets.KEY}");
|
|
449
|
-
expect(task.with.
|
|
450
|
-
expect(task.with.
|
|
449
|
+
expect(task.with.run_config?.model_name).toBe("claude-3-5-sonnet");
|
|
450
|
+
expect(task.with.run_config?.max_cost_usd).toBe(0.5);
|
|
451
|
+
expect(task.with.run_config?.max_tool_rounds).toBe(15);
|
|
452
|
+
expect(task.with.run_config?.service_tier).toBe("SERVICE_TIER_FAST");
|
|
451
453
|
expect(task.with.output?.schema.type).toBe("object");
|
|
452
454
|
expect(task.with.output?.on_invalid).toBe("ON_INVALID_RETRY");
|
|
453
455
|
expect(task.with.output?.max_retries).toBe(2);
|
|
@@ -455,6 +457,189 @@ do:
|
|
|
455
457
|
}
|
|
456
458
|
});
|
|
457
459
|
|
|
460
|
+
it("rejects unknown run_config keys with a named error", () => {
|
|
461
|
+
const yaml = `
|
|
462
|
+
document:
|
|
463
|
+
dsl: '1.0.0'
|
|
464
|
+
name: test
|
|
465
|
+
do:
|
|
466
|
+
- invoke:
|
|
467
|
+
call: agent
|
|
468
|
+
with:
|
|
469
|
+
agent: reviewer
|
|
470
|
+
message: Review
|
|
471
|
+
run_config:
|
|
472
|
+
model: claude-sonnet-4-6
|
|
473
|
+
`;
|
|
474
|
+
// "model" is the pre-#358 key; the shared RunConfig field is
|
|
475
|
+
// "model_name". A typo'd knob must fail parsing, not silently no-op.
|
|
476
|
+
expect(() => loadWorkflowFromYaml(yaml)).toThrow(
|
|
477
|
+
"call:agent 'run_config' has unknown field 'model'",
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("maps service_tier shorthands and canonical names to the enum name", () => {
|
|
482
|
+
for (const [written, expected] of [
|
|
483
|
+
["standard", "SERVICE_TIER_STANDARD"],
|
|
484
|
+
["fast", "SERVICE_TIER_FAST"],
|
|
485
|
+
["FAST", "SERVICE_TIER_FAST"],
|
|
486
|
+
["SERVICE_TIER_STANDARD", "SERVICE_TIER_STANDARD"],
|
|
487
|
+
] as const) {
|
|
488
|
+
const yaml = `
|
|
489
|
+
document:
|
|
490
|
+
dsl: '1.0.0'
|
|
491
|
+
name: test
|
|
492
|
+
do:
|
|
493
|
+
- invoke:
|
|
494
|
+
call: agent
|
|
495
|
+
with:
|
|
496
|
+
agent: reviewer
|
|
497
|
+
message: Review
|
|
498
|
+
run_config:
|
|
499
|
+
service_tier: "${written}"
|
|
500
|
+
`;
|
|
501
|
+
const model = loadWorkflowFromYaml(yaml);
|
|
502
|
+
const task = model.do[0].task;
|
|
503
|
+
if (task.kind === "call:agent") {
|
|
504
|
+
expect(task.with.run_config?.service_tier).toBe(expected);
|
|
505
|
+
} else {
|
|
506
|
+
throw new Error("expected call:agent task");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("rejects an unknown service_tier value with a named error", () => {
|
|
512
|
+
const yaml = `
|
|
513
|
+
document:
|
|
514
|
+
dsl: '1.0.0'
|
|
515
|
+
name: test
|
|
516
|
+
do:
|
|
517
|
+
- invoke:
|
|
518
|
+
call: agent
|
|
519
|
+
with:
|
|
520
|
+
agent: reviewer
|
|
521
|
+
message: Review
|
|
522
|
+
run_config:
|
|
523
|
+
service_tier: turbo
|
|
524
|
+
`;
|
|
525
|
+
// The tier exists to make pricing deterministic — a typo must fail
|
|
526
|
+
// parsing, never silently fall back to the default tier.
|
|
527
|
+
expect(() => loadWorkflowFromYaml(yaml)).toThrow(
|
|
528
|
+
"call:agent 'run_config.service_tier' has unknown value 'turbo'",
|
|
529
|
+
);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("rejects negative run_config bounds", () => {
|
|
533
|
+
const yaml = `
|
|
534
|
+
document:
|
|
535
|
+
dsl: '1.0.0'
|
|
536
|
+
name: test
|
|
537
|
+
do:
|
|
538
|
+
- invoke:
|
|
539
|
+
call: agent
|
|
540
|
+
with:
|
|
541
|
+
agent: reviewer
|
|
542
|
+
message: Review
|
|
543
|
+
run_config:
|
|
544
|
+
max_cost_usd: -1
|
|
545
|
+
`;
|
|
546
|
+
expect(() => loadWorkflowFromYaml(yaml)).toThrow(
|
|
547
|
+
"call:agent 'run_config.max_cost_usd' must be a number >= 0",
|
|
548
|
+
);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("parses git workspace entries", () => {
|
|
552
|
+
const yaml = `
|
|
553
|
+
document:
|
|
554
|
+
dsl: '1.0.0'
|
|
555
|
+
name: test
|
|
556
|
+
do:
|
|
557
|
+
- invoke:
|
|
558
|
+
call: agent
|
|
559
|
+
with:
|
|
560
|
+
agent: reviewer
|
|
561
|
+
message: Review
|
|
562
|
+
workspace_entries:
|
|
563
|
+
- name: app
|
|
564
|
+
source:
|
|
565
|
+
git_repo:
|
|
566
|
+
url: https://github.com/acme/app
|
|
567
|
+
branch: main
|
|
568
|
+
- source:
|
|
569
|
+
git_repo:
|
|
570
|
+
url: https://github.com/acme/lib
|
|
571
|
+
`;
|
|
572
|
+
const model = loadWorkflowFromYaml(yaml);
|
|
573
|
+
const task = model.do[0].task;
|
|
574
|
+
expect(task.kind).toBe("call:agent");
|
|
575
|
+
if (task.kind === "call:agent") {
|
|
576
|
+
const entries = task.with.workspace_entries;
|
|
577
|
+
expect(entries).toHaveLength(2);
|
|
578
|
+
expect(entries?.[0].name).toBe("app");
|
|
579
|
+
expect(entries?.[0].source.git_repo.url).toBe("https://github.com/acme/app");
|
|
580
|
+
expect(entries?.[0].source.git_repo.branch).toBe("main");
|
|
581
|
+
expect(entries?.[1].name).toBeUndefined();
|
|
582
|
+
expect(entries?.[1].source.git_repo.branch).toBeUndefined();
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it("rejects a workspace entry without a git url", () => {
|
|
587
|
+
const yaml = `
|
|
588
|
+
document:
|
|
589
|
+
dsl: '1.0.0'
|
|
590
|
+
name: test
|
|
591
|
+
do:
|
|
592
|
+
- invoke:
|
|
593
|
+
call: agent
|
|
594
|
+
with:
|
|
595
|
+
agent: reviewer
|
|
596
|
+
message: Review
|
|
597
|
+
workspace_entries:
|
|
598
|
+
- source:
|
|
599
|
+
local_path:
|
|
600
|
+
path: /home/me/repo
|
|
601
|
+
`;
|
|
602
|
+
expect(() => loadWorkflowFromYaml(yaml)).toThrow(
|
|
603
|
+
"call:agent 'workspace_entries[0]' requires 'source.git_repo.url'",
|
|
604
|
+
);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("rejects a non-list workspace_entries", () => {
|
|
608
|
+
const yaml = `
|
|
609
|
+
document:
|
|
610
|
+
dsl: '1.0.0'
|
|
611
|
+
name: test
|
|
612
|
+
do:
|
|
613
|
+
- invoke:
|
|
614
|
+
call: agent
|
|
615
|
+
with:
|
|
616
|
+
agent: reviewer
|
|
617
|
+
message: Review
|
|
618
|
+
workspace_entries: "https://github.com/acme/app"
|
|
619
|
+
`;
|
|
620
|
+
expect(() => loadWorkflowFromYaml(yaml)).toThrow(
|
|
621
|
+
"call:agent 'workspace_entries' must be a list",
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("rejects a non-mapping run_config", () => {
|
|
626
|
+
const yaml = `
|
|
627
|
+
document:
|
|
628
|
+
dsl: '1.0.0'
|
|
629
|
+
name: test
|
|
630
|
+
do:
|
|
631
|
+
- invoke:
|
|
632
|
+
call: agent
|
|
633
|
+
with:
|
|
634
|
+
agent: reviewer
|
|
635
|
+
message: Review
|
|
636
|
+
run_config: "cheap"
|
|
637
|
+
`;
|
|
638
|
+
expect(() => loadWorkflowFromYaml(yaml)).toThrow(
|
|
639
|
+
"call:agent 'run_config' must be a mapping",
|
|
640
|
+
);
|
|
641
|
+
});
|
|
642
|
+
|
|
458
643
|
it("normalizes harness shorthand 'native' to HARNESS_NATIVE", () => {
|
|
459
644
|
const yaml = `
|
|
460
645
|
document:
|
|
@@ -108,16 +108,15 @@ describe("CallAgentTaskBuilder", () => {
|
|
|
108
108
|
expect(config.env.GITHUB_TOKEN).toBe("${.secrets.GH_TOKEN}");
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
it("passes org
|
|
111
|
+
it("passes org-prefixed agent references through untouched", async () => {
|
|
112
112
|
mockCallAgent.mockResolvedValue({ final_text: "done" });
|
|
113
113
|
|
|
114
114
|
const taskDef: CallAgentTaskDef = {
|
|
115
115
|
kind: "call:agent",
|
|
116
116
|
call: "agent",
|
|
117
117
|
with: {
|
|
118
|
-
agent: "my-agent",
|
|
118
|
+
agent: "acme/my-agent",
|
|
119
119
|
message: "Hello",
|
|
120
|
-
org: "acme",
|
|
121
120
|
},
|
|
122
121
|
};
|
|
123
122
|
|
|
@@ -127,7 +126,7 @@ describe("CallAgentTaskBuilder", () => {
|
|
|
127
126
|
await executor({}, createState(), makeCtx());
|
|
128
127
|
|
|
129
128
|
const [config] = mockCallAgent.mock.calls[0];
|
|
130
|
-
expect(config.
|
|
129
|
+
expect(config.agent).toBe("acme/my-agent");
|
|
131
130
|
});
|
|
132
131
|
|
|
133
132
|
it("shouldRun returns true", async () => {
|
|
@@ -495,7 +494,7 @@ describe("CallAgentTaskBuilder — output.schema propagation", () => {
|
|
|
495
494
|
agent: "notification-analyst",
|
|
496
495
|
message: "Generate the daily cohort analysis report.",
|
|
497
496
|
output: { schema: cohortSchema, on_invalid: "ON_INVALID_FAIL" },
|
|
498
|
-
|
|
497
|
+
run_config: { model_name: "claude-sonnet-4" },
|
|
499
498
|
harness: "HARNESS_CURSOR",
|
|
500
499
|
},
|
|
501
500
|
};
|
|
@@ -525,7 +524,7 @@ describe("CallAgentTaskBuilder — output.schema propagation", () => {
|
|
|
525
524
|
"Date: ${ $env.NOTIFICATION_DATE }\n" +
|
|
526
525
|
"Data source: decor schema.",
|
|
527
526
|
output: { schema: cohortSchema, on_invalid: "ON_INVALID_FAIL" },
|
|
528
|
-
|
|
527
|
+
run_config: { model_name: "claude-sonnet-4" },
|
|
529
528
|
},
|
|
530
529
|
};
|
|
531
530
|
|
|
@@ -596,7 +595,7 @@ describe("CallAgentTaskBuilder — output.schema propagation", () => {
|
|
|
596
595
|
expect(config.output.schema.required).toEqual(["executive_summary", "cohorts", "anomalies"]);
|
|
597
596
|
});
|
|
598
597
|
|
|
599
|
-
it("preserves output.schema alongside
|
|
598
|
+
it("preserves output.schema alongside run_config, harness, and env fields", async () => {
|
|
600
599
|
mockCallAgent.mockResolvedValue({ structured: { executive_summary: "ok", cohorts: [], anomalies: [] } });
|
|
601
600
|
|
|
602
601
|
const taskDef: CallAgentTaskDef = {
|
|
@@ -606,7 +605,7 @@ describe("CallAgentTaskBuilder — output.schema propagation", () => {
|
|
|
606
605
|
agent: "notification-analyst",
|
|
607
606
|
message: "Analyze data.",
|
|
608
607
|
env: { POSTGRES_CONNECTION_URL: "${.secrets.PG_URL}" },
|
|
609
|
-
|
|
608
|
+
run_config: { model_name: "claude-sonnet-4", max_cost_usd: 0.5 },
|
|
610
609
|
output: { schema: cohortSchema, on_invalid: "ON_INVALID_FAIL" },
|
|
611
610
|
harness: "HARNESS_CURSOR",
|
|
612
611
|
},
|
|
@@ -618,7 +617,8 @@ describe("CallAgentTaskBuilder — output.schema propagation", () => {
|
|
|
618
617
|
|
|
619
618
|
const [config] = mockCallAgent.mock.calls[0];
|
|
620
619
|
expect(config.agent).toBe("notification-analyst");
|
|
621
|
-
expect(config.
|
|
620
|
+
expect(config.run_config?.model_name).toBe("claude-sonnet-4");
|
|
621
|
+
expect(config.run_config?.max_cost_usd).toBe(0.5);
|
|
622
622
|
expect(config.env?.POSTGRES_CONNECTION_URL).toBe("${.secrets.PG_URL}");
|
|
623
623
|
expect(config.harness).toBe("HARNESS_CURSOR");
|
|
624
624
|
expect(config.output).toBeDefined();
|
|
@@ -489,14 +489,125 @@ function parseAgentCallConfig(raw: unknown): AgentCallConfig {
|
|
|
489
489
|
return {
|
|
490
490
|
agent: obj.agent,
|
|
491
491
|
message: obj.message,
|
|
492
|
-
org: typeof obj.org === "string" ? obj.org : undefined,
|
|
493
492
|
env: obj.env as Record<string, string> | undefined,
|
|
494
|
-
|
|
493
|
+
run_config: parseAgentCallRunConfig(obj.run_config),
|
|
495
494
|
output: obj.output as AgentCallConfig["output"],
|
|
496
495
|
harness,
|
|
496
|
+
workspace_entries: parseAgentCallWorkspaceEntries(obj.workspace_entries),
|
|
497
497
|
};
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Parses workspace entries with structural validation. Write-time
|
|
502
|
+
* validation already enforces the git-only constraint, so a malformed
|
|
503
|
+
* entry here means a converter defect or hand-edited YAML — either way
|
|
504
|
+
* an error the author must hear about, not a silent skip.
|
|
505
|
+
*/
|
|
506
|
+
function parseAgentCallWorkspaceEntries(
|
|
507
|
+
raw: unknown,
|
|
508
|
+
): AgentCallConfig["workspace_entries"] {
|
|
509
|
+
if (raw === undefined || raw === null) return undefined;
|
|
510
|
+
if (!Array.isArray(raw)) {
|
|
511
|
+
throw new Error("call:agent 'workspace_entries' must be a list");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return raw.map((item, i) => {
|
|
515
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
516
|
+
throw new Error(`call:agent 'workspace_entries[${i}]' must be a mapping`);
|
|
517
|
+
}
|
|
518
|
+
const entry = item as Record<string, unknown>;
|
|
519
|
+
const source = entry.source as Record<string, unknown> | undefined;
|
|
520
|
+
const gitRepo = source?.git_repo as Record<string, unknown> | undefined;
|
|
521
|
+
if (!gitRepo || typeof gitRepo.url !== "string" || !gitRepo.url) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
`call:agent 'workspace_entries[${i}]' requires 'source.git_repo.url' ` +
|
|
524
|
+
`(the workflow surface supports git sources only)`,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
name: typeof entry.name === "string" && entry.name ? entry.name : undefined,
|
|
529
|
+
source: {
|
|
530
|
+
git_repo: {
|
|
531
|
+
url: gitRepo.url,
|
|
532
|
+
branch: typeof gitRepo.branch === "string" && gitRepo.branch ? gitRepo.branch : undefined,
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Parses the shared RunConfig block with per-key validation rather than a
|
|
541
|
+
* bare cast: a typo'd key or mistyped value is an authoring error the
|
|
542
|
+
* author must hear about, not something to silently carry along.
|
|
543
|
+
*/
|
|
544
|
+
function parseAgentCallRunConfig(raw: unknown): AgentCallConfig["run_config"] {
|
|
545
|
+
if (raw === undefined || raw === null) return undefined;
|
|
546
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
547
|
+
throw new Error("call:agent 'run_config' must be a mapping");
|
|
548
|
+
}
|
|
549
|
+
const obj = raw as Record<string, unknown>;
|
|
550
|
+
|
|
551
|
+
const known = new Set(["model_name", "max_cost_usd", "max_tool_rounds", "service_tier"]);
|
|
552
|
+
for (const key of Object.keys(obj)) {
|
|
553
|
+
if (!known.has(key)) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`call:agent 'run_config' has unknown field '${key}' ` +
|
|
556
|
+
`(expected: model_name, max_cost_usd, max_tool_rounds, service_tier)`,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const modelName = obj.model_name;
|
|
562
|
+
if (modelName !== undefined && typeof modelName !== "string") {
|
|
563
|
+
throw new Error("call:agent 'run_config.model_name' must be a string");
|
|
564
|
+
}
|
|
565
|
+
const maxCostUsd = obj.max_cost_usd;
|
|
566
|
+
if (maxCostUsd !== undefined && (typeof maxCostUsd !== "number" || maxCostUsd < 0)) {
|
|
567
|
+
throw new Error("call:agent 'run_config.max_cost_usd' must be a number >= 0");
|
|
568
|
+
}
|
|
569
|
+
const maxToolRounds = obj.max_tool_rounds;
|
|
570
|
+
if (maxToolRounds !== undefined && (typeof maxToolRounds !== "number" || maxToolRounds < 0)) {
|
|
571
|
+
throw new Error("call:agent 'run_config.max_tool_rounds' must be a number >= 0");
|
|
572
|
+
}
|
|
573
|
+
const serviceTier = parseServiceTier(obj.service_tier);
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
model_name: modelName,
|
|
577
|
+
max_cost_usd: maxCostUsd,
|
|
578
|
+
max_tool_rounds: maxToolRounds,
|
|
579
|
+
service_tier: serviceTier,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Maps YAML service-tier shorthands to canonical enum names, mirroring
|
|
585
|
+
* HARNESS_SHORTHANDS. Unknown values are authoring errors: the tier
|
|
586
|
+
* exists to make pricing deterministic, so a typo must never silently
|
|
587
|
+
* fall back to the default.
|
|
588
|
+
*/
|
|
589
|
+
const SERVICE_TIER_SHORTHANDS: Record<string, string> = {
|
|
590
|
+
standard: "SERVICE_TIER_STANDARD",
|
|
591
|
+
fast: "SERVICE_TIER_FAST",
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
function parseServiceTier(raw: unknown): string | undefined {
|
|
595
|
+
if (raw === undefined || raw === null) return undefined;
|
|
596
|
+
if (typeof raw !== "string") {
|
|
597
|
+
throw new Error("call:agent 'run_config.service_tier' must be a string");
|
|
598
|
+
}
|
|
599
|
+
const canonical =
|
|
600
|
+
SERVICE_TIER_SHORTHANDS[raw.toLowerCase()] ??
|
|
601
|
+
(Object.values(SERVICE_TIER_SHORTHANDS).includes(raw) ? raw : undefined);
|
|
602
|
+
if (!canonical) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`call:agent 'run_config.service_tier' has unknown value '${raw}' ` +
|
|
605
|
+
`(expected: standard, fast)`,
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
return canonical;
|
|
609
|
+
}
|
|
610
|
+
|
|
500
611
|
function parseHumanInputConfig(taskName: string, raw: unknown): import("./types.js").HumanInputConfig {
|
|
501
612
|
if (!raw || typeof raw !== "object") {
|
|
502
613
|
throw new Error(`human_input task '${taskName}' requires a 'with' configuration block`);
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
* This builder is sandbox-safe — it evaluates jq expressions and
|
|
11
11
|
* delegates to `ctx.callAgent()`. The async completion and signal
|
|
12
12
|
* logic live in the workflow-side orchestrator, not here.
|
|
13
|
-
*
|
|
14
|
-
* Mirrors Go's `CallAgentTaskBuilder` in `task_builder_call_agent.go`.
|
|
15
13
|
*/
|
|
16
14
|
|
|
17
15
|
import type {
|
|
@@ -694,22 +694,55 @@ export type CallAgentFn = (
|
|
|
694
694
|
/**
|
|
695
695
|
* Agent call configuration — mirrors the proto `AgentCallTaskConfig`.
|
|
696
696
|
* Parsed from the YAML `with:` block of a `call: agent` task.
|
|
697
|
+
*
|
|
698
|
+
* The agent may be referenced as "slug" (workflow's org) or "org/slug"
|
|
699
|
+
* (explicit org); there is no separate org field.
|
|
697
700
|
*/
|
|
698
701
|
export interface AgentCallConfig {
|
|
699
702
|
readonly agent: string;
|
|
700
703
|
readonly message: string;
|
|
701
|
-
readonly org?: string;
|
|
702
704
|
readonly env?: Record<string, string>;
|
|
703
|
-
readonly
|
|
705
|
+
readonly run_config?: AgentCallRunConfig;
|
|
704
706
|
readonly output?: AgentCallOutputContract;
|
|
705
707
|
readonly harness?: string;
|
|
708
|
+
readonly workspace_entries?: readonly AgentCallWorkspaceEntry[];
|
|
706
709
|
}
|
|
707
710
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
711
|
+
/**
|
|
712
|
+
* Mirrors the shared `ai.stigmer.agentic.session.v1.WorkspaceEntry` in its
|
|
713
|
+
* git-only form — the workflow surface rejects local_path sources at write
|
|
714
|
+
* time (no client is connected to serve one when a task fires), so by the
|
|
715
|
+
* time YAML reaches the runner only git_repo entries exist.
|
|
716
|
+
*
|
|
717
|
+
* The task's `environment_refs` sibling never appears in execution YAML:
|
|
718
|
+
* the server resolves it from the Workflow row at execution create.
|
|
719
|
+
*/
|
|
720
|
+
export interface AgentCallWorkspaceEntry {
|
|
721
|
+
readonly name?: string;
|
|
722
|
+
readonly source: {
|
|
723
|
+
readonly git_repo: {
|
|
724
|
+
readonly url: string;
|
|
725
|
+
readonly branch?: string;
|
|
726
|
+
};
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Mirrors the shared `ai.stigmer.agentic.agentexecution.v1.RunConfig`
|
|
732
|
+
* (issue #358) — the same run-bound vocabulary schedules embed. Every
|
|
733
|
+
* field maps onto its `ExecutionConfig` namesake and is enforced by the
|
|
734
|
+
* runner's generic cost/tool-round guards; nothing here is decorative.
|
|
735
|
+
*/
|
|
736
|
+
export interface AgentCallRunConfig {
|
|
737
|
+
readonly model_name?: string;
|
|
738
|
+
readonly max_cost_usd?: number;
|
|
739
|
+
readonly max_tool_rounds?: number;
|
|
740
|
+
/**
|
|
741
|
+
* Canonical ServiceTier enum name ("SERVICE_TIER_STANDARD" /
|
|
742
|
+
* "SERVICE_TIER_FAST"); the loader maps the YAML shorthands
|
|
743
|
+
* ("standard"/"fast") to these, mirroring harness.
|
|
744
|
+
*/
|
|
745
|
+
readonly service_tier?: string;
|
|
713
746
|
}
|
|
714
747
|
|
|
715
748
|
export interface AgentCallOutputContract {
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* 4. Updating workflow approval status via local activities
|
|
9
9
|
* 5. Returning the agent result when the activity completes
|
|
10
10
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
11
|
+
* The workflow engine's CallAgentTaskBuilder (workflow-engine/tasks/
|
|
12
|
+
* call-agent.ts) calls `ctx.callAgent()`, which is wired to
|
|
13
|
+
* `orchestrateAgentCall()` in execute-serverless-workflow.ts.
|
|
13
14
|
*
|
|
14
15
|
* SANDBOX RULES: Only @temporalio/workflow imports, type-only imports,
|
|
15
16
|
* and pure logic. No Node.js built-ins.
|
|
@@ -54,6 +55,11 @@ type AgentActivities = ReturnType<typeof createCallAgentActivities>;
|
|
|
54
55
|
type StatusActivities = ReturnType<typeof createCallAgentStatusActivities>;
|
|
55
56
|
type EventActivities = ReturnType<typeof createWorkflowEventActivities>;
|
|
56
57
|
|
|
58
|
+
// The 1h ceiling is a deliberate platform constant, not a user knob: the
|
|
59
|
+
// old AgentCallTaskConfig declared a per-task `timeout` that nothing ever
|
|
60
|
+
// read, and #358 deleted it rather than declaring what isn't honored. The
|
|
61
|
+
// real per-run bound is run_config.max_cost_usd, enforced inside the
|
|
62
|
+
// agent execution itself.
|
|
57
63
|
const agentProxy = proxyActivities<AgentActivities>({
|
|
58
64
|
startToCloseTimeout: "1h",
|
|
59
65
|
retry: {
|