@sweny-ai/core 0.1.150 → 0.1.152

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/schema.d.ts CHANGED
@@ -850,7 +850,7 @@ export declare const nodePermissionsZ: z.ZodUnion<[z.ZodEnum<["read", "write"]>,
850
850
  }>]>;
851
851
  /** One typed write intent a node may emit (#365). */
852
852
  export declare const safeOutputDeclarationZ: z.ZodObject<{
853
- type: z.ZodEnum<["comment", "issue", "pr", "label"]>;
853
+ type: z.ZodEnum<["comment", "issue", "pr", "label", "issue_state"]>;
854
854
  via: z.ZodOptional<z.ZodString>;
855
855
  max: z.ZodOptional<z.ZodNumber>;
856
856
  target: z.ZodOptional<z.ZodString>;
@@ -864,23 +864,26 @@ export declare const safeOutputDeclarationZ: z.ZodObject<{
864
864
  }, {
865
865
  input: string;
866
866
  }>]>>;
867
+ state: z.ZodOptional<z.ZodEnum<["reopen", "close"]>>;
867
868
  }, "strict", z.ZodTypeAny, {
868
- type: "comment" | "issue" | "pr" | "label";
869
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
869
870
  number?: string | number | {
870
871
  input: string;
871
872
  } | undefined;
872
873
  labels?: string[] | undefined;
874
+ state?: "reopen" | "close" | undefined;
873
875
  max?: number | undefined;
874
876
  via?: string | undefined;
875
877
  target?: string | undefined;
876
878
  title_prefix?: string | undefined;
877
879
  expires?: string | undefined;
878
880
  }, {
879
- type: "comment" | "issue" | "pr" | "label";
881
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
880
882
  number?: string | number | {
881
883
  input: string;
882
884
  } | undefined;
883
885
  labels?: string[] | undefined;
886
+ state?: "reopen" | "close" | undefined;
884
887
  max?: number | undefined;
885
888
  via?: string | undefined;
886
889
  target?: string | undefined;
@@ -889,7 +892,7 @@ export declare const safeOutputDeclarationZ: z.ZodObject<{
889
892
  }>;
890
893
  /** Workflow-level safe-output policy (#365): the ceiling and run-wide limits. */
891
894
  export declare const safeOutputsPolicyZ: z.ZodObject<{
892
- allow: z.ZodOptional<z.ZodArray<z.ZodEnum<["comment", "issue", "pr", "label"]>, "many">>;
895
+ allow: z.ZodOptional<z.ZodArray<z.ZodEnum<["comment", "issue", "pr", "label", "issue_state"]>, "many">>;
893
896
  max: z.ZodOptional<z.ZodNumber>;
894
897
  staged: z.ZodOptional<z.ZodBoolean>;
895
898
  trusted_actors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -897,14 +900,14 @@ export declare const safeOutputsPolicyZ: z.ZodObject<{
897
900
  screen: z.ZodOptional<z.ZodBoolean>;
898
901
  }, "strict", z.ZodTypeAny, {
899
902
  staged?: boolean | undefined;
900
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
903
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
901
904
  max?: number | undefined;
902
905
  trusted_actors?: string[] | undefined;
903
906
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
904
907
  screen?: boolean | undefined;
905
908
  }, {
906
909
  staged?: boolean | undefined;
907
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
910
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
908
911
  max?: number | undefined;
909
912
  trusted_actors?: string[] | undefined;
910
913
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
@@ -978,7 +981,7 @@ export declare const nodeZ: z.ZodObject<{
978
981
  access?: "write" | "read" | undefined;
979
982
  }>]>>;
980
983
  outputs: z.ZodOptional<z.ZodArray<z.ZodObject<{
981
- type: z.ZodEnum<["comment", "issue", "pr", "label"]>;
984
+ type: z.ZodEnum<["comment", "issue", "pr", "label", "issue_state"]>;
982
985
  via: z.ZodOptional<z.ZodString>;
983
986
  max: z.ZodOptional<z.ZodNumber>;
984
987
  target: z.ZodOptional<z.ZodString>;
@@ -992,23 +995,26 @@ export declare const nodeZ: z.ZodObject<{
992
995
  }, {
993
996
  input: string;
994
997
  }>]>>;
998
+ state: z.ZodOptional<z.ZodEnum<["reopen", "close"]>>;
995
999
  }, "strict", z.ZodTypeAny, {
996
- type: "comment" | "issue" | "pr" | "label";
1000
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
997
1001
  number?: string | number | {
998
1002
  input: string;
999
1003
  } | undefined;
1000
1004
  labels?: string[] | undefined;
1005
+ state?: "reopen" | "close" | undefined;
1001
1006
  max?: number | undefined;
1002
1007
  via?: string | undefined;
1003
1008
  target?: string | undefined;
1004
1009
  title_prefix?: string | undefined;
1005
1010
  expires?: string | undefined;
1006
1011
  }, {
1007
- type: "comment" | "issue" | "pr" | "label";
1012
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
1008
1013
  number?: string | number | {
1009
1014
  input: string;
1010
1015
  } | undefined;
1011
1016
  labels?: string[] | undefined;
1017
+ state?: "reopen" | "close" | undefined;
1012
1018
  max?: number | undefined;
1013
1019
  via?: string | undefined;
1014
1020
  target?: string | undefined;
@@ -1428,11 +1434,12 @@ export declare const nodeZ: z.ZodObject<{
1428
1434
  access?: "write" | "read" | undefined;
1429
1435
  } | undefined;
1430
1436
  outputs?: {
1431
- type: "comment" | "issue" | "pr" | "label";
1437
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
1432
1438
  number?: string | number | {
1433
1439
  input: string;
1434
1440
  } | undefined;
1435
1441
  labels?: string[] | undefined;
1442
+ state?: "reopen" | "close" | undefined;
1436
1443
  max?: number | undefined;
1437
1444
  via?: string | undefined;
1438
1445
  target?: string | undefined;
@@ -1541,11 +1548,12 @@ export declare const nodeZ: z.ZodObject<{
1541
1548
  access?: "write" | "read" | undefined;
1542
1549
  } | undefined;
1543
1550
  outputs?: {
1544
- type: "comment" | "issue" | "pr" | "label";
1551
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
1545
1552
  number?: string | number | {
1546
1553
  input: string;
1547
1554
  } | undefined;
1548
1555
  labels?: string[] | undefined;
1556
+ state?: "reopen" | "close" | undefined;
1549
1557
  max?: number | undefined;
1550
1558
  via?: string | undefined;
1551
1559
  target?: string | undefined;
@@ -1731,7 +1739,7 @@ export declare const workflowZ: z.ZodObject<{
1731
1739
  access?: "write" | "read" | undefined;
1732
1740
  }>]>>;
1733
1741
  outputs: z.ZodOptional<z.ZodArray<z.ZodObject<{
1734
- type: z.ZodEnum<["comment", "issue", "pr", "label"]>;
1742
+ type: z.ZodEnum<["comment", "issue", "pr", "label", "issue_state"]>;
1735
1743
  via: z.ZodOptional<z.ZodString>;
1736
1744
  max: z.ZodOptional<z.ZodNumber>;
1737
1745
  target: z.ZodOptional<z.ZodString>;
@@ -1745,23 +1753,26 @@ export declare const workflowZ: z.ZodObject<{
1745
1753
  }, {
1746
1754
  input: string;
1747
1755
  }>]>>;
1756
+ state: z.ZodOptional<z.ZodEnum<["reopen", "close"]>>;
1748
1757
  }, "strict", z.ZodTypeAny, {
1749
- type: "comment" | "issue" | "pr" | "label";
1758
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
1750
1759
  number?: string | number | {
1751
1760
  input: string;
1752
1761
  } | undefined;
1753
1762
  labels?: string[] | undefined;
1763
+ state?: "reopen" | "close" | undefined;
1754
1764
  max?: number | undefined;
1755
1765
  via?: string | undefined;
1756
1766
  target?: string | undefined;
1757
1767
  title_prefix?: string | undefined;
1758
1768
  expires?: string | undefined;
1759
1769
  }, {
1760
- type: "comment" | "issue" | "pr" | "label";
1770
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
1761
1771
  number?: string | number | {
1762
1772
  input: string;
1763
1773
  } | undefined;
1764
1774
  labels?: string[] | undefined;
1775
+ state?: "reopen" | "close" | undefined;
1765
1776
  max?: number | undefined;
1766
1777
  via?: string | undefined;
1767
1778
  target?: string | undefined;
@@ -2181,11 +2192,12 @@ export declare const workflowZ: z.ZodObject<{
2181
2192
  access?: "write" | "read" | undefined;
2182
2193
  } | undefined;
2183
2194
  outputs?: {
2184
- type: "comment" | "issue" | "pr" | "label";
2195
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
2185
2196
  number?: string | number | {
2186
2197
  input: string;
2187
2198
  } | undefined;
2188
2199
  labels?: string[] | undefined;
2200
+ state?: "reopen" | "close" | undefined;
2189
2201
  max?: number | undefined;
2190
2202
  via?: string | undefined;
2191
2203
  target?: string | undefined;
@@ -2294,11 +2306,12 @@ export declare const workflowZ: z.ZodObject<{
2294
2306
  access?: "write" | "read" | undefined;
2295
2307
  } | undefined;
2296
2308
  outputs?: {
2297
- type: "comment" | "issue" | "pr" | "label";
2309
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
2298
2310
  number?: string | number | {
2299
2311
  input: string;
2300
2312
  } | undefined;
2301
2313
  labels?: string[] | undefined;
2314
+ state?: "reopen" | "close" | undefined;
2302
2315
  max?: number | undefined;
2303
2316
  via?: string | undefined;
2304
2317
  target?: string | undefined;
@@ -2587,7 +2600,7 @@ export declare const workflowZ: z.ZodObject<{
2587
2600
  access?: "write" | "read" | undefined;
2588
2601
  }>]>>;
2589
2602
  safe_outputs: z.ZodOptional<z.ZodObject<{
2590
- allow: z.ZodOptional<z.ZodArray<z.ZodEnum<["comment", "issue", "pr", "label"]>, "many">>;
2603
+ allow: z.ZodOptional<z.ZodArray<z.ZodEnum<["comment", "issue", "pr", "label", "issue_state"]>, "many">>;
2591
2604
  max: z.ZodOptional<z.ZodNumber>;
2592
2605
  staged: z.ZodOptional<z.ZodBoolean>;
2593
2606
  trusted_actors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -2595,14 +2608,14 @@ export declare const workflowZ: z.ZodObject<{
2595
2608
  screen: z.ZodOptional<z.ZodBoolean>;
2596
2609
  }, "strict", z.ZodTypeAny, {
2597
2610
  staged?: boolean | undefined;
2598
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
2611
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
2599
2612
  max?: number | undefined;
2600
2613
  trusted_actors?: string[] | undefined;
2601
2614
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
2602
2615
  screen?: boolean | undefined;
2603
2616
  }, {
2604
2617
  staged?: boolean | undefined;
2605
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
2618
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
2606
2619
  max?: number | undefined;
2607
2620
  trusted_actors?: string[] | undefined;
2608
2621
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
@@ -2652,11 +2665,12 @@ export declare const workflowZ: z.ZodObject<{
2652
2665
  access?: "write" | "read" | undefined;
2653
2666
  } | undefined;
2654
2667
  outputs?: {
2655
- type: "comment" | "issue" | "pr" | "label";
2668
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
2656
2669
  number?: string | number | {
2657
2670
  input: string;
2658
2671
  } | undefined;
2659
2672
  labels?: string[] | undefined;
2673
+ state?: "reopen" | "close" | undefined;
2660
2674
  max?: number | undefined;
2661
2675
  via?: string | undefined;
2662
2676
  target?: string | undefined;
@@ -2780,7 +2794,7 @@ export declare const workflowZ: z.ZodObject<{
2780
2794
  }> | undefined;
2781
2795
  safe_outputs?: {
2782
2796
  staged?: boolean | undefined;
2783
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
2797
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
2784
2798
  max?: number | undefined;
2785
2799
  trusted_actors?: string[] | undefined;
2786
2800
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
@@ -2816,11 +2830,12 @@ export declare const workflowZ: z.ZodObject<{
2816
2830
  access?: "write" | "read" | undefined;
2817
2831
  } | undefined;
2818
2832
  outputs?: {
2819
- type: "comment" | "issue" | "pr" | "label";
2833
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
2820
2834
  number?: string | number | {
2821
2835
  input: string;
2822
2836
  } | undefined;
2823
2837
  labels?: string[] | undefined;
2838
+ state?: "reopen" | "close" | undefined;
2824
2839
  max?: number | undefined;
2825
2840
  via?: string | undefined;
2826
2841
  target?: string | undefined;
@@ -2958,7 +2973,7 @@ export declare const workflowZ: z.ZodObject<{
2958
2973
  }> | undefined;
2959
2974
  safe_outputs?: {
2960
2975
  staged?: boolean | undefined;
2961
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
2976
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
2962
2977
  max?: number | undefined;
2963
2978
  trusted_actors?: string[] | undefined;
2964
2979
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
@@ -3022,11 +3037,12 @@ export declare function parseWorkflow(raw: unknown): {
3022
3037
  access?: "write" | "read" | undefined;
3023
3038
  } | undefined;
3024
3039
  outputs?: {
3025
- type: "comment" | "issue" | "pr" | "label";
3040
+ type: "comment" | "issue" | "pr" | "label" | "issue_state";
3026
3041
  number?: string | number | {
3027
3042
  input: string;
3028
3043
  } | undefined;
3029
3044
  labels?: string[] | undefined;
3045
+ state?: "reopen" | "close" | undefined;
3030
3046
  max?: number | undefined;
3031
3047
  via?: string | undefined;
3032
3048
  target?: string | undefined;
@@ -3150,7 +3166,7 @@ export declare function parseWorkflow(raw: unknown): {
3150
3166
  }> | undefined;
3151
3167
  safe_outputs?: {
3152
3168
  staged?: boolean | undefined;
3153
- allow?: ("comment" | "issue" | "pr" | "label")[] | undefined;
3169
+ allow?: ("comment" | "issue" | "pr" | "label" | "issue_state")[] | undefined;
3154
3170
  max?: number | undefined;
3155
3171
  trusted_actors?: string[] | undefined;
3156
3172
  trusted_associations?: ("OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "FIRST_TIMER" | "NONE")[] | undefined;
@@ -3425,7 +3441,7 @@ export declare const workflowJsonSchema: {
3425
3441
  readonly properties: {
3426
3442
  readonly type: {
3427
3443
  readonly type: "string";
3428
- readonly enum: readonly ["comment", "issue", "pr", "label"];
3444
+ readonly enum: readonly ["comment", "issue", "pr", "label", "issue_state"];
3429
3445
  };
3430
3446
  readonly via: {
3431
3447
  readonly type: "string";
@@ -3464,7 +3480,7 @@ export declare const workflowJsonSchema: {
3464
3480
  readonly description: "Drop an intent older than this when the write stage runs (e.g. 30m, 2h, 7d).";
3465
3481
  };
3466
3482
  readonly number: {
3467
- readonly description: "comment / label: the only issue or PR this output may write to (GitHub number, Linear identifier), or { input: <name> } to pin it to a run input.";
3483
+ readonly description: "comment / label / issue_state: the only issue or PR this output may write to (GitHub number, Linear identifier), or { input: <name> } to pin it to a run input.";
3468
3484
  readonly oneOf: readonly [{
3469
3485
  readonly type: "string";
3470
3486
  readonly minLength: 1;
@@ -3483,6 +3499,11 @@ export declare const workflowJsonSchema: {
3483
3499
  };
3484
3500
  }];
3485
3501
  };
3502
+ readonly state: {
3503
+ readonly type: "string";
3504
+ readonly enum: readonly ["reopen", "close"];
3505
+ readonly description: "issue_state: the one change this output may make (reopen or close). Default: either.";
3506
+ };
3486
3507
  };
3487
3508
  };
3488
3509
  };
@@ -3545,7 +3566,7 @@ export declare const workflowJsonSchema: {
3545
3566
  readonly type: "array";
3546
3567
  readonly items: {
3547
3568
  readonly type: "string";
3548
- readonly enum: readonly ["comment", "issue", "pr", "label"];
3569
+ readonly enum: readonly ["comment", "issue", "pr", "label", "issue_state"];
3549
3570
  };
3550
3571
  readonly minItems: 1;
3551
3572
  readonly description: "Output types any node may declare. Absent: all types.";
package/dist/schema.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * parsing untrusted input (JSON/YAML import, Studio editor).
8
8
  */
9
9
  import { z } from "zod";
10
- import { AUTHOR_ASSOCIATIONS, EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ACCESS, NODE_ON_FAIL, REQUIRES_ON_FAIL, SAFE_OUTPUT_APPLIERS, SAFE_OUTPUT_EXPIRES_PATTERN, SAFE_OUTPUT_MAX_CEILING, SAFE_OUTPUT_TYPES, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, TOOL_CLASSES, WORKFLOW_TYPES, } from "./types.js";
10
+ import { AUTHOR_ASSOCIATIONS, EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ACCESS, NODE_ON_FAIL, REQUIRES_ON_FAIL, SAFE_OUTPUT_APPLIERS, SAFE_OUTPUT_EXPIRES_PATTERN, SAFE_OUTPUT_MAX_CEILING, SAFE_OUTPUT_STATES, SAFE_OUTPUT_TYPES, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, TOOL_CLASSES, WORKFLOW_TYPES, } from "./types.js";
11
11
  import { sourceZ } from "./sources.js";
12
12
  import { workflowInputsZ, WORKFLOW_INPUT_TYPES } from "./inputs.js";
13
13
  export { sourceZ };
@@ -275,6 +275,7 @@ export const safeOutputDeclarationZ = z
275
275
  number: z
276
276
  .union([z.string().min(1), z.number().int().min(1), z.object({ input: z.string().min(1) }).strict()])
277
277
  .optional(),
278
+ state: z.enum(SAFE_OUTPUT_STATES).optional(),
278
279
  })
279
280
  .strict();
280
281
  /** Workflow-level safe-output policy (#365): the ceiling and run-wide limits. */
@@ -636,10 +637,26 @@ export function validateWorkflow(workflow, knownSkills) {
636
637
  nodeId,
637
638
  });
638
639
  }
639
- if (out.number !== undefined && out.type !== "comment" && out.type !== "label") {
640
+ if (out.number !== undefined && out.type !== "comment" && out.type !== "label" && out.type !== "issue_state") {
640
641
  errors.push({
641
642
  code: "UNSUPPORTED_OUTPUT",
642
- message: `Node "${nodeId}" output "${out.type}" pins number, which only applies to comment and label outputs`,
643
+ message: `Node "${nodeId}" output "${out.type}" pins number, which only applies to comment, label and issue_state outputs`,
644
+ nodeId,
645
+ });
646
+ }
647
+ // Closing is destructive: an injected agent must not pick the issue. Only a
648
+ // reopen-only output may rely on the issue named in the request.
649
+ if (out.type === "issue_state" && out.state !== "reopen" && out.number === undefined) {
650
+ errors.push({
651
+ code: "UNSUPPORTED_OUTPUT",
652
+ message: `Node "${nodeId}" output "issue_state" can close issues, so it must pin number (a literal or { input: name }); only state: reopen may omit the pin`,
653
+ nodeId,
654
+ });
655
+ }
656
+ if (out.state !== undefined && out.type !== "issue_state") {
657
+ errors.push({
658
+ code: "UNSUPPORTED_OUTPUT",
659
+ message: `Node "${nodeId}" output "${out.type}" sets state, which only applies to issue_state outputs`,
643
660
  nodeId,
644
661
  });
645
662
  }
@@ -866,7 +883,7 @@ export const workflowJsonSchema = {
866
883
  description: "Drop an intent older than this when the write stage runs (e.g. 30m, 2h, 7d).",
867
884
  },
868
885
  number: {
869
- description: "comment / label: the only issue or PR this output may write to (GitHub number, Linear identifier), or { input: <name> } to pin it to a run input.",
886
+ description: "comment / label / issue_state: the only issue or PR this output may write to (GitHub number, Linear identifier), or { input: <name> } to pin it to a run input.",
870
887
  oneOf: [
871
888
  { type: "string", minLength: 1 },
872
889
  { type: "integer", minimum: 1 },
@@ -878,6 +895,11 @@ export const workflowJsonSchema = {
878
895
  },
879
896
  ],
880
897
  },
898
+ state: {
899
+ type: "string",
900
+ enum: [...SAFE_OUTPUT_STATES],
901
+ description: "issue_state: the one change this output may make (reopen or close). Default: either.",
902
+ },
881
903
  },
882
904
  },
883
905
  },
@@ -247,6 +247,30 @@ export const github = {
247
247
  body: JSON.stringify({ labels: input.labels }),
248
248
  }),
249
249
  },
250
+ {
251
+ name: "github_set_issue_state",
252
+ access: "write",
253
+ description: "Reopen a closed GitHub issue or pull request, or close an open one",
254
+ input_schema: {
255
+ type: "object",
256
+ properties: {
257
+ repo: { type: "string", description: "owner/repo" },
258
+ issue_number: { type: "number", description: "Issue or PR number" },
259
+ state: { type: "string", enum: ["reopen", "close"], description: "reopen or close" },
260
+ },
261
+ required: ["repo", "issue_number", "state"],
262
+ },
263
+ handler: async (input, ctx) => {
264
+ if (input.state !== "reopen" && input.state !== "close") {
265
+ throw new Error(`[GitHub] github_set_issue_state: state must be "reopen" or "close"`);
266
+ }
267
+ const reopen = input.state === "reopen";
268
+ return gh(`/repos/${input.repo}/issues/${input.issue_number}`, ctx, {
269
+ method: "PATCH",
270
+ body: JSON.stringify({ state: reopen ? "open" : "closed", state_reason: reopen ? "reopened" : "completed" }),
271
+ });
272
+ },
273
+ },
250
274
  {
251
275
  name: "github_list_recent_commits",
252
276
  access: "read",
@@ -195,6 +195,50 @@ export const linear = {
195
195
  return data;
196
196
  },
197
197
  },
198
+ {
199
+ name: "linear_set_issue_state",
200
+ access: "write",
201
+ description: "Reopen a closed Linear issue (moves it to the team's first unstarted or backlog state) or close an open one (first completed state). Does nothing when the issue is already in the requested state.",
202
+ input_schema: {
203
+ type: "object",
204
+ properties: {
205
+ issueId: { type: "string", description: "Linear issue ID (UUID) or identifier (e.g. 'OFF-1020')" },
206
+ state: { type: "string", enum: ["reopen", "close"], description: "reopen or close" },
207
+ },
208
+ required: ["issueId", "state"],
209
+ },
210
+ handler: async (input, ctx) => {
211
+ if (input.state !== "reopen" && input.state !== "close") {
212
+ throw new Error(`[Linear] linear_set_issue_state: state must be "reopen" or "close"`);
213
+ }
214
+ const found = await linearGql(`query($id: String!) {
215
+ issue(id: $id) {
216
+ id identifier url
217
+ state { type }
218
+ team { states { nodes { id type position } } }
219
+ }
220
+ }`, { id: input.issueId }, ctx);
221
+ const issue = found?.issue;
222
+ if (!issue?.id)
223
+ throw new Error(`[Linear] linear_set_issue_state: issue ${input.issueId} not found`);
224
+ const isClosed = issue.state?.type === "completed" || issue.state?.type === "canceled";
225
+ const wantClosed = input.state === "close";
226
+ if (isClosed === wantClosed) {
227
+ return { issueUpdate: { success: true, changed: false, issue } };
228
+ }
229
+ const states = issue.team?.states?.nodes ?? [];
230
+ const first = (type) => states.filter((s) => s.type === type).sort((a, b) => a.position - b.position)[0];
231
+ const target = wantClosed ? first("completed") : (first("unstarted") ?? first("backlog"));
232
+ if (!target) {
233
+ throw new Error(`[Linear] linear_set_issue_state: no ${wantClosed ? "completed" : "unstarted"} state for ${input.issueId}`);
234
+ }
235
+ const data = await linearGql(`mutation($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success issue { id identifier url title state { name } } } }`, { id: issue.id, input: { stateId: target.id } }, ctx);
236
+ if (!data?.issueUpdate?.success) {
237
+ throw new Error(`[Linear] linear_set_issue_state failed for ${input.issueId}. Raw response: ${JSON.stringify(data)}`);
238
+ }
239
+ return data;
240
+ },
241
+ },
198
242
  ],
199
243
  // Equivalent tool names on Linear's official remote MCP server (mcp.linear.app).
200
244
  //
package/dist/types.d.ts CHANGED
@@ -393,8 +393,11 @@ export interface NodePermissionsSpec {
393
393
  /** `read` / `write` shorthand, or the object form. */
394
394
  export type NodePermissions = NodeAccess | NodePermissionsSpec;
395
395
  /** Write operations a node may request through `emit_output`. */
396
- export declare const SAFE_OUTPUT_TYPES: readonly ["comment", "issue", "pr", "label"];
396
+ export declare const SAFE_OUTPUT_TYPES: readonly ["comment", "issue", "pr", "label", "issue_state"];
397
397
  export type SafeOutputType = (typeof SAFE_OUTPUT_TYPES)[number];
398
+ /** What an `issue_state` output may do to an issue: reopen a closed one, or close an open one. */
399
+ export declare const SAFE_OUTPUT_STATES: readonly ["reopen", "close"];
400
+ export type SafeOutputState = (typeof SAFE_OUTPUT_STATES)[number];
398
401
  /**
399
402
  * Skills that can apply each output type. The write stage calls the skill's
400
403
  * own tool handler; a skill not listed here cannot apply safe outputs.
@@ -423,12 +426,15 @@ export interface SafeOutputDeclaration {
423
426
  /** Drop an intent older than this when the write stage runs (e.g. `30m`, `2h`, `7d`). */
424
427
  expires?: string;
425
428
  /**
426
- * comment / label: the only issue or PR this output may write to (GitHub
427
- * issue or PR number, Linear issue identifier), or `{ input: <name> }` to pin
428
- * it to a run input. An intent naming another is refused; an intent naming
429
- * none uses the pin. An empty pinned input refuses the write.
429
+ * comment / label / issue_state: the only issue or PR this output may write
430
+ * to (GitHub issue or PR number, Linear issue identifier), or `{ input: <name> }`
431
+ * to pin it to a run input. An intent naming another is refused; an intent
432
+ * naming none uses the pin. An empty pinned input refuses the write.
433
+ * `issue_state` always needs an issue: the pin, or a number on the intent.
430
434
  */
431
435
  number?: SafeOutputPin;
436
+ /** issue_state only: the one change this output may make. Default: either. */
437
+ state?: SafeOutputState;
432
438
  }
433
439
  /** A pinned issue / PR: a literal number or identifier, or the name of a run input that holds one. */
434
440
  export type SafeOutputPin = string | number | {
package/dist/types.js CHANGED
@@ -73,14 +73,16 @@ export const NODE_ACCESS = ["read", "write"];
73
73
  /** Portable built-in tool classes, compiled per harness (Claude Code: native tool names). */
74
74
  export const TOOL_CLASSES = ["shell", "write", "edit", "net", "subagent"];
75
75
  /** Write operations a node may request through `emit_output`. */
76
- export const SAFE_OUTPUT_TYPES = ["comment", "issue", "pr", "label"];
76
+ export const SAFE_OUTPUT_TYPES = ["comment", "issue", "pr", "label", "issue_state"];
77
+ /** What an `issue_state` output may do to an issue: reopen a closed one, or close an open one. */
78
+ export const SAFE_OUTPUT_STATES = ["reopen", "close"];
77
79
  /**
78
80
  * Skills that can apply each output type. The write stage calls the skill's
79
81
  * own tool handler; a skill not listed here cannot apply safe outputs.
80
82
  */
81
83
  export const SAFE_OUTPUT_APPLIERS = {
82
- github: ["comment", "issue", "pr", "label"],
83
- linear: ["comment", "issue"],
84
+ github: ["comment", "issue", "pr", "label", "issue_state"],
85
+ linear: ["comment", "issue", "issue_state"],
84
86
  };
85
87
  /** GitHub author associations accepted by `safe_outputs.trusted_associations`. */
86
88
  export const AUTHOR_ASSOCIATIONS = [