@agentteams/runner 0.0.70 → 0.0.71-dev.150

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.
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test, { mock } from "node:test";
3
- import { mkdtemp, mkdir, writeFile, rm, readFile, access } from "node:fs/promises";
3
+ import { mkdtemp, rm } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { logger } from "../logger.js";
@@ -561,491 +561,6 @@ const withTempDir = async (run) => {
561
561
  await rm(dir, { recursive: true, force: true });
562
562
  }
563
563
  };
564
- const withHarnessRuntime = (authPath, overrides = {}) => ({
565
- ...runtime,
566
- authPath,
567
- harnessConfigId: "harness-1",
568
- ...overrides,
569
- });
570
- const fetchServerHarness = (config) => async () => ({ config });
571
- test("createTriggerHandler blocks trigger when pre-hook fails with onFailure=fail", async () => {
572
- await withTempDir(async (dir) => {
573
- const clientCalls = [];
574
- let runnerCalled = false;
575
- const client = {
576
- fetchTriggerRuntime: async () => withHarnessRuntime(dir),
577
- fetchHarnessConfigById: fetchServerHarness({
578
- preHooks: [{ name: "lint-check", command: "exit 1", onFailure: "fail" }],
579
- }),
580
- isTriggerCancelRequested: async () => false,
581
- updateTriggerHistory: async (...args) => {
582
- clientCalls.push({ method: "updateTriggerHistory", args });
583
- },
584
- updateTriggerStatus: async (...args) => {
585
- clientCalls.push({ method: "updateTriggerStatus", args });
586
- },
587
- };
588
- const handler = createTriggerHandler({
589
- config: {
590
- daemonToken: "daemon-token",
591
- apiUrl: "https://api.example",
592
- pollingIntervalMs: 5000,
593
- timeoutMs: 1500,
594
- idleTimeoutMs: 500,
595
- runnerCmd: "opencode",
596
- },
597
- client: client,
598
- }, {
599
- createRunnerFactory: () => () => ({
600
- run: async () => {
601
- runnerCalled = true;
602
- return { exitCode: 0 };
603
- },
604
- }),
605
- createLogReporter: () => ({
606
- start: () => undefined,
607
- append: () => undefined,
608
- stop: async () => undefined,
609
- }),
610
- readHistoryFile: async () => "",
611
- resolveRunnerHistoryPaths: () => ({
612
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
613
- parentHistoryPath: null,
614
- }),
615
- });
616
- await handler({ ...trigger, parentTriggerId: null });
617
- assert.equal(runnerCalled, false, "Runner should not be called when pre-hook fails");
618
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
619
- assert.ok(statusCall);
620
- assert.equal(statusCall.args[1], "FAILED");
621
- assert.ok(String(statusCall.args[2]).includes("lint-check"));
622
- });
623
- });
624
- test("createTriggerHandler continues when pre-hook fails with onFailure=warn", async () => {
625
- await withTempDir(async (dir) => {
626
- let runnerCalled = false;
627
- const clientCalls = [];
628
- const client = {
629
- fetchTriggerRuntime: async () => withHarnessRuntime(dir),
630
- fetchHarnessConfigById: fetchServerHarness({
631
- preHooks: [{ name: "optional-check", command: "exit 1", onFailure: "warn" }],
632
- }),
633
- isTriggerCancelRequested: async () => false,
634
- updateTriggerHistory: async (...args) => {
635
- clientCalls.push({ method: "updateTriggerHistory", args });
636
- },
637
- updateTriggerStatus: async (...args) => {
638
- clientCalls.push({ method: "updateTriggerStatus", args });
639
- },
640
- };
641
- const handler = createTriggerHandler({
642
- config: {
643
- daemonToken: "daemon-token",
644
- apiUrl: "https://api.example",
645
- pollingIntervalMs: 5000,
646
- timeoutMs: 1500,
647
- idleTimeoutMs: 500,
648
- runnerCmd: "opencode",
649
- },
650
- client: client,
651
- }, {
652
- createRunnerFactory: () => () => ({
653
- run: async () => {
654
- runnerCalled = true;
655
- return { exitCode: 0 };
656
- },
657
- }),
658
- createLogReporter: () => ({
659
- start: () => undefined,
660
- append: () => undefined,
661
- stop: async () => undefined,
662
- }),
663
- readHistoryFile: async () => "### Summary\n- done\n",
664
- resolveRunnerHistoryPaths: () => ({
665
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
666
- parentHistoryPath: null,
667
- }),
668
- });
669
- await handler({ ...trigger, parentTriggerId: null });
670
- assert.equal(runnerCalled, true, "Runner should still execute when pre-hook fails with warn");
671
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
672
- assert.ok(statusCall);
673
- assert.equal(statusCall.args[1], "DONE");
674
- });
675
- });
676
- test("createTriggerHandler runs normally when no pre-hooks are defined", async () => {
677
- await withTempDir(async (dir) => {
678
- let runnerCalled = false;
679
- const clientCalls = [];
680
- const client = {
681
- fetchTriggerRuntime: async () => ({ ...runtime, authPath: dir }),
682
- isTriggerCancelRequested: async () => false,
683
- updateTriggerHistory: async (...args) => {
684
- clientCalls.push({ method: "updateTriggerHistory", args });
685
- },
686
- updateTriggerStatus: async (...args) => {
687
- clientCalls.push({ method: "updateTriggerStatus", args });
688
- },
689
- };
690
- const handler = createTriggerHandler({
691
- config: {
692
- daemonToken: "daemon-token",
693
- apiUrl: "https://api.example",
694
- pollingIntervalMs: 5000,
695
- timeoutMs: 1500,
696
- idleTimeoutMs: 500,
697
- runnerCmd: "opencode",
698
- },
699
- client: client,
700
- }, {
701
- createRunnerFactory: () => () => ({
702
- run: async () => {
703
- runnerCalled = true;
704
- return { exitCode: 0 };
705
- },
706
- }),
707
- createLogReporter: () => ({
708
- start: () => undefined,
709
- append: () => undefined,
710
- stop: async () => undefined,
711
- }),
712
- readHistoryFile: async () => "### Summary\n- done\n",
713
- resolveRunnerHistoryPaths: () => ({
714
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
715
- parentHistoryPath: null,
716
- }),
717
- });
718
- await handler({ ...trigger, parentTriggerId: null });
719
- assert.equal(runnerCalled, true);
720
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
721
- assert.ok(statusCall);
722
- assert.equal(statusCall.args[1], "DONE");
723
- });
724
- });
725
- test("createTriggerHandler ignores local harness file when no server harness is selected", async () => {
726
- await withTempDir(async (dir) => {
727
- const harnessDir = join(dir, ".agentteams");
728
- await mkdir(harnessDir, { recursive: true });
729
- await writeFile(join(harnessDir, "harness.yml"), [
730
- "preHooks:",
731
- " - name: local-should-not-run",
732
- " command: sh -c 'echo local > local.txt && exit 1'",
733
- " onFailure: fail",
734
- ].join("\n"));
735
- let runnerCalled = false;
736
- const clientCalls = [];
737
- const client = {
738
- fetchTriggerRuntime: async () => ({ ...runtime, authPath: dir, harnessConfigId: null }),
739
- isTriggerCancelRequested: async () => false,
740
- updateTriggerHistory: async (...args) => {
741
- clientCalls.push({ method: "updateTriggerHistory", args });
742
- },
743
- updateTriggerStatus: async (...args) => {
744
- clientCalls.push({ method: "updateTriggerStatus", args });
745
- },
746
- };
747
- const handler = createTriggerHandler({
748
- config: {
749
- daemonToken: "daemon-token",
750
- apiUrl: "https://api.example",
751
- pollingIntervalMs: 5000,
752
- timeoutMs: 1500,
753
- idleTimeoutMs: 500,
754
- runnerCmd: "opencode",
755
- },
756
- client: client,
757
- }, {
758
- createRunnerFactory: () => () => ({
759
- run: async () => {
760
- runnerCalled = true;
761
- return { exitCode: 0 };
762
- },
763
- }),
764
- createLogReporter: () => ({
765
- start: () => undefined,
766
- append: () => undefined,
767
- stop: async () => undefined,
768
- }),
769
- readHistoryFile: async () => "### Summary\n- done\n",
770
- resolveRunnerHistoryPaths: () => ({
771
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
772
- parentHistoryPath: null,
773
- }),
774
- });
775
- await handler({ ...trigger, parentTriggerId: null });
776
- assert.equal(runnerCalled, true);
777
- await assert.rejects(access(join(dir, "local.txt")));
778
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
779
- assert.ok(statusCall);
780
- assert.equal(statusCall.args[1], "DONE");
781
- });
782
- });
783
- test("createTriggerHandler always runs pre-hooks without a convention trigger", async () => {
784
- await withTempDir(async (dir) => {
785
- let runnerCalled = false;
786
- const clientCalls = [];
787
- const client = {
788
- fetchTriggerRuntime: async () => withHarnessRuntime(dir, {
789
- conventions: [],
790
- planType: "BUG_FIX",
791
- }),
792
- fetchHarnessConfigById: fetchServerHarness({
793
- preHooks: [{ name: "always-run", command: "sh -c 'echo always > always.txt && exit 1'", onFailure: "fail" }],
794
- }),
795
- isTriggerCancelRequested: async () => false,
796
- updateTriggerHistory: async (...args) => {
797
- clientCalls.push({ method: "updateTriggerHistory", args });
798
- },
799
- updateTriggerStatus: async (...args) => {
800
- clientCalls.push({ method: "updateTriggerStatus", args });
801
- },
802
- };
803
- const handler = createTriggerHandler({
804
- config: {
805
- daemonToken: "daemon-token",
806
- apiUrl: "https://api.example",
807
- pollingIntervalMs: 5000,
808
- timeoutMs: 1500,
809
- idleTimeoutMs: 500,
810
- runnerCmd: "opencode",
811
- },
812
- client: client,
813
- }, {
814
- createRunnerFactory: () => () => ({
815
- run: async () => {
816
- runnerCalled = true;
817
- return { exitCode: 0 };
818
- },
819
- }),
820
- createLogReporter: () => ({
821
- start: () => undefined,
822
- append: () => undefined,
823
- stop: async () => undefined,
824
- }),
825
- readHistoryFile: async () => "",
826
- resolveRunnerHistoryPaths: () => ({
827
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
828
- parentHistoryPath: null,
829
- }),
830
- });
831
- await handler({ ...trigger, parentTriggerId: null });
832
- assert.equal(runnerCalled, false);
833
- assert.equal(await readFile(join(dir, "always.txt"), "utf8"), "always\n");
834
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
835
- assert.ok(statusCall);
836
- assert.equal(statusCall.args[1], "FAILED");
837
- assert.match(String(statusCall.args[2]), /always-run/);
838
- });
839
- });
840
- test("createTriggerHandler runs convention-linked pre-hooks when the convention matches", async () => {
841
- await withTempDir(async (dir) => {
842
- let runnerCalled = false;
843
- const clientCalls = [];
844
- const conventions = [{
845
- id: "conv-bugfix",
846
- filePath: ".agentteams/rules/bugfix.md",
847
- trigger: "task:BUG_FIX",
848
- title: "Bug Fix Convention",
849
- description: "Rules for bug fix tasks",
850
- }];
851
- const client = {
852
- fetchTriggerRuntime: async () => withHarnessRuntime(dir, {
853
- conventions,
854
- planType: "BUG_FIX",
855
- }),
856
- fetchHarnessConfigById: fetchServerHarness({
857
- preHooks: [{
858
- name: "only-on-bugfix",
859
- command: "sh -c 'echo bugfix > bugfix.txt && exit 1'",
860
- onFailure: "fail",
861
- conventionTrigger: "task:BUG_FIX",
862
- }],
863
- }),
864
- isTriggerCancelRequested: async () => false,
865
- updateTriggerHistory: async (...args) => {
866
- clientCalls.push({ method: "updateTriggerHistory", args });
867
- },
868
- updateTriggerStatus: async (...args) => {
869
- clientCalls.push({ method: "updateTriggerStatus", args });
870
- },
871
- };
872
- const handler = createTriggerHandler({
873
- config: {
874
- daemonToken: "daemon-token",
875
- apiUrl: "https://api.example",
876
- pollingIntervalMs: 5000,
877
- timeoutMs: 1500,
878
- idleTimeoutMs: 500,
879
- runnerCmd: "opencode",
880
- },
881
- client: client,
882
- }, {
883
- createRunnerFactory: () => () => ({
884
- run: async () => {
885
- runnerCalled = true;
886
- return { exitCode: 0 };
887
- },
888
- }),
889
- createLogReporter: () => ({
890
- start: () => undefined,
891
- append: () => undefined,
892
- stop: async () => undefined,
893
- }),
894
- readHistoryFile: async () => "",
895
- resolveRunnerHistoryPaths: () => ({
896
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
897
- parentHistoryPath: null,
898
- }),
899
- });
900
- await handler({ ...trigger, parentTriggerId: null });
901
- assert.equal(runnerCalled, false);
902
- assert.equal(await readFile(join(dir, "bugfix.txt"), "utf8"), "bugfix\n");
903
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
904
- assert.ok(statusCall);
905
- assert.equal(statusCall.args[1], "FAILED");
906
- assert.match(String(statusCall.args[2]), /only-on-bugfix/);
907
- });
908
- });
909
- test("createTriggerHandler skips convention-linked pre-hooks when the convention does not match", async () => {
910
- await withTempDir(async (dir) => {
911
- let runnerCalled = false;
912
- const clientCalls = [];
913
- const conventions = [{
914
- id: "conv-bugfix",
915
- filePath: ".agentteams/rules/bugfix.md",
916
- trigger: "task:BUG_FIX",
917
- title: "Bug Fix Convention",
918
- description: "Rules for bug fix tasks",
919
- }];
920
- const client = {
921
- fetchTriggerRuntime: async () => withHarnessRuntime(dir, {
922
- conventions,
923
- planType: "BUG_FIX",
924
- }),
925
- fetchHarnessConfigById: fetchServerHarness({
926
- preHooks: [{
927
- name: "only-on-feature",
928
- command: "sh -c 'echo feature > feature.txt && exit 1'",
929
- onFailure: "fail",
930
- conventionTrigger: "task:FEATURE",
931
- }],
932
- }),
933
- isTriggerCancelRequested: async () => false,
934
- updateTriggerHistory: async (...args) => {
935
- clientCalls.push({ method: "updateTriggerHistory", args });
936
- },
937
- updateTriggerStatus: async (...args) => {
938
- clientCalls.push({ method: "updateTriggerStatus", args });
939
- },
940
- };
941
- const handler = createTriggerHandler({
942
- config: {
943
- daemonToken: "daemon-token",
944
- apiUrl: "https://api.example",
945
- pollingIntervalMs: 5000,
946
- timeoutMs: 1500,
947
- idleTimeoutMs: 500,
948
- runnerCmd: "opencode",
949
- },
950
- client: client,
951
- }, {
952
- createRunnerFactory: () => () => ({
953
- run: async () => {
954
- runnerCalled = true;
955
- return { exitCode: 0 };
956
- },
957
- }),
958
- createLogReporter: () => ({
959
- start: () => undefined,
960
- append: () => undefined,
961
- stop: async () => undefined,
962
- }),
963
- readHistoryFile: async () => "### Summary\n- done\n",
964
- resolveRunnerHistoryPaths: () => ({
965
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
966
- parentHistoryPath: null,
967
- }),
968
- });
969
- await handler({ ...trigger, parentTriggerId: null });
970
- assert.equal(runnerCalled, true);
971
- await assert.rejects(access(join(dir, "feature.txt")));
972
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
973
- assert.ok(statusCall);
974
- assert.equal(statusCall.args[1], "DONE");
975
- });
976
- });
977
- test("createTriggerHandler runs unconditional hooks while skipping non-matching conditional hooks", async () => {
978
- await withTempDir(async (dir) => {
979
- let runnerCalled = false;
980
- const clientCalls = [];
981
- const conventions = [{
982
- id: "conv-bugfix",
983
- filePath: ".agentteams/rules/bugfix.md",
984
- trigger: "task:BUG_FIX",
985
- title: "Bug Fix Convention",
986
- description: "Rules for bug fix tasks",
987
- }];
988
- const client = {
989
- fetchTriggerRuntime: async () => withHarnessRuntime(dir, {
990
- conventions,
991
- planType: "BUG_FIX",
992
- }),
993
- fetchHarnessConfigById: fetchServerHarness({
994
- preHooks: [
995
- { name: "always-run", command: "sh -c 'echo always > always.txt'", onFailure: "warn" },
996
- {
997
- name: "only-on-feature",
998
- command: "sh -c 'echo feature > feature.txt && exit 1'",
999
- onFailure: "fail",
1000
- conventionTrigger: "task:FEATURE",
1001
- },
1002
- ],
1003
- }),
1004
- isTriggerCancelRequested: async () => false,
1005
- updateTriggerHistory: async (...args) => {
1006
- clientCalls.push({ method: "updateTriggerHistory", args });
1007
- },
1008
- updateTriggerStatus: async (...args) => {
1009
- clientCalls.push({ method: "updateTriggerStatus", args });
1010
- },
1011
- };
1012
- const handler = createTriggerHandler({
1013
- config: {
1014
- daemonToken: "daemon-token",
1015
- apiUrl: "https://api.example",
1016
- pollingIntervalMs: 5000,
1017
- timeoutMs: 1500,
1018
- idleTimeoutMs: 500,
1019
- runnerCmd: "opencode",
1020
- },
1021
- client: client,
1022
- }, {
1023
- createRunnerFactory: () => () => ({
1024
- run: async () => {
1025
- runnerCalled = true;
1026
- return { exitCode: 0 };
1027
- },
1028
- }),
1029
- createLogReporter: () => ({
1030
- start: () => undefined,
1031
- append: () => undefined,
1032
- stop: async () => undefined,
1033
- }),
1034
- readHistoryFile: async () => "### Summary\n- done\n",
1035
- resolveRunnerHistoryPaths: () => ({
1036
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
1037
- parentHistoryPath: null,
1038
- }),
1039
- });
1040
- await handler({ ...trigger, parentTriggerId: null });
1041
- assert.equal(runnerCalled, true);
1042
- assert.equal(await readFile(join(dir, "always.txt"), "utf8"), "always\n");
1043
- await assert.rejects(access(join(dir, "feature.txt")));
1044
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
1045
- assert.ok(statusCall);
1046
- assert.equal(statusCall.args[1], "DONE");
1047
- });
1048
- });
1049
564
  test("createTriggerHandler passes the API-provided runner prompt unchanged", async () => {
1050
565
  await withTempDir(async (dir) => {
1051
566
  const runnerInputs = [];
@@ -1148,267 +663,4 @@ test("createTriggerHandler does not append history or convention text to the API
1148
663
  assert.doesNotMatch(runnerInputs[0]?.prompt ?? "", /Context-Matched Conventions \(AUTO-LOADED\)/);
1149
664
  });
1150
665
  });
1151
- // ---------------------------------------------------------------------------
1152
- // Post-execution hook tests
1153
- // ---------------------------------------------------------------------------
1154
- test("createTriggerHandler marks DONE when post-hook succeeds after runner success", async () => {
1155
- await withTempDir(async (dir) => {
1156
- const clientCalls = [];
1157
- const client = {
1158
- fetchTriggerRuntime: async () => withHarnessRuntime(dir),
1159
- fetchHarnessConfigById: fetchServerHarness({
1160
- postHooks: [{ name: "quality-gate", command: "echo ok", onFailure: "fail" }],
1161
- }),
1162
- isTriggerCancelRequested: async () => false,
1163
- updateTriggerHistory: async (...args) => {
1164
- clientCalls.push({ method: "updateTriggerHistory", args });
1165
- },
1166
- updateTriggerStatus: async (...args) => {
1167
- clientCalls.push({ method: "updateTriggerStatus", args });
1168
- },
1169
- };
1170
- const handler = createTriggerHandler({
1171
- config: { daemonToken: "t", apiUrl: "https://api.example", pollingIntervalMs: 5000, timeoutMs: 1500, idleTimeoutMs: 500, runnerCmd: "opencode" },
1172
- client: client,
1173
- }, {
1174
- createRunnerFactory: () => () => ({ run: async () => ({ exitCode: 0 }) }),
1175
- createLogReporter: () => ({ start: () => undefined, append: () => undefined, stop: async () => undefined }),
1176
- readHistoryFile: async () => "### Summary\n- done\n",
1177
- resolveRunnerHistoryPaths: () => ({ currentHistoryPath: join(dir, "h.md"), parentHistoryPath: null }),
1178
- });
1179
- await handler({ ...trigger, parentTriggerId: null });
1180
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
1181
- assert.ok(statusCall);
1182
- assert.equal(statusCall.args[1], "DONE");
1183
- });
1184
- });
1185
- test("createTriggerHandler marks FAILED when post-hook fails with onFailure=fail", async () => {
1186
- await withTempDir(async (dir) => {
1187
- const clientCalls = [];
1188
- const client = {
1189
- fetchTriggerRuntime: async () => withHarnessRuntime(dir),
1190
- fetchHarnessConfigById: fetchServerHarness({
1191
- postHooks: [{ name: "quality-gate", command: "exit 1", onFailure: "fail" }],
1192
- }),
1193
- isTriggerCancelRequested: async () => false,
1194
- updateTriggerHistory: async (...args) => {
1195
- clientCalls.push({ method: "updateTriggerHistory", args });
1196
- },
1197
- updateTriggerStatus: async (...args) => {
1198
- clientCalls.push({ method: "updateTriggerStatus", args });
1199
- },
1200
- };
1201
- const handler = createTriggerHandler({
1202
- config: { daemonToken: "t", apiUrl: "https://api.example", pollingIntervalMs: 5000, timeoutMs: 1500, idleTimeoutMs: 500, runnerCmd: "opencode" },
1203
- client: client,
1204
- }, {
1205
- createRunnerFactory: () => () => ({ run: async () => ({ exitCode: 0 }) }),
1206
- createLogReporter: () => ({ start: () => undefined, append: () => undefined, stop: async () => undefined }),
1207
- readHistoryFile: async () => "### Summary\n- done\n",
1208
- resolveRunnerHistoryPaths: () => ({ currentHistoryPath: join(dir, "h.md"), parentHistoryPath: null }),
1209
- });
1210
- await handler({ ...trigger, parentTriggerId: null });
1211
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
1212
- assert.ok(statusCall);
1213
- assert.equal(statusCall.args[1], "FAILED");
1214
- assert.ok(String(statusCall.args[2]).includes("quality-gate"));
1215
- });
1216
- });
1217
- test("createTriggerHandler marks NEEDS_REVIEW when post-hook fails with onFailure=needs_review", async () => {
1218
- await withTempDir(async (dir) => {
1219
- const clientCalls = [];
1220
- const client = {
1221
- fetchTriggerRuntime: async () => withHarnessRuntime(dir),
1222
- fetchHarnessConfigById: fetchServerHarness({
1223
- postHooks: [{ name: "review-gate", command: "exit 1", onFailure: "needs_review" }],
1224
- }),
1225
- isTriggerCancelRequested: async () => false,
1226
- updateTriggerHistory: async (...args) => {
1227
- clientCalls.push({ method: "updateTriggerHistory", args });
1228
- },
1229
- updateTriggerStatus: async (...args) => {
1230
- clientCalls.push({ method: "updateTriggerStatus", args });
1231
- },
1232
- };
1233
- const handler = createTriggerHandler({
1234
- config: { daemonToken: "t", apiUrl: "https://api.example", pollingIntervalMs: 5000, timeoutMs: 1500, idleTimeoutMs: 500, runnerCmd: "opencode" },
1235
- client: client,
1236
- }, {
1237
- createRunnerFactory: () => () => ({ run: async () => ({ exitCode: 0 }) }),
1238
- createLogReporter: () => ({ start: () => undefined, append: () => undefined, stop: async () => undefined }),
1239
- readHistoryFile: async () => "### Summary\n- done\n",
1240
- resolveRunnerHistoryPaths: () => ({ currentHistoryPath: join(dir, "h.md"), parentHistoryPath: null }),
1241
- });
1242
- await handler({ ...trigger, parentTriggerId: null });
1243
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
1244
- assert.ok(statusCall);
1245
- assert.equal(statusCall.args[1], "NEEDS_REVIEW");
1246
- });
1247
- });
1248
- test("createTriggerHandler skips post-hooks when runner fails", async () => {
1249
- await withTempDir(async (dir) => {
1250
- const clientCalls = [];
1251
- const logEntries = [];
1252
- const client = {
1253
- fetchTriggerRuntime: async () => withHarnessRuntime(dir),
1254
- fetchHarnessConfigById: fetchServerHarness({
1255
- postHooks: [{ name: "should-not-run", command: "echo unreachable", onFailure: "fail" }],
1256
- }),
1257
- isTriggerCancelRequested: async () => false,
1258
- updateTriggerHistory: async (...args) => {
1259
- clientCalls.push({ method: "updateTriggerHistory", args });
1260
- },
1261
- updateTriggerStatus: async (...args) => {
1262
- clientCalls.push({ method: "updateTriggerStatus", args });
1263
- },
1264
- };
1265
- const handler = createTriggerHandler({
1266
- config: { daemonToken: "t", apiUrl: "https://api.example", pollingIntervalMs: 5000, timeoutMs: 1500, idleTimeoutMs: 500, runnerCmd: "opencode" },
1267
- client: client,
1268
- }, {
1269
- createRunnerFactory: () => () => ({ run: async () => ({ exitCode: 1 }) }),
1270
- createLogReporter: () => ({
1271
- start: () => undefined,
1272
- append: (_level, msg) => { logEntries.push(msg); },
1273
- stop: async () => undefined,
1274
- }),
1275
- readHistoryFile: async () => "",
1276
- resolveRunnerHistoryPaths: () => ({ currentHistoryPath: join(dir, "h.md"), parentHistoryPath: null }),
1277
- });
1278
- await handler({ ...trigger, parentTriggerId: null });
1279
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
1280
- assert.ok(statusCall);
1281
- assert.equal(statusCall.args[1], "FAILED");
1282
- assert.equal(logEntries.some((l) => l.includes("post-execution")), false, "Post-hooks should not run when runner fails");
1283
- });
1284
- });
1285
- test("createTriggerHandler leaves prompt convention audit recording to the API", async () => {
1286
- await withTempDir(async (dir) => {
1287
- const conventions = [
1288
- {
1289
- id: "conv-auto",
1290
- filePath: ".agentteams/rules/bugfix.md",
1291
- trigger: "task:BUG_FIX",
1292
- title: "Auto Match Conv",
1293
- description: null,
1294
- },
1295
- {
1296
- id: "conv-pinned",
1297
- filePath: ".agentteams/rules/pinned.md",
1298
- trigger: "always_on",
1299
- title: "Pinned Conv",
1300
- description: null,
1301
- },
1302
- {
1303
- id: "conv-unrelated",
1304
- filePath: ".agentteams/rules/unused.md",
1305
- trigger: "task:FEATURE",
1306
- title: "Unrelated Conv",
1307
- description: null,
1308
- },
1309
- ];
1310
- let recordCalled = false;
1311
- const client = {
1312
- fetchTriggerRuntime: async () => withHarnessRuntime(dir, {
1313
- conventions,
1314
- planType: "BUG_FIX",
1315
- }),
1316
- fetchHarnessConfigById: fetchServerHarness({
1317
- conventionIds: ["conv-pinned"],
1318
- }),
1319
- isTriggerCancelRequested: async () => false,
1320
- updateTriggerHistory: async () => undefined,
1321
- updateTriggerStatus: async () => undefined,
1322
- recordInjectedConventions: async () => {
1323
- recordCalled = true;
1324
- },
1325
- };
1326
- const handler = createTriggerHandler({
1327
- config: {
1328
- daemonToken: "daemon-token",
1329
- apiUrl: "https://api.example",
1330
- pollingIntervalMs: 5000,
1331
- timeoutMs: 1500,
1332
- idleTimeoutMs: 500,
1333
- runnerCmd: "opencode",
1334
- },
1335
- client: client,
1336
- }, {
1337
- createRunnerFactory: () => () => ({
1338
- run: async () => ({ exitCode: 0 }),
1339
- }),
1340
- createLogReporter: () => ({
1341
- start: () => undefined,
1342
- append: () => undefined,
1343
- stop: async () => undefined,
1344
- }),
1345
- readHistoryFile: async () => "### Summary\n- done\n",
1346
- resolveRunnerHistoryPaths: () => ({
1347
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
1348
- parentHistoryPath: null,
1349
- }),
1350
- });
1351
- await handler({ ...trigger, parentTriggerId: null });
1352
- assert.equal(recordCalled, false);
1353
- });
1354
- });
1355
- test("createTriggerHandler ignores unavailable prompt convention recording client method", async () => {
1356
- await withTempDir(async (dir) => {
1357
- const conventions = [
1358
- {
1359
- id: "conv-pinned",
1360
- filePath: ".agentteams/rules/pinned.md",
1361
- trigger: null,
1362
- title: "Pinned Conv",
1363
- description: null,
1364
- },
1365
- ];
1366
- const clientCalls = [];
1367
- const client = {
1368
- fetchTriggerRuntime: async () => withHarnessRuntime(dir, {
1369
- conventions,
1370
- planType: "BUG_FIX",
1371
- }),
1372
- fetchHarnessConfigById: fetchServerHarness({
1373
- conventionIds: ["conv-pinned"],
1374
- }),
1375
- isTriggerCancelRequested: async () => false,
1376
- updateTriggerHistory: async (...args) => {
1377
- clientCalls.push({ method: "updateTriggerHistory", args });
1378
- },
1379
- updateTriggerStatus: async (...args) => {
1380
- clientCalls.push({ method: "updateTriggerStatus", args });
1381
- },
1382
- };
1383
- const handler = createTriggerHandler({
1384
- config: {
1385
- daemonToken: "daemon-token",
1386
- apiUrl: "https://api.example",
1387
- pollingIntervalMs: 5000,
1388
- timeoutMs: 1500,
1389
- idleTimeoutMs: 500,
1390
- runnerCmd: "opencode",
1391
- },
1392
- client: client,
1393
- }, {
1394
- createRunnerFactory: () => () => ({
1395
- run: async () => ({ exitCode: 0 }),
1396
- }),
1397
- createLogReporter: () => ({
1398
- start: () => undefined,
1399
- append: () => undefined,
1400
- stop: async () => undefined,
1401
- }),
1402
- readHistoryFile: async () => "### Summary\n- done\n",
1403
- resolveRunnerHistoryPaths: () => ({
1404
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
1405
- parentHistoryPath: null,
1406
- }),
1407
- });
1408
- await handler({ ...trigger, parentTriggerId: null });
1409
- const statusCall = clientCalls.find((c) => c.method === "updateTriggerStatus");
1410
- assert.ok(statusCall);
1411
- assert.equal(statusCall.args[1], "DONE");
1412
- });
1413
- });
1414
666
  //# sourceMappingURL=trigger-handler.test.js.map