@sweny-ai/core 0.1.143 → 0.1.145
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/cli/main.js +1 -0
- package/dist/cli/run-output.js +4 -0
- package/dist/executor.d.ts +12 -0
- package/dist/executor.js +108 -6
- package/dist/harness/claude-code.d.ts +12 -4
- package/dist/harness/claude-code.js +41 -8
- package/dist/harness/codex.js +11 -7
- package/dist/harness/sandbox-wrapper.d.ts +16 -3
- package/dist/harness/sandbox-wrapper.js +44 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -1
- package/dist/node-policy.d.ts +31 -0
- package/dist/node-policy.js +42 -0
- package/dist/safe-outputs.d.ts +103 -0
- package/dist/safe-outputs.js +460 -0
- package/dist/schema.d.ts +489 -1
- package/dist/schema.js +203 -1
- package/dist/skills/github.js +18 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.js +29 -0
- package/package.json +1 -1
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 { EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ON_FAIL, REQUIRES_ON_FAIL, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, 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_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 };
|
|
@@ -244,6 +244,47 @@ export const nodeRetryZ = z
|
|
|
244
244
|
instruction: z.union([z.string().min(1), retryInstructionAutoZ, retryInstructionReflectZ]).optional(),
|
|
245
245
|
})
|
|
246
246
|
.strict();
|
|
247
|
+
/**
|
|
248
|
+
* Node / workflow permissions (#365): `read` | `write`, or an object with
|
|
249
|
+
* `access`, `deny` (portable tool classes) and `strict`. The object form must
|
|
250
|
+
* declare at least one key.
|
|
251
|
+
*/
|
|
252
|
+
export const nodePermissionsZ = z.union([
|
|
253
|
+
z.enum(NODE_ACCESS),
|
|
254
|
+
z
|
|
255
|
+
.object({
|
|
256
|
+
access: z.enum(NODE_ACCESS).optional(),
|
|
257
|
+
deny: z.array(z.enum(TOOL_CLASSES)).min(1).optional(),
|
|
258
|
+
strict: z.boolean().optional(),
|
|
259
|
+
})
|
|
260
|
+
.strict()
|
|
261
|
+
.refine((p) => p.access !== undefined || p.deny !== undefined || p.strict !== undefined, {
|
|
262
|
+
message: "permissions must declare at least one of access, deny, strict",
|
|
263
|
+
}),
|
|
264
|
+
]);
|
|
265
|
+
/** One typed write intent a node may emit (#365). */
|
|
266
|
+
export const safeOutputDeclarationZ = z
|
|
267
|
+
.object({
|
|
268
|
+
type: z.enum(SAFE_OUTPUT_TYPES),
|
|
269
|
+
via: z.string().min(1).optional(),
|
|
270
|
+
max: z.number().int().min(1).max(SAFE_OUTPUT_MAX_CEILING).optional(),
|
|
271
|
+
target: z.string().min(1).optional(),
|
|
272
|
+
title_prefix: z.string().min(1).max(64).optional(),
|
|
273
|
+
labels: z.array(z.string().min(1)).min(1).optional(),
|
|
274
|
+
expires: z.string().regex(SAFE_OUTPUT_EXPIRES_PATTERN).optional(),
|
|
275
|
+
})
|
|
276
|
+
.strict();
|
|
277
|
+
/** Workflow-level safe-output policy (#365): the ceiling and run-wide limits. */
|
|
278
|
+
export const safeOutputsPolicyZ = z
|
|
279
|
+
.object({
|
|
280
|
+
allow: z.array(z.enum(SAFE_OUTPUT_TYPES)).min(1).optional(),
|
|
281
|
+
max: z.number().int().min(1).max(SAFE_OUTPUT_MAX_CEILING).optional(),
|
|
282
|
+
staged: z.boolean().optional(),
|
|
283
|
+
trusted_actors: z.array(z.string().min(1)).min(1).optional(),
|
|
284
|
+
trusted_associations: z.array(z.enum(AUTHOR_ASSOCIATIONS)).min(1).optional(),
|
|
285
|
+
screen: z.boolean().optional(),
|
|
286
|
+
})
|
|
287
|
+
.strict();
|
|
247
288
|
export const nodeZ = z
|
|
248
289
|
.object({
|
|
249
290
|
name: z.string().min(1),
|
|
@@ -255,6 +296,8 @@ export const nodeZ = z
|
|
|
255
296
|
tools: nodeToolsZ.optional(),
|
|
256
297
|
fail_soft: z.boolean().optional(),
|
|
257
298
|
on_fail: z.enum(NODE_ON_FAIL).optional(),
|
|
299
|
+
permissions: nodePermissionsZ.optional(),
|
|
300
|
+
outputs: z.array(safeOutputDeclarationZ).min(1).optional(),
|
|
258
301
|
rules: nodeSourcesZ.optional(),
|
|
259
302
|
context: nodeSourcesZ.optional(),
|
|
260
303
|
eval: z.array(evaluatorZ).min(1).optional(),
|
|
@@ -307,6 +350,8 @@ export const workflowZ = z.object({
|
|
|
307
350
|
model: z.string().min(1).optional(),
|
|
308
351
|
judge_budget: z.number().int().min(0).optional(),
|
|
309
352
|
inputs: workflowInputsZ.optional(),
|
|
353
|
+
permissions: nodePermissionsZ.optional(),
|
|
354
|
+
safe_outputs: safeOutputsPolicyZ.optional(),
|
|
310
355
|
});
|
|
311
356
|
const LEGACY_VERIFY_MESSAGE = "uses the legacy 'verify:' field. It was renamed to 'eval:' in @sweny-ai/core v0.2.0 with a different shape (named evaluators with kind: value | function | judge). " +
|
|
312
357
|
"Migration guide: https://spec.sweny.ai/nodes/#eval";
|
|
@@ -548,6 +593,48 @@ export function validateWorkflow(workflow, knownSkills) {
|
|
|
548
593
|
});
|
|
549
594
|
}
|
|
550
595
|
}
|
|
596
|
+
// Permissions and safe outputs (#365). The workflow's `permissions` and
|
|
597
|
+
// `safe_outputs.allow` are a ceiling every node inherits.
|
|
598
|
+
const wfAccess = typeof workflow.permissions === "string" ? workflow.permissions : workflow.permissions?.access;
|
|
599
|
+
const allowedOutputs = workflow.safe_outputs?.allow;
|
|
600
|
+
for (const [nodeId, node] of Object.entries(workflow.nodes)) {
|
|
601
|
+
const nodeAccess = typeof node.permissions === "string" ? node.permissions : node.permissions?.access;
|
|
602
|
+
if (wfAccess === "read" && nodeAccess === "write") {
|
|
603
|
+
errors.push({
|
|
604
|
+
code: "PERMISSION_CEILING",
|
|
605
|
+
message: `Node "${nodeId}" asks for permissions write, above the workflow's permissions read`,
|
|
606
|
+
nodeId,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
const seenTypes = new Set();
|
|
610
|
+
for (const out of node.outputs ?? []) {
|
|
611
|
+
if (seenTypes.has(out.type)) {
|
|
612
|
+
errors.push({
|
|
613
|
+
code: "DUPLICATE_OUTPUT",
|
|
614
|
+
message: `Node "${nodeId}" declares output "${out.type}" more than once; merge them into one entry`,
|
|
615
|
+
nodeId,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
seenTypes.add(out.type);
|
|
619
|
+
if (allowedOutputs && !allowedOutputs.includes(out.type)) {
|
|
620
|
+
errors.push({
|
|
621
|
+
code: "OUTPUT_NOT_ALLOWED",
|
|
622
|
+
message: `Node "${nodeId}" declares output "${out.type}", outside the workflow's safe_outputs.allow [${allowedOutputs.join(", ")}]`,
|
|
623
|
+
nodeId,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (out.via && !SAFE_OUTPUT_APPLIERS[out.via]?.includes(out.type)) {
|
|
627
|
+
const supported = Object.entries(SAFE_OUTPUT_APPLIERS)
|
|
628
|
+
.filter(([, types]) => types.includes(out.type))
|
|
629
|
+
.map(([id]) => id);
|
|
630
|
+
errors.push({
|
|
631
|
+
code: "UNSUPPORTED_OUTPUT",
|
|
632
|
+
message: `Node "${nodeId}" output "${out.type}" names via "${out.via}", which cannot apply it (supported: ${supported.join(", ")})`,
|
|
633
|
+
nodeId,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
551
638
|
// Skill references
|
|
552
639
|
if (knownSkills) {
|
|
553
640
|
// Merge workflow inline skills into known set
|
|
@@ -705,6 +792,71 @@ export const workflowJsonSchema = {
|
|
|
705
792
|
},
|
|
706
793
|
],
|
|
707
794
|
},
|
|
795
|
+
Permissions: {
|
|
796
|
+
description: "What a node's agent may do. 'read' runs it read-only: only access: read skill tools, no external skill MCP servers, no shell, file-write, edit, fetch or subagent built-ins. On the workflow it is the default and the ceiling for every node.",
|
|
797
|
+
oneOf: [
|
|
798
|
+
{ type: "string", enum: [...NODE_ACCESS] },
|
|
799
|
+
{
|
|
800
|
+
type: "object",
|
|
801
|
+
additionalProperties: false,
|
|
802
|
+
minProperties: 1,
|
|
803
|
+
properties: {
|
|
804
|
+
access: { type: "string", enum: [...NODE_ACCESS] },
|
|
805
|
+
deny: {
|
|
806
|
+
type: "array",
|
|
807
|
+
items: { type: "string", enum: [...TOOL_CLASSES] },
|
|
808
|
+
minItems: 1,
|
|
809
|
+
description: "Built-in tool classes the agent must not have, compiled per harness.",
|
|
810
|
+
},
|
|
811
|
+
strict: {
|
|
812
|
+
type: "boolean",
|
|
813
|
+
description: "Exclusive MCP (only the servers sweny injects), and refuse the node on a harness that cannot enforce the policy.",
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
],
|
|
818
|
+
},
|
|
819
|
+
SafeOutput: {
|
|
820
|
+
type: "object",
|
|
821
|
+
required: ["type"],
|
|
822
|
+
additionalProperties: false,
|
|
823
|
+
properties: {
|
|
824
|
+
type: { type: "string", enum: [...SAFE_OUTPUT_TYPES] },
|
|
825
|
+
via: {
|
|
826
|
+
type: "string",
|
|
827
|
+
minLength: 1,
|
|
828
|
+
description: "Skill that applies the write. Default: the first node skill that supports the type.",
|
|
829
|
+
},
|
|
830
|
+
max: {
|
|
831
|
+
type: "integer",
|
|
832
|
+
minimum: 1,
|
|
833
|
+
maximum: SAFE_OUTPUT_MAX_CEILING,
|
|
834
|
+
description: "Max writes of this type from this node per run. Default 1.",
|
|
835
|
+
},
|
|
836
|
+
target: {
|
|
837
|
+
type: "string",
|
|
838
|
+
minLength: 1,
|
|
839
|
+
description: "Pinned target (GitHub owner/repo, Linear team id). GitHub default: GITHUB_REPOSITORY.",
|
|
840
|
+
},
|
|
841
|
+
title_prefix: {
|
|
842
|
+
type: "string",
|
|
843
|
+
minLength: 1,
|
|
844
|
+
maxLength: 64,
|
|
845
|
+
description: "Prepended to issue and PR titles that do not already start with it.",
|
|
846
|
+
},
|
|
847
|
+
labels: {
|
|
848
|
+
type: "array",
|
|
849
|
+
items: { type: "string", minLength: 1 },
|
|
850
|
+
minItems: 1,
|
|
851
|
+
description: "issue / pr: labels always added. label: the only labels the agent may add.",
|
|
852
|
+
},
|
|
853
|
+
expires: {
|
|
854
|
+
type: "string",
|
|
855
|
+
pattern: SAFE_OUTPUT_EXPIRES_PATTERN.source,
|
|
856
|
+
description: "Drop an intent older than this when the write stage runs (e.g. 30m, 2h, 7d).",
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
},
|
|
708
860
|
},
|
|
709
861
|
properties: {
|
|
710
862
|
id: { type: "string", minLength: 1 },
|
|
@@ -739,6 +891,46 @@ export const workflowJsonSchema = {
|
|
|
739
891
|
minLength: 1,
|
|
740
892
|
description: "Default execution model for every node. Overridable per-node. Free-text passthrough (no registry); resolved as node.model ?? workflow.model ?? executor default.",
|
|
741
893
|
},
|
|
894
|
+
permissions: {
|
|
895
|
+
$ref: "#/$defs/Permissions",
|
|
896
|
+
description: "Default and ceiling for every node's permissions. Absent: write.",
|
|
897
|
+
},
|
|
898
|
+
safe_outputs: {
|
|
899
|
+
type: "object",
|
|
900
|
+
description: "Workflow-level safe-output policy: the ceiling on output types and run-wide limits.",
|
|
901
|
+
additionalProperties: false,
|
|
902
|
+
properties: {
|
|
903
|
+
allow: {
|
|
904
|
+
type: "array",
|
|
905
|
+
items: { type: "string", enum: [...SAFE_OUTPUT_TYPES] },
|
|
906
|
+
minItems: 1,
|
|
907
|
+
description: "Output types any node may declare. Absent: all types.",
|
|
908
|
+
},
|
|
909
|
+
max: {
|
|
910
|
+
type: "integer",
|
|
911
|
+
minimum: 1,
|
|
912
|
+
maximum: SAFE_OUTPUT_MAX_CEILING,
|
|
913
|
+
description: "Total writes per run across all nodes.",
|
|
914
|
+
},
|
|
915
|
+
staged: { type: "boolean", description: "Preview every write and apply none." },
|
|
916
|
+
trusted_actors: {
|
|
917
|
+
type: "array",
|
|
918
|
+
items: { type: "string", minLength: 1 },
|
|
919
|
+
minItems: 1,
|
|
920
|
+
description: "GitHub logins whose runs may write.",
|
|
921
|
+
},
|
|
922
|
+
trusted_associations: {
|
|
923
|
+
type: "array",
|
|
924
|
+
items: { type: "string", enum: [...AUTHOR_ASSOCIATIONS] },
|
|
925
|
+
minItems: 1,
|
|
926
|
+
description: "Author associations (from the GitHub event payload) whose runs may write.",
|
|
927
|
+
},
|
|
928
|
+
screen: {
|
|
929
|
+
type: "boolean",
|
|
930
|
+
description: "One model call that may veto the writes after every deterministic check. It can never authorize one.",
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
},
|
|
742
934
|
judge_budget: {
|
|
743
935
|
type: "integer",
|
|
744
936
|
minimum: 0,
|
|
@@ -890,6 +1082,16 @@ export const workflowJsonSchema = {
|
|
|
890
1082
|
enum: [...NODE_ON_FAIL],
|
|
891
1083
|
description: "What to do when this node finishes 'failed' (agent-level failure, or an eval failure that exhausted retries and was not softened by fail_soft). 'halt' (default) stops the workflow with the failure surfaced so a broken node never advances down a conditional edge; 'continue' preserves the legacy fall-through where routing proceeds from the failed node. Distinct from requires.on_fail (the pre-condition gate).",
|
|
892
1084
|
},
|
|
1085
|
+
permissions: {
|
|
1086
|
+
$ref: "#/$defs/Permissions",
|
|
1087
|
+
description: "What this node's agent may do. Absent: read when the node declares outputs, else the workflow's permissions, else write.",
|
|
1088
|
+
},
|
|
1089
|
+
outputs: {
|
|
1090
|
+
type: "array",
|
|
1091
|
+
items: { $ref: "#/$defs/SafeOutput" },
|
|
1092
|
+
minItems: 1,
|
|
1093
|
+
description: "Typed write intents this node may emit with the emit_output tool. sweny applies them after the node, within the declared caps.",
|
|
1094
|
+
},
|
|
893
1095
|
rules: {
|
|
894
1096
|
$ref: "#/$defs/NodeSources",
|
|
895
1097
|
description: "Per-node rules. Additive by default; set { only: true } to block cascade.",
|
package/dist/skills/github.js
CHANGED
|
@@ -229,6 +229,24 @@ export const github = {
|
|
|
229
229
|
return reused ? { ...pr, reused: true } : pr;
|
|
230
230
|
},
|
|
231
231
|
},
|
|
232
|
+
{
|
|
233
|
+
name: "github_add_labels",
|
|
234
|
+
access: "write",
|
|
235
|
+
description: "Add labels to a GitHub issue or pull request",
|
|
236
|
+
input_schema: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
repo: { type: "string", description: "owner/repo" },
|
|
240
|
+
issue_number: { type: "number", description: "Issue or PR number" },
|
|
241
|
+
labels: { type: "array", items: { type: "string" }, description: "Labels to add" },
|
|
242
|
+
},
|
|
243
|
+
required: ["repo", "issue_number", "labels"],
|
|
244
|
+
},
|
|
245
|
+
handler: async (input, ctx) => gh(`/repos/${input.repo}/issues/${input.issue_number}/labels`, ctx, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
body: JSON.stringify({ labels: input.labels }),
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
232
250
|
{
|
|
233
251
|
name: "github_list_recent_commits",
|
|
234
252
|
access: "read",
|
package/dist/types.d.ts
CHANGED
|
@@ -362,6 +362,93 @@ export interface Node {
|
|
|
362
362
|
* Distinct from `requires.on_fail`, which is the pre-condition gate.
|
|
363
363
|
*/
|
|
364
364
|
on_fail?: NodeOnFail;
|
|
365
|
+
/**
|
|
366
|
+
* What this node's agent may do (#365). `read` runs the node read-only:
|
|
367
|
+
* only `access: "read"` skill tools, no external skill MCP servers, no
|
|
368
|
+
* shell / file-write / edit / fetch / subagent built-ins. Absent: `read`
|
|
369
|
+
* when the node declares `outputs`, else the workflow's `permissions`,
|
|
370
|
+
* else `write` (today's behavior).
|
|
371
|
+
*/
|
|
372
|
+
permissions?: NodePermissions;
|
|
373
|
+
/**
|
|
374
|
+
* Typed write intents this node may emit (#365). The agent records them with
|
|
375
|
+
* the `emit_output` tool; sweny applies them after the node, within the
|
|
376
|
+
* declared caps. See {@link SafeOutputDeclaration}.
|
|
377
|
+
*/
|
|
378
|
+
outputs?: SafeOutputDeclaration[];
|
|
379
|
+
}
|
|
380
|
+
/** Node access level. `read` = no write-capable tool reaches the agent. */
|
|
381
|
+
export declare const NODE_ACCESS: readonly ["read", "write"];
|
|
382
|
+
export type NodeAccess = (typeof NODE_ACCESS)[number];
|
|
383
|
+
/** Portable built-in tool classes, compiled per harness (Claude Code: native tool names). */
|
|
384
|
+
export declare const TOOL_CLASSES: readonly ["shell", "write", "edit", "net", "subagent"];
|
|
385
|
+
/** Object form of {@link NodePermissions}. */
|
|
386
|
+
export interface NodePermissionsSpec {
|
|
387
|
+
access?: NodeAccess;
|
|
388
|
+
/** Built-in tool classes the agent must not have. */
|
|
389
|
+
deny?: (typeof TOOL_CLASSES)[number][];
|
|
390
|
+
/** Exclusive MCP, and refuse the node on a harness that cannot enforce the policy. */
|
|
391
|
+
strict?: boolean;
|
|
392
|
+
}
|
|
393
|
+
/** `read` / `write` shorthand, or the object form. */
|
|
394
|
+
export type NodePermissions = NodeAccess | NodePermissionsSpec;
|
|
395
|
+
/** Write operations a node may request through `emit_output`. */
|
|
396
|
+
export declare const SAFE_OUTPUT_TYPES: readonly ["comment", "issue", "pr", "label"];
|
|
397
|
+
export type SafeOutputType = (typeof SAFE_OUTPUT_TYPES)[number];
|
|
398
|
+
/**
|
|
399
|
+
* Skills that can apply each output type. The write stage calls the skill's
|
|
400
|
+
* own tool handler; a skill not listed here cannot apply safe outputs.
|
|
401
|
+
*/
|
|
402
|
+
export declare const SAFE_OUTPUT_APPLIERS: Readonly<Record<string, readonly SafeOutputType[]>>;
|
|
403
|
+
/** GitHub author associations accepted by `safe_outputs.trusted_associations`. */
|
|
404
|
+
export declare const AUTHOR_ASSOCIATIONS: readonly ["OWNER", "MEMBER", "COLLABORATOR", "CONTRIBUTOR", "FIRST_TIME_CONTRIBUTOR", "FIRST_TIMER", "NONE"];
|
|
405
|
+
export type AuthorAssociation = (typeof AUTHOR_ASSOCIATIONS)[number];
|
|
406
|
+
/** Upper bound on a single output's `max` and on `safe_outputs.max`. */
|
|
407
|
+
export declare const SAFE_OUTPUT_MAX_CEILING = 100;
|
|
408
|
+
/** `expires` grammar: a positive integer and a unit (s, m, h, d). */
|
|
409
|
+
export declare const SAFE_OUTPUT_EXPIRES_PATTERN: RegExp;
|
|
410
|
+
/** One declared output on a node. */
|
|
411
|
+
export interface SafeOutputDeclaration {
|
|
412
|
+
type: SafeOutputType;
|
|
413
|
+
/** Skill that applies it. Default: the first node skill that supports the type. */
|
|
414
|
+
via?: string;
|
|
415
|
+
/** Max writes of this type from this node per run. Default 1. */
|
|
416
|
+
max?: number;
|
|
417
|
+
/** Pinned target (GitHub `owner/repo`, Linear team id). GitHub default: `GITHUB_REPOSITORY`. */
|
|
418
|
+
target?: string;
|
|
419
|
+
/** Prepended to issue / PR titles that do not already start with it. */
|
|
420
|
+
title_prefix?: string;
|
|
421
|
+
/** issue / pr: labels always added. label: the only labels the agent may add. */
|
|
422
|
+
labels?: string[];
|
|
423
|
+
/** Drop an intent older than this when the write stage runs (e.g. `30m`, `2h`, `7d`). */
|
|
424
|
+
expires?: string;
|
|
425
|
+
}
|
|
426
|
+
/** Workflow-level safe-output policy: the ceiling and run-wide limits. */
|
|
427
|
+
export interface SafeOutputsPolicy {
|
|
428
|
+
/** Output types any node may declare. Absent: all types. */
|
|
429
|
+
allow?: SafeOutputType[];
|
|
430
|
+
/** Total writes per run across all nodes. */
|
|
431
|
+
max?: number;
|
|
432
|
+
/** Preview every write and apply none. */
|
|
433
|
+
staged?: boolean;
|
|
434
|
+
/** GitHub logins whose runs may write. */
|
|
435
|
+
trusted_actors?: string[];
|
|
436
|
+
/** Author associations whose runs may write (from the GitHub event payload). */
|
|
437
|
+
trusted_associations?: AuthorAssociation[];
|
|
438
|
+
/** One model call that may veto the writes. It can never authorize one. */
|
|
439
|
+
screen?: boolean;
|
|
440
|
+
}
|
|
441
|
+
/** What the write stage did with one intent. Metadata only: never a title or body. */
|
|
442
|
+
export interface SafeOutputReceipt {
|
|
443
|
+
type: string;
|
|
444
|
+
/** Skill that applied (or would apply) it. */
|
|
445
|
+
via?: string;
|
|
446
|
+
status: "applied" | "staged" | "skipped" | "refused" | "vetoed" | "failed";
|
|
447
|
+
/** Why it was skipped, refused, vetoed or failed. Fixed wording, never model text. */
|
|
448
|
+
reason?: string;
|
|
449
|
+
target?: string;
|
|
450
|
+
/** Issue / PR / comment number or id the write produced. */
|
|
451
|
+
ref?: string | number;
|
|
365
452
|
}
|
|
366
453
|
/**
|
|
367
454
|
* Per-node skill-tool filter. See {@link Node.tools}.
|
|
@@ -424,6 +511,10 @@ export interface Workflow {
|
|
|
424
511
|
* See `src/inputs.ts` for the field shape and validation rules.
|
|
425
512
|
*/
|
|
426
513
|
inputs?: import("./inputs.js").WorkflowInputs;
|
|
514
|
+
/** Default and ceiling for every node's `permissions` (#365). Absent: `write`. */
|
|
515
|
+
permissions?: NodePermissions;
|
|
516
|
+
/** Workflow-level safe-output policy (#365). */
|
|
517
|
+
safe_outputs?: SafeOutputsPolicy;
|
|
427
518
|
}
|
|
428
519
|
/**
|
|
429
520
|
* Inline skill definition in a workflow's `skills` block.
|
|
@@ -490,6 +581,8 @@ export interface NodeResult {
|
|
|
490
581
|
};
|
|
491
582
|
/** Opinions this run could not honor natively. Always empty for Claude Code. */
|
|
492
583
|
degraded?: string[];
|
|
584
|
+
/** Safe outputs (#365): what the write stage did with each intent. Absent when the node declares none. */
|
|
585
|
+
outputs?: SafeOutputReceipt[];
|
|
493
586
|
}
|
|
494
587
|
export interface ToolCall {
|
|
495
588
|
tool: string;
|
|
@@ -652,6 +745,12 @@ export interface Claude {
|
|
|
652
745
|
* (external MCP servers, shell, file-edit built-ins).
|
|
653
746
|
*/
|
|
654
747
|
readOnly?: boolean;
|
|
748
|
+
/**
|
|
749
|
+
* The node's portable policy (#365), compiled per adapter. Legacy `Claude`
|
|
750
|
+
* implementations may ignore it; `readOnly` and `disallowedTools` above
|
|
751
|
+
* carry the same read-only and native-deny intent.
|
|
752
|
+
*/
|
|
753
|
+
policy?: import("./harness/types.js").NodePolicy;
|
|
655
754
|
}): Promise<NodeResult>;
|
|
656
755
|
/**
|
|
657
756
|
* Evaluate a routing condition — pick one of N choices.
|
package/dist/types.js
CHANGED
|
@@ -67,6 +67,35 @@ export const REQUIRES_ON_FAIL = ["fail", "skip"];
|
|
|
67
67
|
export const NODE_ON_FAIL = ["halt", "continue"];
|
|
68
68
|
/** MCP server transport type. Inferred from command/url when omitted. */
|
|
69
69
|
export const MCP_TRANSPORTS = ["stdio", "http"];
|
|
70
|
+
// ─── Permissions and safe outputs (#365) ─────────────────────────
|
|
71
|
+
/** Node access level. `read` = no write-capable tool reaches the agent. */
|
|
72
|
+
export const NODE_ACCESS = ["read", "write"];
|
|
73
|
+
/** Portable built-in tool classes, compiled per harness (Claude Code: native tool names). */
|
|
74
|
+
export const TOOL_CLASSES = ["shell", "write", "edit", "net", "subagent"];
|
|
75
|
+
/** Write operations a node may request through `emit_output`. */
|
|
76
|
+
export const SAFE_OUTPUT_TYPES = ["comment", "issue", "pr", "label"];
|
|
77
|
+
/**
|
|
78
|
+
* Skills that can apply each output type. The write stage calls the skill's
|
|
79
|
+
* own tool handler; a skill not listed here cannot apply safe outputs.
|
|
80
|
+
*/
|
|
81
|
+
export const SAFE_OUTPUT_APPLIERS = {
|
|
82
|
+
github: ["comment", "issue", "pr", "label"],
|
|
83
|
+
linear: ["comment", "issue"],
|
|
84
|
+
};
|
|
85
|
+
/** GitHub author associations accepted by `safe_outputs.trusted_associations`. */
|
|
86
|
+
export const AUTHOR_ASSOCIATIONS = [
|
|
87
|
+
"OWNER",
|
|
88
|
+
"MEMBER",
|
|
89
|
+
"COLLABORATOR",
|
|
90
|
+
"CONTRIBUTOR",
|
|
91
|
+
"FIRST_TIME_CONTRIBUTOR",
|
|
92
|
+
"FIRST_TIMER",
|
|
93
|
+
"NONE",
|
|
94
|
+
];
|
|
95
|
+
/** Upper bound on a single output's `max` and on `safe_outputs.max`. */
|
|
96
|
+
export const SAFE_OUTPUT_MAX_CEILING = 100;
|
|
97
|
+
/** `expires` grammar: a positive integer and a unit (s, m, h, d). */
|
|
98
|
+
export const SAFE_OUTPUT_EXPIRES_PATTERN = /^[1-9][0-9]*(s|m|h|d)$/;
|
|
70
99
|
/**
|
|
71
100
|
* Workflow type discriminator. Cloud uses this to render runs in a
|
|
72
101
|
* type-specific way. Adding a value requires a corresponding renderer.
|