patchwork-os 1.2.0-beta.2.canary.625 → 1.2.0-beta.2.canary.626
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.
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information boundary — declared labels and the boundary decision (ADR-0021,
|
|
3
|
+
* Phases 1 and 2).
|
|
4
|
+
*
|
|
5
|
+
* The invariant this exists to hold:
|
|
6
|
+
*
|
|
7
|
+
* > No model-bound context leaves Patchwork without passing the
|
|
8
|
+
* > information-boundary decision point.
|
|
9
|
+
*
|
|
10
|
+
* Two properties are deliberate and load-bearing.
|
|
11
|
+
*
|
|
12
|
+
* **Declared, never detected.** A step says what it carries; a destination says
|
|
13
|
+
* what it may receive. Nothing is inferred. ADR-0021 rules detection out on the
|
|
14
|
+
* grounds that a detector which IS the boundary fails silently on everything it
|
|
15
|
+
* does not recognise, and its recall is unknowable. A detector may later suggest
|
|
16
|
+
* a classification; policy still decides.
|
|
17
|
+
*
|
|
18
|
+
* **Pure.** The decision is a function of (declared policy, destination policy)
|
|
19
|
+
* and nothing else — no clock, no filesystem, no model. That is what makes it
|
|
20
|
+
* testable without a model in the loop, and what stops the boundary being
|
|
21
|
+
* enforced by the thing it constrains.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Sensitivity ladder, ordered. The order is the whole comparison: a destination
|
|
25
|
+
* cleared for `internal` is cleared for `public`, never the reverse.
|
|
26
|
+
*/
|
|
27
|
+
export declare const CLASSIFICATIONS: readonly ["public", "internal", "personal", "confidential", "restricted"];
|
|
28
|
+
export type Classification = (typeof CLASSIFICATIONS)[number];
|
|
29
|
+
/**
|
|
30
|
+
* Default for a step that declares nothing.
|
|
31
|
+
*
|
|
32
|
+
* `internal` rather than `restricted`, deliberately. Every existing recipe
|
|
33
|
+
* declares nothing, so a strict default would refuse the entire installed base
|
|
34
|
+
* on upgrade to solve a problem those installs do not have — and a boundary
|
|
35
|
+
* that breaks everything on day one gets switched off, which protects nobody.
|
|
36
|
+
* Same fail-soft reasoning as the identity roster's implicit owner.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_CLASSIFICATION: Classification;
|
|
39
|
+
export declare function classificationRank(c: Classification): number;
|
|
40
|
+
/** What a step declares it is carrying. */
|
|
41
|
+
export interface DataPolicy {
|
|
42
|
+
classification: Classification;
|
|
43
|
+
/** Free-form tags (e.g. "financial"). Used for redaction, not for ranking. */
|
|
44
|
+
categories?: string[];
|
|
45
|
+
}
|
|
46
|
+
/** What a destination declares it may receive. */
|
|
47
|
+
export interface Destination {
|
|
48
|
+
id: string;
|
|
49
|
+
/** `local` never leaves the machine; `remote` crosses a network boundary. */
|
|
50
|
+
type: "local" | "remote";
|
|
51
|
+
/** Classifications this destination is cleared for. */
|
|
52
|
+
classifications: Classification[];
|
|
53
|
+
/**
|
|
54
|
+
* Categories this destination must never receive even when it is cleared for
|
|
55
|
+
* the classification. Redaction removes them; if they cannot be removed the
|
|
56
|
+
* decision escalates rather than silently sending them.
|
|
57
|
+
*/
|
|
58
|
+
forbiddenCategories?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Whether a human may authorise an otherwise-refused send. Absent means no:
|
|
61
|
+
* the refusal stands, which keeps DENY meaningful as "no approval unlocks
|
|
62
|
+
* this" exactly as the autonomy gate's `forbid` does.
|
|
63
|
+
*/
|
|
64
|
+
approvable?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The five outcomes.
|
|
68
|
+
*
|
|
69
|
+
* `LOCAL_ONLY` is distinct from `DENY` on purpose: it means the data may be
|
|
70
|
+
* processed, just not here. It is a routing instruction, and the caller is
|
|
71
|
+
* expected to retry against a local destination rather than fail the step.
|
|
72
|
+
*/
|
|
73
|
+
export type BoundaryDecision = "ALLOW" | "ALLOW_REDACTED" | "LOCAL_ONLY" | "REQUIRE_APPROVAL" | "DENY";
|
|
74
|
+
export interface BoundaryOutcome {
|
|
75
|
+
decision: BoundaryDecision;
|
|
76
|
+
/** One sentence, safe to show a human. Never contains the payload. */
|
|
77
|
+
reason: string;
|
|
78
|
+
/** Categories that must be removed before dispatch (ALLOW_REDACTED only). */
|
|
79
|
+
redactCategories?: string[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Decide whether `policy` may be sent to `destination`.
|
|
83
|
+
*
|
|
84
|
+
* The rule table, in evaluation order:
|
|
85
|
+
*
|
|
86
|
+
* 1. destination not cleared for the classification
|
|
87
|
+
* → LOCAL_ONLY if a local destination could take it, else
|
|
88
|
+
* REQUIRE_APPROVAL if the destination is approvable, else DENY
|
|
89
|
+
* 2. cleared, but carries a forbidden category
|
|
90
|
+
* → ALLOW_REDACTED (the category is removable)
|
|
91
|
+
* 3. otherwise → ALLOW
|
|
92
|
+
*
|
|
93
|
+
* Rule 1 runs FIRST and is not reachable past. A destination cannot become
|
|
94
|
+
* cleared by redacting a category, because the classification is a property of
|
|
95
|
+
* the step as a whole — dropping a tag does not declassify what remains.
|
|
96
|
+
*/
|
|
97
|
+
export declare function decideBoundary(policy: DataPolicy, destination: Destination, opts?: {
|
|
98
|
+
localDestinationAccepts?: boolean;
|
|
99
|
+
}): BoundaryOutcome;
|
|
100
|
+
/**
|
|
101
|
+
* Parse a step's declared `data_policy`, falling back to the default.
|
|
102
|
+
*
|
|
103
|
+
* An UNRECOGNISED classification is not silently defaulted — it returns null so
|
|
104
|
+
* the caller can fail closed. Defaulting a typo to `internal` would let
|
|
105
|
+
* `classification: confidentail` sail through as ordinary internal data, which
|
|
106
|
+
* is precisely the failure a declared-labels scheme cannot afford: the operator
|
|
107
|
+
* believes they labelled it and the system believes they did not.
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseDataPolicy(raw: unknown): DataPolicy | null;
|
|
110
|
+
export declare function narrowest(a: BoundaryOutcome, b: BoundaryOutcome): BoundaryOutcome;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information boundary — declared labels and the boundary decision (ADR-0021,
|
|
3
|
+
* Phases 1 and 2).
|
|
4
|
+
*
|
|
5
|
+
* The invariant this exists to hold:
|
|
6
|
+
*
|
|
7
|
+
* > No model-bound context leaves Patchwork without passing the
|
|
8
|
+
* > information-boundary decision point.
|
|
9
|
+
*
|
|
10
|
+
* Two properties are deliberate and load-bearing.
|
|
11
|
+
*
|
|
12
|
+
* **Declared, never detected.** A step says what it carries; a destination says
|
|
13
|
+
* what it may receive. Nothing is inferred. ADR-0021 rules detection out on the
|
|
14
|
+
* grounds that a detector which IS the boundary fails silently on everything it
|
|
15
|
+
* does not recognise, and its recall is unknowable. A detector may later suggest
|
|
16
|
+
* a classification; policy still decides.
|
|
17
|
+
*
|
|
18
|
+
* **Pure.** The decision is a function of (declared policy, destination policy)
|
|
19
|
+
* and nothing else — no clock, no filesystem, no model. That is what makes it
|
|
20
|
+
* testable without a model in the loop, and what stops the boundary being
|
|
21
|
+
* enforced by the thing it constrains.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Sensitivity ladder, ordered. The order is the whole comparison: a destination
|
|
25
|
+
* cleared for `internal` is cleared for `public`, never the reverse.
|
|
26
|
+
*/
|
|
27
|
+
export const CLASSIFICATIONS = [
|
|
28
|
+
"public",
|
|
29
|
+
"internal",
|
|
30
|
+
"personal",
|
|
31
|
+
"confidential",
|
|
32
|
+
"restricted",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Default for a step that declares nothing.
|
|
36
|
+
*
|
|
37
|
+
* `internal` rather than `restricted`, deliberately. Every existing recipe
|
|
38
|
+
* declares nothing, so a strict default would refuse the entire installed base
|
|
39
|
+
* on upgrade to solve a problem those installs do not have — and a boundary
|
|
40
|
+
* that breaks everything on day one gets switched off, which protects nobody.
|
|
41
|
+
* Same fail-soft reasoning as the identity roster's implicit owner.
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_CLASSIFICATION = "internal";
|
|
44
|
+
export function classificationRank(c) {
|
|
45
|
+
return CLASSIFICATIONS.indexOf(c);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decide whether `policy` may be sent to `destination`.
|
|
49
|
+
*
|
|
50
|
+
* The rule table, in evaluation order:
|
|
51
|
+
*
|
|
52
|
+
* 1. destination not cleared for the classification
|
|
53
|
+
* → LOCAL_ONLY if a local destination could take it, else
|
|
54
|
+
* REQUIRE_APPROVAL if the destination is approvable, else DENY
|
|
55
|
+
* 2. cleared, but carries a forbidden category
|
|
56
|
+
* → ALLOW_REDACTED (the category is removable)
|
|
57
|
+
* 3. otherwise → ALLOW
|
|
58
|
+
*
|
|
59
|
+
* Rule 1 runs FIRST and is not reachable past. A destination cannot become
|
|
60
|
+
* cleared by redacting a category, because the classification is a property of
|
|
61
|
+
* the step as a whole — dropping a tag does not declassify what remains.
|
|
62
|
+
*/
|
|
63
|
+
export function decideBoundary(policy, destination, opts = {}) {
|
|
64
|
+
const cleared = destination.classifications.includes(policy.classification);
|
|
65
|
+
if (!cleared) {
|
|
66
|
+
if (destination.type === "remote" && opts.localDestinationAccepts) {
|
|
67
|
+
return {
|
|
68
|
+
decision: "LOCAL_ONLY",
|
|
69
|
+
reason: `"${policy.classification}" may not leave the machine; a local destination accepts it`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (destination.approvable) {
|
|
73
|
+
return {
|
|
74
|
+
decision: "REQUIRE_APPROVAL",
|
|
75
|
+
reason: `"${destination.id}" is not cleared for "${policy.classification}"; a human may authorise this send`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
decision: "DENY",
|
|
80
|
+
reason: `"${destination.id}" is not cleared for "${policy.classification}" and no approval can unlock it`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const forbidden = destination.forbiddenCategories ?? [];
|
|
84
|
+
const carried = policy.categories ?? [];
|
|
85
|
+
const hits = carried.filter((c) => forbidden.includes(c));
|
|
86
|
+
if (hits.length > 0) {
|
|
87
|
+
return {
|
|
88
|
+
decision: "ALLOW_REDACTED",
|
|
89
|
+
reason: `"${destination.id}" is cleared for "${policy.classification}" but must not receive: ${hits.join(", ")}`,
|
|
90
|
+
redactCategories: hits,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
decision: "ALLOW",
|
|
95
|
+
reason: `"${destination.id}" is cleared for "${policy.classification}"`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Parse a step's declared `data_policy`, falling back to the default.
|
|
100
|
+
*
|
|
101
|
+
* An UNRECOGNISED classification is not silently defaulted — it returns null so
|
|
102
|
+
* the caller can fail closed. Defaulting a typo to `internal` would let
|
|
103
|
+
* `classification: confidentail` sail through as ordinary internal data, which
|
|
104
|
+
* is precisely the failure a declared-labels scheme cannot afford: the operator
|
|
105
|
+
* believes they labelled it and the system believes they did not.
|
|
106
|
+
*/
|
|
107
|
+
export function parseDataPolicy(raw) {
|
|
108
|
+
if (raw === undefined || raw === null) {
|
|
109
|
+
return { classification: DEFAULT_CLASSIFICATION };
|
|
110
|
+
}
|
|
111
|
+
if (typeof raw !== "object")
|
|
112
|
+
return null;
|
|
113
|
+
const obj = raw;
|
|
114
|
+
const c = obj.classification;
|
|
115
|
+
let classification;
|
|
116
|
+
if (c === undefined) {
|
|
117
|
+
classification = DEFAULT_CLASSIFICATION;
|
|
118
|
+
}
|
|
119
|
+
else if (typeof c === "string" &&
|
|
120
|
+
CLASSIFICATIONS.includes(c)) {
|
|
121
|
+
classification = c;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const categories = Array.isArray(obj.categories)
|
|
127
|
+
? obj.categories.filter((x) => typeof x === "string")
|
|
128
|
+
: undefined;
|
|
129
|
+
return categories ? { classification, categories } : { classification };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* NEVER-WIDEN composition, mirroring the autonomy gate's rule.
|
|
133
|
+
*
|
|
134
|
+
* A later stage may restrict further; it may never restore what an earlier one
|
|
135
|
+
* removed. Applied by taking the MORE restrictive of two decisions, so ordering
|
|
136
|
+
* the stages wrongly cannot loosen the result.
|
|
137
|
+
*/
|
|
138
|
+
const RESTRICTIVENESS = {
|
|
139
|
+
ALLOW: 0,
|
|
140
|
+
ALLOW_REDACTED: 1,
|
|
141
|
+
LOCAL_ONLY: 2,
|
|
142
|
+
REQUIRE_APPROVAL: 3,
|
|
143
|
+
DENY: 4,
|
|
144
|
+
};
|
|
145
|
+
export function narrowest(a, b) {
|
|
146
|
+
return RESTRICTIVENESS[b.decision] > RESTRICTIVENESS[a.decision] ? b : a;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=dataPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataPolicy.js","sourceRoot":"","sources":["../../src/privacy/dataPolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* CHANGELOG: chained users with model:local in ~/.patchwork/config now route to
|
|
8
8
|
* localFn (Ollama/LM Studio) instead of Anthropic API — opt-in behaviour change.
|
|
9
9
|
*/
|
|
10
|
+
import { type Destination } from "../privacy/dataPolicy.js";
|
|
10
11
|
/**
|
|
11
12
|
* Token usage reported by an adapter. Both fields are integers; absent
|
|
12
13
|
* `usage` on the parent `AgentResult` means the driver didn't surface
|
|
@@ -42,6 +43,17 @@ export interface AgentResult {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
export interface AgentExecutorDeps {
|
|
46
|
+
/**
|
|
47
|
+
* Receipt sink for information-boundary decisions (ADR-0021 Phase 3).
|
|
48
|
+
* Optional: an unwired sink means no receipt, never a refusal — the
|
|
49
|
+
* boundary must not depend on its own audit trail being configured.
|
|
50
|
+
*/
|
|
51
|
+
recordBoundaryDecisionFn?: (r: {
|
|
52
|
+
decision: string;
|
|
53
|
+
reason: string;
|
|
54
|
+
destinationId?: string;
|
|
55
|
+
redactCategories?: string[];
|
|
56
|
+
}) => void;
|
|
45
57
|
anthropicFn: (prompt: string, model: string) => Promise<AgentResult>;
|
|
46
58
|
/** Handles openai, grok, gemini, gemini-api, codex — passes driver name through. */
|
|
47
59
|
providerDriverFn: (driver: "openai" | "grok" | "gemini" | "gemini-api" | "codex", prompt: string, model: string | undefined,
|
|
@@ -82,6 +94,19 @@ export interface AgentExecutorInput {
|
|
|
82
94
|
allowedTools?: string[];
|
|
83
95
|
/** Deny rules via --disallowed-tools (any mode). */
|
|
84
96
|
disallowedTools?: string[];
|
|
97
|
+
/**
|
|
98
|
+
* Information-boundary context (ADR-0021). Absent means the step declared no
|
|
99
|
+
* `data_policy` and no destination is registered, which is byte-identical to
|
|
100
|
+
* pre-boundary behaviour — the overwhelmingly common case on upgrade.
|
|
101
|
+
*/
|
|
102
|
+
boundary?: {
|
|
103
|
+
/** Raw `data_policy` as declared on the step. Parsed here, not by callers. */
|
|
104
|
+
dataPolicy?: unknown;
|
|
105
|
+
/** Where this prompt is about to go. */
|
|
106
|
+
destination?: Destination;
|
|
107
|
+
/** Whether ANY registered local destination accepts the classification. */
|
|
108
|
+
localDestinationAccepts?: boolean;
|
|
109
|
+
};
|
|
85
110
|
/**
|
|
86
111
|
* Worker-autonomy hard requirement. When true, this agent step carries a
|
|
87
112
|
* worker-mandated tool sandbox (see disallowedToolsForAgentStep) that ONLY the
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* CHANGELOG: chained users with model:local in ~/.patchwork/config now route to
|
|
8
8
|
* localFn (Ollama/LM Studio) instead of Anthropic API — opt-in behaviour change.
|
|
9
9
|
*/
|
|
10
|
+
import { decideBoundary, parseDataPolicy, } from "../privacy/dataPolicy.js";
|
|
10
11
|
import { resolveLocalModel } from "./localSettings.js";
|
|
11
12
|
/**
|
|
12
13
|
* Model the anthropic/local agent paths fall back to when a step omits `model`.
|
|
@@ -50,6 +51,33 @@ const CODEX_WORKER_SANDBOX_LOCKDOWN = {
|
|
|
50
51
|
networkAccess: false,
|
|
51
52
|
webSearch: false,
|
|
52
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Evaluate the information boundary for one agent step.
|
|
56
|
+
*
|
|
57
|
+
* Returns null when the step declares nothing AND no destination is registered
|
|
58
|
+
* — byte-identical to pre-boundary behaviour, which is the state of every
|
|
59
|
+
* existing install.
|
|
60
|
+
*
|
|
61
|
+
* A MALFORMED `data_policy` fails closed with DENY rather than defaulting. The
|
|
62
|
+
* operator wrote a label; silently reading it as `internal` because of a typo
|
|
63
|
+
* would leave them believing data was protected when it was not.
|
|
64
|
+
*/
|
|
65
|
+
function evaluateBoundary(ctx) {
|
|
66
|
+
if (!ctx?.destination)
|
|
67
|
+
return null;
|
|
68
|
+
const policy = parseDataPolicy(ctx.dataPolicy);
|
|
69
|
+
if (policy === null) {
|
|
70
|
+
return {
|
|
71
|
+
decision: "DENY",
|
|
72
|
+
reason: "data_policy declares an unrecognised classification — refusing rather than defaulting a typo to `internal`",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return decideBoundary(policy, ctx.destination, {
|
|
76
|
+
...(ctx.localDestinationAccepts !== undefined && {
|
|
77
|
+
localDestinationAccepts: ctx.localDestinationAccepts,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
53
81
|
export async function executeAgent(input, deps) {
|
|
54
82
|
const { prompt, driver, model, mcpAccess, sandbox, allowedTools, disallowedTools, providerOptions, enforceSandbox, } = input;
|
|
55
83
|
// NEVER-WIDEN guard. A worker-mandated sandbox is enforceable only on drivers
|
|
@@ -66,6 +94,42 @@ export async function executeAgent(input, deps) {
|
|
|
66
94
|
servedBy: { driver: driver ?? "auto" },
|
|
67
95
|
};
|
|
68
96
|
}
|
|
97
|
+
// ── INFORMATION BOUNDARY (ADR-0021) ─────────────────────────────────────
|
|
98
|
+
// Evaluated BEFORE dispatch, and here rather than in `costRouter`, which is
|
|
99
|
+
// the tempting seam and the wrong one: costRouter short-circuits with no
|
|
100
|
+
// downshift list and no USD cap, so binding privacy there would cover only
|
|
101
|
+
// budgeted steps while presenting as total enforcement. This function is on
|
|
102
|
+
// the unconditional path for both runners.
|
|
103
|
+
//
|
|
104
|
+
// Precedence is privacy -> capability -> cost, and it is not negotiable by
|
|
105
|
+
// the later stages: a cheaper or more capable model that is not authorised
|
|
106
|
+
// for the data does not become authorised by being cheaper or more capable.
|
|
107
|
+
const boundary = evaluateBoundary(input.boundary);
|
|
108
|
+
if (boundary && boundary.decision !== "ALLOW") {
|
|
109
|
+
deps.recordBoundaryDecisionFn?.({
|
|
110
|
+
decision: boundary.decision,
|
|
111
|
+
reason: boundary.reason,
|
|
112
|
+
destinationId: input.boundary?.destination?.id,
|
|
113
|
+
...(boundary.redactCategories && {
|
|
114
|
+
redactCategories: boundary.redactCategories,
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
// ALLOW_REDACTED is not implemented as a transform in this phase, and is
|
|
118
|
+
// therefore REFUSED rather than silently sent unredacted. Failing closed on
|
|
119
|
+
// "we know something must be removed and cannot remove it" is the whole
|
|
120
|
+
// point; the alternative sends the data and logs that it should not have.
|
|
121
|
+
return {
|
|
122
|
+
text: `[agent step failed: information boundary — ${boundary.reason}]`,
|
|
123
|
+
servedBy: { driver: driver ?? "auto" },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (boundary) {
|
|
127
|
+
deps.recordBoundaryDecisionFn?.({
|
|
128
|
+
decision: boundary.decision,
|
|
129
|
+
reason: boundary.reason,
|
|
130
|
+
destinationId: input.boundary?.destination?.id,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
69
133
|
const cliOpts = mcpAccess !== undefined ||
|
|
70
134
|
sandbox !== undefined ||
|
|
71
135
|
allowedTools !== undefined ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentExecutor.js","sourceRoot":"","sources":["../../src/recipes/agentExecutor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"agentExecutor.js","sourceRoot":"","sources":["../../src/recipes/agentExecutor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwork-os",
|
|
3
|
-
"version": "1.2.0-beta.2.canary.
|
|
3
|
+
"version": "1.2.0-beta.2.canary.626",
|
|
4
4
|
"description": "Your personal AI runtime, local-first. Patchwork OS gives any AI model a consistent set of tools, YAML recipes, a delegation policy with approval queue, and a durable trace memory — all on your machine, all under your policy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|