@sweny-ai/core 0.1.176 → 0.1.178
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/agent-env.d.ts +9 -0
- package/dist/agent-env.js +4 -0
- package/dist/cli/comment-output.js +4 -5
- package/dist/cli/config-file.d.ts +37 -2
- package/dist/cli/config-file.js +68 -3
- package/dist/cli/final-output.d.ts +4 -1
- package/dist/cli/final-output.js +6 -8
- package/dist/cli/main.d.ts +2 -1
- package/dist/cli/main.js +18 -10
- package/dist/cli/resume.js +1 -0
- package/dist/cli/run-history.d.ts +3 -7
- package/dist/cli/run-history.js +5 -12
- package/dist/cli/run-output.d.ts +16 -10
- package/dist/cli/run-output.js +30 -16
- package/dist/cli/ticket.js +3 -2
- package/dist/cli/try.js +2 -3
- package/dist/decider.d.ts +176 -64
- package/dist/decider.js +475 -155
- package/dist/executor.d.ts +55 -7
- package/dist/executor.js +511 -99
- package/dist/harness/acp.js +2 -2
- package/dist/harness/claude-code.js +6 -4
- package/dist/harness/codex.js +2 -2
- package/dist/harness/pi.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/journal.d.ts +34 -5
- package/dist/journal.js +42 -13
- package/dist/safe-file.d.ts +74 -0
- package/dist/safe-file.js +211 -0
- package/dist/safe-outputs.d.ts +7 -1
- package/dist/safe-outputs.js +7 -1
- package/dist/schema.d.ts +189 -241
- package/dist/schema.js +56 -36
- package/dist/skills/git-push.d.ts +99 -10
- package/dist/skills/git-push.js +413 -52
- package/dist/skills/github.d.ts +19 -15
- package/dist/skills/github.js +91 -15
- package/dist/startup-env.d.ts +30 -0
- package/dist/startup-env.js +48 -0
- package/dist/types.d.ts +86 -4
- package/dist/types.js +22 -0
- package/dist/when.d.ts +8 -1
- package/dist/when.js +8 -1
- package/dist/workflow-builder.js +3 -1
- package/dist/workflows/browser.js +2 -2
- package/dist/workflows/implement.yml +37 -5
- package/dist/workflows/triage.yml +40 -6
- 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 { AUTHOR_ASSOCIATIONS, CONTEXT_MODES, 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";
|
|
10
|
+
import { AUTHOR_ASSOCIATIONS, CONTEXT_MODES, DECIDER_CONFIDENCE_FLOOR, DECIDER_MARGIN_FLOOR, DECIDER_MIN_CONFIDENCE, DECIDER_MIN_MARGIN, DECIDER_MODE_REMOVED, DECIDER_PROVIDER_MOVED, EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ACCESS, NODE_ON_FAIL, REQUIRES_ON_FAIL, ROUTE_BY, 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 { resolveNodePermissions } from "./node-policy.js";
|
|
13
13
|
import { workflowInputsZ, WORKFLOW_INPUT_TYPES } from "./inputs.js";
|
|
@@ -280,26 +280,22 @@ export const budgetZ = z
|
|
|
280
280
|
message: "budget must declare at least one of tokens, cost_usd",
|
|
281
281
|
});
|
|
282
282
|
/**
|
|
283
|
-
* Decision model
|
|
284
|
-
*
|
|
285
|
-
*
|
|
283
|
+
* Decision model thresholds (#357) for the nodes that set `route_by:
|
|
284
|
+
* decider`. Thresholds may be raised, and lowered no further than the floors.
|
|
285
|
+
* The provider (endpoint, model, key) comes only from operator config.
|
|
286
|
+
*
|
|
287
|
+
* Shapes from the shadow-mode release fail with an explanation instead of
|
|
288
|
+
* silently changing routing: `mode` ({@link DECIDER_MODE_REMOVED}) and
|
|
289
|
+
* `provider` ({@link DECIDER_PROVIDER_MOVED}).
|
|
286
290
|
*/
|
|
287
291
|
export const deciderZ = z
|
|
288
292
|
.object({
|
|
289
|
-
mode: z.
|
|
290
|
-
provider: z
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
model: z.string().min(1),
|
|
294
|
-
api_key_env: z.string().min(1).optional(),
|
|
295
|
-
})
|
|
296
|
-
.strict()
|
|
297
|
-
.optional(),
|
|
293
|
+
mode: z.custom(() => false, { message: DECIDER_MODE_REMOVED }).optional(),
|
|
294
|
+
provider: z.custom(() => false, { message: DECIDER_PROVIDER_MOVED }).optional(),
|
|
295
|
+
min_confidence: z.number().min(DECIDER_CONFIDENCE_FLOOR).max(1).optional(),
|
|
296
|
+
min_margin: z.number().min(DECIDER_MARGIN_FLOOR).max(1).optional(),
|
|
298
297
|
})
|
|
299
|
-
.strict()
|
|
300
|
-
.refine((d) => d.mode === "off" || d.provider !== undefined, {
|
|
301
|
-
message: "decider.provider is required when mode is shadow",
|
|
302
|
-
});
|
|
298
|
+
.strict();
|
|
303
299
|
/** One typed write intent a node may emit (#365). */
|
|
304
300
|
export const safeOutputDeclarationZ = z
|
|
305
301
|
.object({
|
|
@@ -342,6 +338,7 @@ export const nodeZ = z
|
|
|
342
338
|
tools: nodeToolsZ.optional(),
|
|
343
339
|
fail_soft: z.boolean().optional(),
|
|
344
340
|
on_fail: z.enum(NODE_ON_FAIL).optional(),
|
|
341
|
+
route_by: z.enum(ROUTE_BY).optional(),
|
|
345
342
|
permissions: nodePermissionsZ.optional(),
|
|
346
343
|
outputs: z.array(safeOutputDeclarationZ).min(1).optional(),
|
|
347
344
|
rules: nodeSourcesZ.optional(),
|
|
@@ -361,6 +358,7 @@ export const edgeZ = z
|
|
|
361
358
|
// Natural language (the harness picks the edge) or `{ expr }`, a
|
|
362
359
|
// deterministic expression sweny evaluates itself (#461). One or the other.
|
|
363
360
|
when: z.union([z.string(), z.object({ expr: z.string().min(1).max(WHEN_EXPR_MAX_LENGTH) }).strict()]).optional(),
|
|
361
|
+
description: z.string().min(1).optional(),
|
|
364
362
|
max_iterations: z.number().int().min(1).max(EDGE_MAX_ITERATIONS_CEILING).optional(),
|
|
365
363
|
})
|
|
366
364
|
.strict();
|
|
@@ -478,6 +476,22 @@ export function validateWorkflow(workflow, knownSkills) {
|
|
|
478
476
|
else
|
|
479
477
|
m.set(k, [v]);
|
|
480
478
|
};
|
|
479
|
+
// One edge per (from, to) pair (#357). A route picks a target node, so two
|
|
480
|
+
// edges to the same target would make their conditions ambiguous (which
|
|
481
|
+
// one's max_iterations counts, which one's condition the router saw). Write
|
|
482
|
+
// one edge whose condition covers both cases instead.
|
|
483
|
+
const seenPairs = new Set();
|
|
484
|
+
for (const edge of workflow.edges) {
|
|
485
|
+
const key = JSON.stringify([edge.from, edge.to]);
|
|
486
|
+
if (seenPairs.has(key)) {
|
|
487
|
+
errors.push({
|
|
488
|
+
code: "DUPLICATE_EDGE",
|
|
489
|
+
message: `More than one edge from "${edge.from}" to "${edge.to}"; merge them into one edge (combine the conditions with OR)`,
|
|
490
|
+
nodeId: edge.from,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
seenPairs.add(key);
|
|
494
|
+
}
|
|
481
495
|
// Edge targets must exist
|
|
482
496
|
for (const edge of workflow.edges) {
|
|
483
497
|
if (!nodeIds.has(edge.from)) {
|
|
@@ -1000,28 +1014,24 @@ export const workflowJsonSchema = {
|
|
|
1000
1014
|
},
|
|
1001
1015
|
Decider: {
|
|
1002
1016
|
type: "object",
|
|
1003
|
-
required: ["mode"],
|
|
1004
1017
|
additionalProperties: false,
|
|
1005
|
-
description: "Decision model for
|
|
1018
|
+
description: "Decision model thresholds for the nodes that set route_by: decider. The provider (URL, model, key) comes only from operator config (.sweny.yml or SWENY_DECIDER_* env), never from the workflow; 'provider' and the removed 'mode' key are rejected.",
|
|
1006
1019
|
properties: {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
},
|
|
1020
|
+
min_confidence: {
|
|
1021
|
+
type: "number",
|
|
1022
|
+
minimum: DECIDER_CONFIDENCE_FLOOR,
|
|
1023
|
+
maximum: 1,
|
|
1024
|
+
default: DECIDER_MIN_CONFIDENCE,
|
|
1025
|
+
description: "Accept the decider's answer only at or above this confidence.",
|
|
1026
|
+
},
|
|
1027
|
+
min_margin: {
|
|
1028
|
+
type: "number",
|
|
1029
|
+
minimum: DECIDER_MARGIN_FLOOR,
|
|
1030
|
+
maximum: 1,
|
|
1031
|
+
default: DECIDER_MIN_MARGIN,
|
|
1032
|
+
description: "Accept it only when the top label leads the runner-up by at least this much.",
|
|
1021
1033
|
},
|
|
1022
1034
|
},
|
|
1023
|
-
if: { properties: { mode: { const: "shadow" } } },
|
|
1024
|
-
then: { required: ["provider"] },
|
|
1025
1035
|
},
|
|
1026
1036
|
SafeOutput: {
|
|
1027
1037
|
type: "object",
|
|
@@ -1174,7 +1184,7 @@ export const workflowJsonSchema = {
|
|
|
1174
1184
|
},
|
|
1175
1185
|
decider: {
|
|
1176
1186
|
$ref: "#/$defs/Decider",
|
|
1177
|
-
description: "
|
|
1187
|
+
description: "Thresholds for the decision model that decides the routes of route_by: decider nodes when confident. Default: 0.85 confidence, 0.2 margin.",
|
|
1178
1188
|
},
|
|
1179
1189
|
context_mode: {
|
|
1180
1190
|
type: "string",
|
|
@@ -1335,6 +1345,11 @@ export const workflowJsonSchema = {
|
|
|
1335
1345
|
enum: [...NODE_ON_FAIL],
|
|
1336
1346
|
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).",
|
|
1337
1347
|
},
|
|
1348
|
+
route_by: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
enum: [...ROUTE_BY],
|
|
1351
|
+
description: "Who picks this node's natural-language route. 'agent' (default): the agent's route evaluation. 'decider': the operator's decision model first, the agent when it is not confident. Expressions always win over both; put safety conditions in expressions.",
|
|
1352
|
+
},
|
|
1338
1353
|
permissions: {
|
|
1339
1354
|
$ref: "#/$defs/Permissions",
|
|
1340
1355
|
description: "What this node's agent may do. Absent: read when the node declares outputs, else the workflow's permissions, else write.",
|
|
@@ -1456,6 +1471,11 @@ export const workflowJsonSchema = {
|
|
|
1456
1471
|
},
|
|
1457
1472
|
],
|
|
1458
1473
|
},
|
|
1474
|
+
description: {
|
|
1475
|
+
type: "string",
|
|
1476
|
+
minLength: 1,
|
|
1477
|
+
description: "The condition in natural language. For an { expr } edge, what the next rung reads when the expression cannot be evaluated because a field it needs is missing or invalid.",
|
|
1478
|
+
},
|
|
1459
1479
|
max_iterations: {
|
|
1460
1480
|
type: "integer",
|
|
1461
1481
|
minimum: 1,
|
|
@@ -7,14 +7,51 @@
|
|
|
7
7
|
* the sweny process, with the github skill's token (`GITHUB_TOKEN`), right
|
|
8
8
|
* before it opens the PR. The agent never holds the token.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* The checkout is agent-written, so nothing in it may run, or steer the push,
|
|
11
|
+
* while the token is in play. The push therefore never runs in the checkout:
|
|
12
|
+
*
|
|
13
|
+
* 1. Read-only git in the checkout, with no token in its env, resolves the
|
|
14
|
+
* head commit, checks that every fetch and push URL of `origin` is the PR's
|
|
15
|
+
* repo on the GitHub server, and finds the object store.
|
|
16
|
+
* 2. A private temporary bare repo borrows those objects (`alternates`, data
|
|
17
|
+
* only) and gets one ref, the head commit.
|
|
18
|
+
* 3. git pushes from that repo, with no system or global config, an empty
|
|
19
|
+
* hooks dir, `--no-verify` and no credential helper, to an explicit URL
|
|
20
|
+
* built from the PR's repo (`<server>/<owner>/<repo>.git`) with an explicit
|
|
21
|
+
* refspec. No agent-written config, hook, push URL or `insteadOf` is read.
|
|
22
|
+
* The token goes to git as command-scope config in the child env, never
|
|
23
|
+
* argv, and only on this one invocation.
|
|
24
|
+
*
|
|
25
|
+
* The git binary, its PATH, the proxy and CA settings and the server URL come
|
|
26
|
+
* from the environment captured at process startup (startup-env.ts), never
|
|
27
|
+
* from a value the workspace `.env` introduced, and git is run by an absolute
|
|
28
|
+
* path that lies outside the workspace, the temp dir and any directory this
|
|
29
|
+
* user can write (see {@link resolveTrustedGit}).
|
|
30
|
+
*
|
|
31
|
+
* The private repo lives in the temp dir, which this user (and so the agent)
|
|
32
|
+
* can write. A process the agent left running could edit its config between
|
|
33
|
+
* `init` and `push` (an `insteadOf`, a proxy, `sslVerify=false`). Two layers:
|
|
34
|
+
* the push carries command-scope config for its exact URL (proxy from the
|
|
35
|
+
* trusted env or none, `sslVerify=true`, CA only from the trusted env), which
|
|
36
|
+
* beats repo-local config of the same URL; and the repo's config, alternates,
|
|
37
|
+
* top-level entries and hooks dir are fingerprinted after setup and compared
|
|
38
|
+
* right before the push, which is refused on any change. git has no switch to
|
|
39
|
+
* skip `$GIT_DIR/config`, so a byte check is the idiomatic guard. Residual
|
|
40
|
+
* risk: a change landing in the milliseconds between that check and git's own
|
|
41
|
+
* config read. The harnesses stop the agent's process (Codex its whole process
|
|
42
|
+
* group) when a node ends, but a deliberately detached process (`setsid`)
|
|
43
|
+
* escapes any group kill; only the sandbox's own process boundary ends it.
|
|
44
|
+
*
|
|
45
|
+
* Guard rails: only a local branch that exists, only when origin is the PR's
|
|
46
|
+
* own repo, never the base branch or the repo's default branch (from the
|
|
47
|
+
* GitHub API, not the checkout's `origin/HEAD`). The head must be new on the
|
|
48
|
+
* remote, or an unprotected branch with an open PR into the base (checked by
|
|
49
|
+
* the caller through the API); the push leases on the sha that check saw and
|
|
50
|
+
* only fast-forwards.
|
|
15
51
|
* A push that fails is logged and the PR is still requested: the agent may
|
|
16
52
|
* already have pushed (persisted credentials, an `agent_env` grant).
|
|
17
53
|
*/
|
|
54
|
+
import type { BranchPusher } from "../types.js";
|
|
18
55
|
export interface GitRunResult {
|
|
19
56
|
code: number;
|
|
20
57
|
stdout: string;
|
|
@@ -24,6 +61,16 @@ export type GitRunner = (args: string[], opts: {
|
|
|
24
61
|
cwd: string;
|
|
25
62
|
env: NodeJS.ProcessEnv;
|
|
26
63
|
}) => Promise<GitRunResult>;
|
|
64
|
+
/** The trusted entries of a PATH value, in order (see {@link trustedDir}). */
|
|
65
|
+
export declare function trustedPathDirs(pathValue: string | undefined, workspace: string): string[];
|
|
66
|
+
/**
|
|
67
|
+
* The absolute path of the first `git` on `pathValue` (the startup PATH) whose
|
|
68
|
+
* directory, and whose resolved file and its directory, pass
|
|
69
|
+
* {@link trustedDir}. Undefined when there is none: nothing is pushed.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveTrustedGit(pathValue: string | undefined, workspace: string): string | undefined;
|
|
72
|
+
/** A runner that executes the git at `gitPath` (absolute), never a PATH lookup. */
|
|
73
|
+
export declare function gitAt(gitPath: string): GitRunner;
|
|
27
74
|
export interface PushHeadOptions {
|
|
28
75
|
/** `owner/repo` the PR is for. */
|
|
29
76
|
repo: string;
|
|
@@ -31,12 +78,30 @@ export interface PushHeadOptions {
|
|
|
31
78
|
head: string;
|
|
32
79
|
/** The PR's base branch. */
|
|
33
80
|
base: string;
|
|
34
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* The repo's default branch, from the GitHub API. Without it nothing is
|
|
83
|
+
* pushed: the checkout's `origin/HEAD` is agent-written and not consulted.
|
|
84
|
+
*/
|
|
85
|
+
defaultBranch?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The head branch's sha on the remote, as the API reported it, or "" when it
|
|
88
|
+
* did not exist (`headPushLease` in github.ts). The push leases on it, so a
|
|
89
|
+
* branch created or moved since the check is not overwritten. Without it
|
|
90
|
+
* nothing is pushed.
|
|
91
|
+
*/
|
|
92
|
+
remoteSha?: string;
|
|
93
|
+
/** The github skill's token. Without it nothing is pushed. */
|
|
35
94
|
token?: string;
|
|
36
|
-
/** The checkout
|
|
37
|
-
cwd
|
|
95
|
+
/** The checkout (the run's `cwd`). */
|
|
96
|
+
cwd: string;
|
|
97
|
+
/**
|
|
98
|
+
* The operator's env: `GITHUB_SERVER_URL`, proxy and CA settings. Default:
|
|
99
|
+
* `process.env`, read through the startup snapshot (startup-env.ts), so a
|
|
100
|
+
* value the workspace `.env` introduced is never used. PATH always comes from
|
|
101
|
+
* the startup snapshot.
|
|
102
|
+
*/
|
|
38
103
|
env?: NodeJS.ProcessEnv;
|
|
39
|
-
/** Test seam. */
|
|
104
|
+
/** Test seam. Default: the git {@link resolveTrustedGit} finds on the startup PATH. */
|
|
40
105
|
git?: GitRunner;
|
|
41
106
|
}
|
|
42
107
|
export interface PushHeadResult {
|
|
@@ -54,9 +119,33 @@ export declare function repoFromRemote(url: string): {
|
|
|
54
119
|
} | undefined;
|
|
55
120
|
/** A branch name sweny will pass to git: plain ref characters, no option or traversal forms. */
|
|
56
121
|
export declare function isSafeBranch(name: string): boolean;
|
|
122
|
+
/** `owner/repo` with plain name characters only. */
|
|
123
|
+
export declare function isSafeRepo(repo: string): boolean;
|
|
57
124
|
/** The command-scope git config that authenticates one push to `server` with `token`. */
|
|
58
125
|
export declare function pushAuthConfig(server: string, token: string, env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
59
126
|
/**
|
|
60
|
-
*
|
|
127
|
+
* Command-scope transport config for the push to `destination`, keyed to that
|
|
128
|
+
* exact URL so it beats repo-local `http.*` and `http.<url>.*` entries: the
|
|
129
|
+
* proxy from the trusted env or none (an empty `http.proxy` disables proxying,
|
|
130
|
+
* env included), TLS verification on, and a CA bundle only from the trusted env.
|
|
131
|
+
*/
|
|
132
|
+
export declare function pushTransportConfig(destination: string, trusted: NodeJS.ProcessEnv): Array<[string, string]>;
|
|
133
|
+
/**
|
|
134
|
+
* The env every sweny-side git call runs with: the trusted PATH, no system or
|
|
135
|
+
* global config, no prompts, no inherited git settings, and no kept value the
|
|
136
|
+
* workspace `.env` introduced.
|
|
137
|
+
*/
|
|
138
|
+
export declare function hardenedGitEnv(env: NodeJS.ProcessEnv, trustedPath?: string): NodeJS.ProcessEnv;
|
|
139
|
+
/**
|
|
140
|
+
* True when a fresh `git init --bare` config holds only repo-format keys
|
|
141
|
+
* (`[core]` format/fs flags, `[extensions]` object format and ref storage).
|
|
142
|
+
*/
|
|
143
|
+
export declare function isPlainRepoConfig(text: string): boolean;
|
|
144
|
+
/** A branch name without a leading `refs/heads/`, so `refs/heads/develop` and `develop` compare equal. */
|
|
145
|
+
export declare function branchName(ref: string): string;
|
|
146
|
+
/**
|
|
147
|
+
* Push the PR's head branch from the sweny process. Never throws.
|
|
61
148
|
*/
|
|
62
149
|
export declare function pushHeadBranch(opts: PushHeadOptions): Promise<PushHeadResult>;
|
|
150
|
+
/** A pusher bound to one run's checkout and env (handed to tools as `ToolContext.pushBranch`). */
|
|
151
|
+
export declare function bindBranchPusher(cwd: string, env?: NodeJS.ProcessEnv): BranchPusher;
|