@relay-baton/shared 1.0.0
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/LICENSE +21 -0
- package/dist/constants.d.ts +54 -0
- package/dist/constants.js +77 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/safe-path.d.ts +40 -0
- package/dist/safe-path.js +117 -0
- package/dist/types.d.ts +199 -0
- package/dist/types.js +2 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DongGeon Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export declare const SESSION_DIR = ".ai-session";
|
|
2
|
+
/**
|
|
3
|
+
* v2.6 multi-session workspace. Named work items live under
|
|
4
|
+
* `.ai-session/sessions/<name>/`; the legacy flat `.ai-session/` IS the
|
|
5
|
+
* `default` work item (so existing repos need no migration). The active pointer
|
|
6
|
+
* + work-item list live in `.ai-session/workspace.json`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WORKSPACE_FILE = "workspace.json";
|
|
9
|
+
export declare const SESSIONS_SUBDIR = "sessions";
|
|
10
|
+
export declare const DEFAULT_SESSION = "default";
|
|
11
|
+
/**
|
|
12
|
+
* v1.0 stability contract. These are the frozen schema versions for the two
|
|
13
|
+
* persisted contracts relay-baton owns. Bump ONLY on a breaking shape change;
|
|
14
|
+
* loaders normalize older payloads up to the current version.
|
|
15
|
+
*/
|
|
16
|
+
export declare const CONFIG_VERSION = 1;
|
|
17
|
+
export declare const SESSION_SCHEMA_VERSION = 1;
|
|
18
|
+
/**
|
|
19
|
+
* v2.0 artifact schema registry. Current on-disk schema version for each
|
|
20
|
+
* versioned artifact relay-baton owns. The migration tooling (`migrate
|
|
21
|
+
* --check`, `doctor --deep`) compares what it finds on disk against these.
|
|
22
|
+
* Bump a value ONLY on a breaking shape change and register a migrator.
|
|
23
|
+
* Artifacts without a schemaVersion field are treated as legacy v1.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ARTIFACT_SCHEMA_VERSIONS: {
|
|
26
|
+
readonly sessionJson: 1;
|
|
27
|
+
readonly checkpoints: 1;
|
|
28
|
+
readonly gitBaseline: 1;
|
|
29
|
+
readonly conversation: 1;
|
|
30
|
+
};
|
|
31
|
+
export type VersionedArtifact = keyof typeof ARTIFACT_SCHEMA_VERSIONS;
|
|
32
|
+
export declare const SESSION_FILES: {
|
|
33
|
+
readonly task: "task.md";
|
|
34
|
+
readonly state: "state.md";
|
|
35
|
+
readonly compactState: "compact-state.md";
|
|
36
|
+
readonly handoff: "handoff.md";
|
|
37
|
+
readonly plan: "plan.md";
|
|
38
|
+
readonly decisions: "decisions.md";
|
|
39
|
+
readonly changedFiles: "changed-files.md";
|
|
40
|
+
readonly repoMap: "repo-map.md";
|
|
41
|
+
readonly commandsLog: "commands.log";
|
|
42
|
+
readonly errors: "errors.md";
|
|
43
|
+
readonly testResults: "test-results.md";
|
|
44
|
+
readonly fullDiff: "full-diff.patch";
|
|
45
|
+
readonly contextBudget: "context-budget.json";
|
|
46
|
+
readonly gitBaseline: "git-baseline.json";
|
|
47
|
+
readonly sessionJson: "session.json";
|
|
48
|
+
readonly conversation: "conversation.jsonl";
|
|
49
|
+
readonly checkpoints: "checkpoints.jsonl";
|
|
50
|
+
readonly usage: "usage.jsonl";
|
|
51
|
+
};
|
|
52
|
+
export declare const TRUNCATE_MARKER = "[TRUNCATED by relay-baton token diet: original content exceeded section budget]";
|
|
53
|
+
export declare const IGNORE_DIRS: Set<string>;
|
|
54
|
+
export declare const SKIP_DIFF_PATTERNS: RegExp[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKIP_DIFF_PATTERNS = exports.IGNORE_DIRS = exports.TRUNCATE_MARKER = exports.SESSION_FILES = exports.ARTIFACT_SCHEMA_VERSIONS = exports.SESSION_SCHEMA_VERSION = exports.CONFIG_VERSION = exports.DEFAULT_SESSION = exports.SESSIONS_SUBDIR = exports.WORKSPACE_FILE = exports.SESSION_DIR = void 0;
|
|
4
|
+
exports.SESSION_DIR = ".ai-session";
|
|
5
|
+
/**
|
|
6
|
+
* v2.6 multi-session workspace. Named work items live under
|
|
7
|
+
* `.ai-session/sessions/<name>/`; the legacy flat `.ai-session/` IS the
|
|
8
|
+
* `default` work item (so existing repos need no migration). The active pointer
|
|
9
|
+
* + work-item list live in `.ai-session/workspace.json`.
|
|
10
|
+
*/
|
|
11
|
+
exports.WORKSPACE_FILE = "workspace.json";
|
|
12
|
+
exports.SESSIONS_SUBDIR = "sessions";
|
|
13
|
+
exports.DEFAULT_SESSION = "default";
|
|
14
|
+
/**
|
|
15
|
+
* v1.0 stability contract. These are the frozen schema versions for the two
|
|
16
|
+
* persisted contracts relay-baton owns. Bump ONLY on a breaking shape change;
|
|
17
|
+
* loaders normalize older payloads up to the current version.
|
|
18
|
+
*/
|
|
19
|
+
exports.CONFIG_VERSION = 1;
|
|
20
|
+
exports.SESSION_SCHEMA_VERSION = 1;
|
|
21
|
+
/**
|
|
22
|
+
* v2.0 artifact schema registry. Current on-disk schema version for each
|
|
23
|
+
* versioned artifact relay-baton owns. The migration tooling (`migrate
|
|
24
|
+
* --check`, `doctor --deep`) compares what it finds on disk against these.
|
|
25
|
+
* Bump a value ONLY on a breaking shape change and register a migrator.
|
|
26
|
+
* Artifacts without a schemaVersion field are treated as legacy v1.
|
|
27
|
+
*/
|
|
28
|
+
exports.ARTIFACT_SCHEMA_VERSIONS = {
|
|
29
|
+
sessionJson: 1,
|
|
30
|
+
checkpoints: 1,
|
|
31
|
+
gitBaseline: 1,
|
|
32
|
+
conversation: 1,
|
|
33
|
+
};
|
|
34
|
+
exports.SESSION_FILES = {
|
|
35
|
+
task: "task.md",
|
|
36
|
+
state: "state.md",
|
|
37
|
+
compactState: "compact-state.md",
|
|
38
|
+
handoff: "handoff.md",
|
|
39
|
+
plan: "plan.md",
|
|
40
|
+
decisions: "decisions.md",
|
|
41
|
+
changedFiles: "changed-files.md",
|
|
42
|
+
repoMap: "repo-map.md",
|
|
43
|
+
commandsLog: "commands.log",
|
|
44
|
+
errors: "errors.md",
|
|
45
|
+
testResults: "test-results.md",
|
|
46
|
+
fullDiff: "full-diff.patch",
|
|
47
|
+
contextBudget: "context-budget.json",
|
|
48
|
+
gitBaseline: "git-baseline.json",
|
|
49
|
+
sessionJson: "session.json",
|
|
50
|
+
// v0.7 — append-only conversation event log (Agent Room groundwork).
|
|
51
|
+
conversation: "conversation.jsonl",
|
|
52
|
+
// v1.7 — append-only execution checkpoints (guarded execution workflow).
|
|
53
|
+
checkpoints: "checkpoints.jsonl",
|
|
54
|
+
// v2.4 — append-only local usage ledger (token/quota proxy; never transmitted).
|
|
55
|
+
usage: "usage.jsonl",
|
|
56
|
+
};
|
|
57
|
+
exports.TRUNCATE_MARKER = "[TRUNCATED by relay-baton token diet: original content exceeded section budget]";
|
|
58
|
+
exports.IGNORE_DIRS = new Set([
|
|
59
|
+
".git",
|
|
60
|
+
"node_modules",
|
|
61
|
+
"bin",
|
|
62
|
+
"obj",
|
|
63
|
+
"dist",
|
|
64
|
+
"build",
|
|
65
|
+
".next",
|
|
66
|
+
".turbo",
|
|
67
|
+
"coverage",
|
|
68
|
+
".ai-session",
|
|
69
|
+
]);
|
|
70
|
+
exports.SKIP_DIFF_PATTERNS = [
|
|
71
|
+
/(^|\/)package-lock\.json$/,
|
|
72
|
+
/(^|\/)pnpm-lock\.yaml$/,
|
|
73
|
+
/(^|\/)yarn\.lock$/,
|
|
74
|
+
/(^|\/)dist\//,
|
|
75
|
+
/(^|\/)build\//,
|
|
76
|
+
/\.min\.js$/,
|
|
77
|
+
];
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./constants"), exports);
|
|
19
|
+
__exportStar(require("./safe-path"), exports);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.2 — untrusted-bundle / path-traversal safety.
|
|
3
|
+
*
|
|
4
|
+
* Externally-received bundles and archives carry a manifest whose `target`
|
|
5
|
+
* paths we must NOT trust. A malicious manifest could point a `target` at an
|
|
6
|
+
* absolute path (`/etc/passwd`, `C:\Windows\...`), escape the bundle root with
|
|
7
|
+
* `..`, or route through a symlink that resolves outside the root. Resolving
|
|
8
|
+
* such a target and reading it would turn `inspect` into an arbitrary-file-read
|
|
9
|
+
* (and SHA-256 oracle) primitive.
|
|
10
|
+
*
|
|
11
|
+
* `resolveWithin` is the single chokepoint: it rejects absolute targets, `..`
|
|
12
|
+
* escapes, and symlinked escapes, returning a path that is provably inside
|
|
13
|
+
* `root`. Anything suspicious returns `null` — callers treat that as "unsafe"
|
|
14
|
+
* rather than reading the file.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Per-file size cap applied when an inspector reads an untrusted file to verify
|
|
18
|
+
* it (8 MiB). Curated handoff/session artifacts are small; a manifest claiming
|
|
19
|
+
* a huge file is a red flag and would also let a hostile bundle exhaust memory
|
|
20
|
+
* during verification.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MAX_INSPECT_FILE_BYTES: number;
|
|
23
|
+
export type UnsafeReason = "absolute" | "escapes-root" | "symlink-escape";
|
|
24
|
+
export interface ResolveWithinResult {
|
|
25
|
+
/** Absolute path inside `root`, or null when the target is unsafe. */
|
|
26
|
+
abs: string | null;
|
|
27
|
+
/** POSIX-style relative path from root (for display), even when unsafe. */
|
|
28
|
+
rel: string;
|
|
29
|
+
reason?: UnsafeReason;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a manifest `target` against `root`, refusing anything that would
|
|
33
|
+
* escape `root`. `root` is assumed trusted (it is where we put the bundle).
|
|
34
|
+
*
|
|
35
|
+
* Rejects, in order:
|
|
36
|
+
* - absolute `target` (`/x`, `C:\x`, `\\server\share`)
|
|
37
|
+
* - normalized path that escapes `root` via `..`
|
|
38
|
+
* - a path whose existing prefix is a symlink pointing outside `root`
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveWithin(root: string, target: string): ResolveWithinResult;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MAX_INSPECT_FILE_BYTES = void 0;
|
|
37
|
+
exports.resolveWithin = resolveWithin;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
/**
|
|
41
|
+
* v2.2 — untrusted-bundle / path-traversal safety.
|
|
42
|
+
*
|
|
43
|
+
* Externally-received bundles and archives carry a manifest whose `target`
|
|
44
|
+
* paths we must NOT trust. A malicious manifest could point a `target` at an
|
|
45
|
+
* absolute path (`/etc/passwd`, `C:\Windows\...`), escape the bundle root with
|
|
46
|
+
* `..`, or route through a symlink that resolves outside the root. Resolving
|
|
47
|
+
* such a target and reading it would turn `inspect` into an arbitrary-file-read
|
|
48
|
+
* (and SHA-256 oracle) primitive.
|
|
49
|
+
*
|
|
50
|
+
* `resolveWithin` is the single chokepoint: it rejects absolute targets, `..`
|
|
51
|
+
* escapes, and symlinked escapes, returning a path that is provably inside
|
|
52
|
+
* `root`. Anything suspicious returns `null` — callers treat that as "unsafe"
|
|
53
|
+
* rather than reading the file.
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Per-file size cap applied when an inspector reads an untrusted file to verify
|
|
57
|
+
* it (8 MiB). Curated handoff/session artifacts are small; a manifest claiming
|
|
58
|
+
* a huge file is a red flag and would also let a hostile bundle exhaust memory
|
|
59
|
+
* during verification.
|
|
60
|
+
*/
|
|
61
|
+
exports.MAX_INSPECT_FILE_BYTES = 8 * 1024 * 1024;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a manifest `target` against `root`, refusing anything that would
|
|
64
|
+
* escape `root`. `root` is assumed trusted (it is where we put the bundle).
|
|
65
|
+
*
|
|
66
|
+
* Rejects, in order:
|
|
67
|
+
* - absolute `target` (`/x`, `C:\x`, `\\server\share`)
|
|
68
|
+
* - normalized path that escapes `root` via `..`
|
|
69
|
+
* - a path whose existing prefix is a symlink pointing outside `root`
|
|
70
|
+
*/
|
|
71
|
+
function resolveWithin(root, target) {
|
|
72
|
+
const relForDisplay = toPosix(target);
|
|
73
|
+
if (path.isAbsolute(target) || /^[a-zA-Z]:[\\/]/.test(target) || /^\\\\/.test(target)) {
|
|
74
|
+
return { abs: null, rel: relForDisplay, reason: "absolute" };
|
|
75
|
+
}
|
|
76
|
+
const rootResolved = path.resolve(root);
|
|
77
|
+
const candidate = path.resolve(rootResolved, target);
|
|
78
|
+
const rel = path.relative(rootResolved, candidate);
|
|
79
|
+
if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
80
|
+
return { abs: null, rel: relForDisplay, reason: "escapes-root" };
|
|
81
|
+
}
|
|
82
|
+
// Symlink escape: walk the existing prefix and ensure realpath stays inside.
|
|
83
|
+
if (escapesViaSymlink(rootResolved, candidate)) {
|
|
84
|
+
return { abs: null, rel: relForDisplay, reason: "symlink-escape" };
|
|
85
|
+
}
|
|
86
|
+
return { abs: candidate, rel: toPosix(rel) };
|
|
87
|
+
}
|
|
88
|
+
function escapesViaSymlink(rootResolved, candidate) {
|
|
89
|
+
let realRoot;
|
|
90
|
+
try {
|
|
91
|
+
realRoot = fs.realpathSync(rootResolved);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Root itself missing — nothing to verify against; treat as in-root.
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// Find the deepest existing ancestor of `candidate` and realpath it.
|
|
98
|
+
let existing = candidate;
|
|
99
|
+
while (!fs.existsSync(existing)) {
|
|
100
|
+
const parent = path.dirname(existing);
|
|
101
|
+
if (parent === existing)
|
|
102
|
+
return false; // reached filesystem root
|
|
103
|
+
existing = parent;
|
|
104
|
+
}
|
|
105
|
+
let realExisting;
|
|
106
|
+
try {
|
|
107
|
+
realExisting = fs.realpathSync(existing);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const rel = path.relative(realRoot, realExisting);
|
|
113
|
+
return rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel);
|
|
114
|
+
}
|
|
115
|
+
function toPosix(p) {
|
|
116
|
+
return p.split(path.sep).join("/").replace(/\\/g, "/");
|
|
117
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export type AgentId = "codex" | "claude" | "opencode" | "gemini" | "aider" | "cursor";
|
|
2
|
+
export type DietProfileName = "off" | "lite" | "balanced" | "caveman" | "ultra";
|
|
3
|
+
export type SessionStatus = "initialized" | "running" | "fallback_detected" | "handoff_ready" | "running_fallback" | "planning" | "plan_ready" | "executing" | "compressing" | "completed" | "failed";
|
|
4
|
+
export type WorkflowMode = "fallback" | "plan-execute";
|
|
5
|
+
export interface DietProfile {
|
|
6
|
+
maxHandoffChars: number;
|
|
7
|
+
maxDiffChars: number;
|
|
8
|
+
maxRepoMapChars: number;
|
|
9
|
+
maxLogTailChars: number;
|
|
10
|
+
maxStateChars: number;
|
|
11
|
+
maxErrorChars: number;
|
|
12
|
+
/** v0.5 plan-execute: max chars for plan.md. Defaults to maxHandoffChars when absent. */
|
|
13
|
+
maxPlanChars?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface AuthPolicy {
|
|
16
|
+
mode: "cli-session" | "api-key";
|
|
17
|
+
allowApiKeyEnv: boolean;
|
|
18
|
+
warnIfApiKeyEnvDetected: boolean;
|
|
19
|
+
blockedEnvVars: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface AgentConfig {
|
|
22
|
+
command: string;
|
|
23
|
+
args: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface RelayBatonConfig {
|
|
26
|
+
/**
|
|
27
|
+
* v1.0 frozen-contract marker. Absent = legacy (treated as version 1).
|
|
28
|
+
* See CONFIG_VERSION in constants.
|
|
29
|
+
*/
|
|
30
|
+
configVersion?: number;
|
|
31
|
+
primaryAgent: AgentId;
|
|
32
|
+
fallbackAgent: AgentId;
|
|
33
|
+
agents: Record<string, AgentConfig>;
|
|
34
|
+
fallbackPatterns: string[];
|
|
35
|
+
commands: {
|
|
36
|
+
test: string;
|
|
37
|
+
build: string;
|
|
38
|
+
};
|
|
39
|
+
authPolicy: AuthPolicy;
|
|
40
|
+
tokenDiet: {
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
profile: DietProfileName;
|
|
43
|
+
outputCompression: boolean;
|
|
44
|
+
profiles: Record<string, DietProfile>;
|
|
45
|
+
};
|
|
46
|
+
planExecute?: {
|
|
47
|
+
defaultPlanner: AgentId;
|
|
48
|
+
defaultExecutor: AgentId;
|
|
49
|
+
maxPlanChars?: number;
|
|
50
|
+
};
|
|
51
|
+
contextCompression?: {
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
/** Auto-compress inside `run` when the threshold is crossed. */
|
|
54
|
+
auto: boolean;
|
|
55
|
+
/** Fraction (0..1) of the profile budget that triggers compression. */
|
|
56
|
+
threshold: number;
|
|
57
|
+
/** Keep the pre-compression commands.log as commands.log.full.<timestamp>. */
|
|
58
|
+
rotateRawArtifacts: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* v0.9: adaptive per-agent thresholds. When the active agent has an entry,
|
|
61
|
+
* it overrides the global `threshold`. Agents with larger context windows
|
|
62
|
+
* can run hotter (higher threshold) before compaction kicks in.
|
|
63
|
+
*/
|
|
64
|
+
perAgent?: Partial<Record<AgentId, number>>;
|
|
65
|
+
};
|
|
66
|
+
hooks?: {
|
|
67
|
+
/** Shell commands run before building a handoff (in order). */
|
|
68
|
+
preHandoff?: string[];
|
|
69
|
+
/** Shell commands run after an agent finishes a run/handoff (in order). */
|
|
70
|
+
postExecute?: string[];
|
|
71
|
+
};
|
|
72
|
+
guardrails?: {
|
|
73
|
+
/** Block when recorded execution checkpoints reach this count. */
|
|
74
|
+
maxSteps?: number;
|
|
75
|
+
/** Block when the working tree has at least this many changed files. */
|
|
76
|
+
maxChangedFiles?: number;
|
|
77
|
+
/** Block when handoff chars / maxHandoffChars reaches this ratio (0..1). */
|
|
78
|
+
maxBudgetRatio?: number;
|
|
79
|
+
/** Surface that mutating steps still require explicit human confirmation. */
|
|
80
|
+
requireConfirmation?: boolean;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export interface SessionMeta {
|
|
84
|
+
/**
|
|
85
|
+
* v1.0 frozen-contract marker. Absent = legacy (treated as version 1).
|
|
86
|
+
* See SESSION_SCHEMA_VERSION in constants.
|
|
87
|
+
*/
|
|
88
|
+
schemaVersion?: number;
|
|
89
|
+
id: string;
|
|
90
|
+
createdAt: string;
|
|
91
|
+
updatedAt: string;
|
|
92
|
+
repoRoot: string;
|
|
93
|
+
task: string;
|
|
94
|
+
status: SessionStatus;
|
|
95
|
+
primaryAgent: AgentId;
|
|
96
|
+
fallbackAgent: AgentId;
|
|
97
|
+
activeAgent: AgentId | "none";
|
|
98
|
+
lastAgent: AgentId | "none";
|
|
99
|
+
fallbackReason: string | null;
|
|
100
|
+
lastError: string | null;
|
|
101
|
+
tokenDietProfile: DietProfileName;
|
|
102
|
+
/** ISO timestamp when the most recent `run` / `handoff` command kicked off. */
|
|
103
|
+
startedAt?: string;
|
|
104
|
+
/** ISO timestamp when the session transitioned to `completed` or `failed`. */
|
|
105
|
+
endedAt?: string;
|
|
106
|
+
/** endedAt - startedAt, in milliseconds. Cleared on a new run/handoff. */
|
|
107
|
+
durationMs?: number;
|
|
108
|
+
/** Total number of handoff documents successfully written in this session. */
|
|
109
|
+
handoffCount?: number;
|
|
110
|
+
/** Which workflow produced this session. Absent = legacy fallback flow. */
|
|
111
|
+
workflowMode?: WorkflowMode;
|
|
112
|
+
/** Agent that authored .ai-session/plan.md. */
|
|
113
|
+
planAuthor?: AgentId | null;
|
|
114
|
+
/** Agent that executes from the plan. */
|
|
115
|
+
executor?: AgentId | null;
|
|
116
|
+
/** ISO timestamp when plan.md passed its quality gate. */
|
|
117
|
+
planFinalizedAt?: string;
|
|
118
|
+
/** ISO timestamp when the execute phase started. */
|
|
119
|
+
executeStartedAt?: string;
|
|
120
|
+
}
|
|
121
|
+
export interface AgentRunInput {
|
|
122
|
+
task: string;
|
|
123
|
+
repoRoot: string;
|
|
124
|
+
sessionDir: string;
|
|
125
|
+
prompt?: string;
|
|
126
|
+
dietProfile?: DietProfileName;
|
|
127
|
+
allowApiKeyEnv?: boolean;
|
|
128
|
+
}
|
|
129
|
+
export interface AgentCommand {
|
|
130
|
+
command: string;
|
|
131
|
+
args: string[];
|
|
132
|
+
cwd: string;
|
|
133
|
+
}
|
|
134
|
+
export interface AgentEvent {
|
|
135
|
+
type: "stdout" | "stderr" | "exit" | "error" | "fallback";
|
|
136
|
+
text?: string;
|
|
137
|
+
code?: number;
|
|
138
|
+
reason?: string;
|
|
139
|
+
}
|
|
140
|
+
export interface BudgetSnapshot {
|
|
141
|
+
profile: DietProfileName;
|
|
142
|
+
maxHandoffChars: number;
|
|
143
|
+
used: {
|
|
144
|
+
handoff: number;
|
|
145
|
+
repoMap: number;
|
|
146
|
+
fullDiff: number;
|
|
147
|
+
commandsLog: number;
|
|
148
|
+
compactState: number;
|
|
149
|
+
};
|
|
150
|
+
truncated: boolean;
|
|
151
|
+
}
|
|
152
|
+
export type ConversationRole = "user" | "claude" | "codex" | "relay-baton";
|
|
153
|
+
export type ConversationEventKind = "message" | "command" | "prompt_preview" | "agent_run" | "plan" | "execute" | "review" | "diagnose" | "handoff" | "status" | "budget";
|
|
154
|
+
export interface ConversationEvent {
|
|
155
|
+
/** v2.0 versioned-contract marker; absent in pre-v2.0 events (legacy v1). */
|
|
156
|
+
schemaVersion?: number;
|
|
157
|
+
/** Stable id, e.g. "evt_<uuid>". */
|
|
158
|
+
id: string;
|
|
159
|
+
/** ISO timestamp. */
|
|
160
|
+
ts: string;
|
|
161
|
+
/** Owning session (SessionMeta.id) when known. */
|
|
162
|
+
sessionId?: string;
|
|
163
|
+
role: ConversationRole;
|
|
164
|
+
kind: ConversationEventKind;
|
|
165
|
+
/** Token-diet-friendly summary. NEVER a full diff/log — reference instead. */
|
|
166
|
+
text: string;
|
|
167
|
+
/** Pointers to .ai-session artifacts (relative names), not inlined blobs. */
|
|
168
|
+
refs?: Record<string, string>;
|
|
169
|
+
/** Kind-specific, all optional. */
|
|
170
|
+
meta?: {
|
|
171
|
+
agent?: AgentId;
|
|
172
|
+
diet?: DietProfileName;
|
|
173
|
+
exitCode?: number;
|
|
174
|
+
confirmed?: boolean;
|
|
175
|
+
stepRefs?: string[];
|
|
176
|
+
[k: string]: unknown;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export interface BatonProject {
|
|
180
|
+
id: string;
|
|
181
|
+
name: string;
|
|
182
|
+
path: string;
|
|
183
|
+
createdAt: string;
|
|
184
|
+
updatedAt: string;
|
|
185
|
+
lastUsedAt?: string;
|
|
186
|
+
defaultDiet?: DietProfileName;
|
|
187
|
+
primaryAgent?: AgentId;
|
|
188
|
+
fallbackAgent?: AgentId;
|
|
189
|
+
/**
|
|
190
|
+
* v0.8: project-level fallback-pattern overlay. When set and non-empty,
|
|
191
|
+
* these are appended to the global config.fallbackPatterns (deduped,
|
|
192
|
+
* case-insensitive) for runs scoped to this project.
|
|
193
|
+
*/
|
|
194
|
+
fallbackPatterns?: string[];
|
|
195
|
+
}
|
|
196
|
+
export interface ProjectRegistryData {
|
|
197
|
+
activeProjectId: string | null;
|
|
198
|
+
projects: BatonProject[];
|
|
199
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@relay-baton/shared",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared utilities for relay-baton (safe-path guards and common helpers)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "DongGeon Lee",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/dgl1231/relay-baton.git",
|
|
10
|
+
"directory": "packages/shared"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/dgl1231/relay-baton#readme",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
28
|
+
"test": "echo \"no tests\" && exit 0"
|
|
29
|
+
}
|
|
30
|
+
}
|