@pellux/goodvibes-sdk 0.36.0 → 0.37.1
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/contracts/artifacts/operator-contract.json +1 -1
- package/dist/platform/core/compaction-sections.d.ts +8 -0
- package/dist/platform/core/compaction-sections.d.ts.map +1 -1
- package/dist/platform/core/compaction-sections.js +71 -2
- package/dist/platform/core/context-compaction.d.ts +13 -0
- package/dist/platform/core/context-compaction.d.ts.map +1 -1
- package/dist/platform/core/context-compaction.js +24 -2
- package/dist/platform/core/orchestrator-context-runtime.d.ts.map +1 -1
- package/dist/platform/core/orchestrator-context-runtime.js +1 -2
- package/dist/platform/core/orchestrator-tool-runtime.d.ts.map +1 -1
- package/dist/platform/core/orchestrator-tool-runtime.js +30 -2
- package/dist/platform/permissions/manager.d.ts.map +1 -1
- package/dist/platform/permissions/manager.js +3 -2
- package/dist/platform/permissions/prompt.d.ts +8 -1
- package/dist/platform/permissions/prompt.d.ts.map +1 -1
- package/dist/platform/permissions/types.d.ts +6 -0
- package/dist/platform/permissions/types.d.ts.map +1 -1
- package/dist/platform/runtime/services.d.ts +2 -0
- package/dist/platform/runtime/services.d.ts.map +1 -1
- package/dist/platform/runtime/services.js +15 -0
- package/dist/platform/tools/index.d.ts +1 -0
- package/dist/platform/tools/index.d.ts.map +1 -1
- package/dist/platform/version.js +1 -1
- package/dist/platform/workspace/checkpoint/index.d.ts +9 -0
- package/dist/platform/workspace/checkpoint/index.d.ts.map +1 -0
- package/dist/platform/workspace/checkpoint/index.js +7 -0
- package/dist/platform/workspace/checkpoint/manager.d.ts +214 -0
- package/dist/platform/workspace/checkpoint/manager.d.ts.map +1 -0
- package/dist/platform/workspace/checkpoint/manager.js +543 -0
- package/dist/platform/workspace/checkpoint/side-git.d.ts +130 -0
- package/dist/platform/workspace/checkpoint/side-git.d.ts.map +1 -0
- package/dist/platform/workspace/checkpoint/side-git.js +264 -0
- package/dist/platform/workspace/checkpoint/types.d.ts +96 -0
- package/dist/platform/workspace/checkpoint/types.d.ts.map +1 -0
- package/dist/platform/workspace/checkpoint/types.js +20 -0
- package/dist/platform/workspace/index.d.ts +1 -0
- package/dist/platform/workspace/index.d.ts.map +1 -1
- package/dist/platform/workspace/index.js +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* side-git.ts
|
|
3
|
+
*
|
|
4
|
+
* SideGitRunner — a hidden git repository ("side repo") whose object store
|
|
5
|
+
* (GIT_DIR) lives under <workspaceRoot>/.goodvibes/checkpoints/git while its
|
|
6
|
+
* GIT_WORK_TREE is the live workspace itself.
|
|
7
|
+
*
|
|
8
|
+
* This is the dotfiles-bare-repo trick (`git --git-dir=X --work-tree=Y`):
|
|
9
|
+
* git tracks arbitrary files in Y using an object store rooted at X,
|
|
10
|
+
* completely independent of whatever real .git directory Y may or may not
|
|
11
|
+
* already have. It gives us, for free:
|
|
12
|
+
* - content-addressed, deduped storage (unchanged files cost ~nothing)
|
|
13
|
+
* - `git diff` / `git diff --stat` between any two snapshots
|
|
14
|
+
* - whole-tree restore via `read-tree` + `checkout-index`
|
|
15
|
+
* - correct behavior in a workspace that is NOT itself a git repo
|
|
16
|
+
*
|
|
17
|
+
* DO NOT reuse GitService (../../git/service.ts) for this: it binds a single
|
|
18
|
+
* baseDir with no GIT_DIR support, and it fires Pre/Post hook events on
|
|
19
|
+
* every commit/add — automatic silent snapshots must never trigger a user's
|
|
20
|
+
* PreCommit/PostCommit hooks. AgentWorktree (../../agents/worktree.ts) already
|
|
21
|
+
* proves the `simpleGit(...).raw([...])` + explicit env pattern used here.
|
|
22
|
+
*
|
|
23
|
+
* Checkpoints are addressed entirely through our own ref namespace
|
|
24
|
+
* (refs/goodvibes/checkpoints/<id>) and through commit objects created via
|
|
25
|
+
* `commit-tree`, never through the side repo's HEAD/branch. There is no
|
|
26
|
+
* meaningful "current branch" in this design — parent/lineage is tracked in
|
|
27
|
+
* the manifest (manager.ts), not via git HEAD — so there is nothing to leave
|
|
28
|
+
* "detached" and no branch state to pollute.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
31
|
+
import { join } from 'node:path';
|
|
32
|
+
import { simpleGit } from 'simple-git';
|
|
33
|
+
import { logger } from '../../utils/logger.js';
|
|
34
|
+
import { summarizeError } from '../../utils/error-display.js';
|
|
35
|
+
/** Git's well-known empty-tree object hash, valid in every repository. */
|
|
36
|
+
export const EMPTY_TREE_HASH = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
37
|
+
/** Ref namespace all workspace checkpoints live under. */
|
|
38
|
+
export const CHECKPOINT_REF_PREFIX = 'refs/goodvibes/checkpoints/';
|
|
39
|
+
/** Fallback identity used for checkpoint commits so this never depends on the user's global git config. */
|
|
40
|
+
const FALLBACK_IDENTITY = { name: 'GoodVibes Checkpoints', email: 'checkpoints@goodvibes.local' };
|
|
41
|
+
/**
|
|
42
|
+
* Environment variable names that simple-git's built-in vulnerability scanner
|
|
43
|
+
* treats as unsafe (it would otherwise throw on every raw call rather than
|
|
44
|
+
* risk spawning an interactive editor/pager/askpass/proxy). We never need
|
|
45
|
+
* any of these — every commit here is created via `commit-tree -m <message>`,
|
|
46
|
+
* never a bare `commit`/`rebase -i`/`merge` — so stripping them from the
|
|
47
|
+
* inherited environment we pass through is both safe and avoids depending on
|
|
48
|
+
* simple-git's `unsafe.allowUnsafe*` escape hatches (not part of this
|
|
49
|
+
* package version's public option types).
|
|
50
|
+
*/
|
|
51
|
+
const UNSAFE_GIT_ENV_KEYS = new Set([
|
|
52
|
+
'editor',
|
|
53
|
+
'git_editor',
|
|
54
|
+
'git_sequence_editor',
|
|
55
|
+
'git_askpass',
|
|
56
|
+
'ssh_askpass',
|
|
57
|
+
'git_config',
|
|
58
|
+
'git_config_global',
|
|
59
|
+
'git_config_system',
|
|
60
|
+
'git_config_count',
|
|
61
|
+
'git_exec_path',
|
|
62
|
+
'git_external_diff',
|
|
63
|
+
'git_pager',
|
|
64
|
+
'pager',
|
|
65
|
+
'git_proxy_command',
|
|
66
|
+
'git_template_dir',
|
|
67
|
+
'git_ssh',
|
|
68
|
+
'git_ssh_command',
|
|
69
|
+
'prefix',
|
|
70
|
+
]);
|
|
71
|
+
/** Copy `env`, dropping keys (case-insensitively) that simple-git's vulnerability scanner blocks by default. */
|
|
72
|
+
function sanitizeGitEnv(env) {
|
|
73
|
+
const sanitized = {};
|
|
74
|
+
for (const [key, value] of Object.entries(env)) {
|
|
75
|
+
if (UNSAFE_GIT_ENV_KEYS.has(key.toLowerCase()))
|
|
76
|
+
continue;
|
|
77
|
+
sanitized[key] = value;
|
|
78
|
+
}
|
|
79
|
+
return sanitized;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Thin runner around a `simple-git` instance permanently scoped (via `.env()`)
|
|
83
|
+
* to an isolated GIT_DIR/GIT_WORK_TREE pair. Every method here is a small,
|
|
84
|
+
* named wrapper around a raw git invocation — no hook emission, no shared
|
|
85
|
+
* state with the user's real repository.
|
|
86
|
+
*/
|
|
87
|
+
export class SideGitRunner {
|
|
88
|
+
workspaceRoot;
|
|
89
|
+
gitDir;
|
|
90
|
+
git;
|
|
91
|
+
constructor(opts) {
|
|
92
|
+
this.workspaceRoot = opts.workspaceRoot;
|
|
93
|
+
this.gitDir = opts.gitDir;
|
|
94
|
+
this.git = simpleGit({ baseDir: opts.workspaceRoot }).env({
|
|
95
|
+
...sanitizeGitEnv(process.env),
|
|
96
|
+
GIT_DIR: opts.gitDir,
|
|
97
|
+
GIT_WORK_TREE: opts.workspaceRoot,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Run an arbitrary raw git command against the side repo, returning stdout. */
|
|
101
|
+
async raw(args) {
|
|
102
|
+
return this.git.raw(args);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Idempotently initialize the side repo: `git init` the GIT_DIR if it does
|
|
106
|
+
* not already look initialized, set a local (side-repo-only) fallback
|
|
107
|
+
* identity so commits never depend on the user's global git config, and
|
|
108
|
+
* ensure `.goodvibes` is gitignored from the user's own repo's perspective.
|
|
109
|
+
*/
|
|
110
|
+
async init() {
|
|
111
|
+
if (!existsSync(this.gitDir)) {
|
|
112
|
+
mkdirSync(this.gitDir, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
const alreadyInit = existsSync(join(this.gitDir, 'HEAD'));
|
|
115
|
+
if (!alreadyInit) {
|
|
116
|
+
await this.git.raw(['init', '--quiet']);
|
|
117
|
+
logger.debug('SideGitRunner.init: initialized side repo', { gitDir: this.gitDir });
|
|
118
|
+
}
|
|
119
|
+
// Local (side-repo-scoped) identity — never touches the user's ~/.gitconfig
|
|
120
|
+
// or the workspace's real .git/config (there is no --global here, and
|
|
121
|
+
// GIT_DIR points at our own directory, so `git config` without --global
|
|
122
|
+
// writes to <gitDir>/config).
|
|
123
|
+
await this.git.raw(['config', 'user.name', FALLBACK_IDENTITY.name]);
|
|
124
|
+
await this.git.raw(['config', 'user.email', FALLBACK_IDENTITY.email]);
|
|
125
|
+
this.ensureGoodvibesIgnored();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Ensure `<workspaceRoot>/.goodvibes/.gitignore` contains a bare `*` line so
|
|
129
|
+
* the side repo's own storage (and every other tool's state under
|
|
130
|
+
* `.goodvibes`) is invisible to the user's OWN git repo (if any). Without
|
|
131
|
+
* this, `git status`/`git add -A` in the user's real repo would see our
|
|
132
|
+
* GIT_DIR as an untracked directory and could accidentally stage it.
|
|
133
|
+
*
|
|
134
|
+
* This intentionally only ever writes inside `.goodvibes/` — it never
|
|
135
|
+
* touches the workspace's own top-level `.gitignore`.
|
|
136
|
+
*/
|
|
137
|
+
ensureGoodvibesIgnored() {
|
|
138
|
+
const goodvibesDir = join(this.workspaceRoot, '.goodvibes');
|
|
139
|
+
const ignorePath = join(goodvibesDir, '.gitignore');
|
|
140
|
+
if (!existsSync(goodvibesDir)) {
|
|
141
|
+
mkdirSync(goodvibesDir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
if (!existsSync(ignorePath)) {
|
|
145
|
+
writeFileSync(ignorePath, '*\n', 'utf-8');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const existing = readFileSync(ignorePath, 'utf-8');
|
|
149
|
+
const hasIgnoreAll = existing.split('\n').some((line) => line.trim() === '*');
|
|
150
|
+
if (!hasIgnoreAll) {
|
|
151
|
+
writeFileSync(ignorePath, `${existing.replace(/\s*$/, '')}\n*\n`, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logger.warn('SideGitRunner.ensureGoodvibesIgnored: failed to write .goodvibes/.gitignore', {
|
|
156
|
+
error: summarizeError(err),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Stage changes into the side index.
|
|
162
|
+
*
|
|
163
|
+
* @param paths When provided (non-empty), stage only these pathspecs
|
|
164
|
+
* (scoped snapshot). Otherwise sweep the whole work tree, always excluding
|
|
165
|
+
* `.goodvibes` (our own storage) via pathspec magic — the same
|
|
166
|
+
* `:(exclude)` pattern AgentWorktree already uses for the same reason.
|
|
167
|
+
* Beyond that exclusion, git's normal `.gitignore` handling already applies
|
|
168
|
+
* here: `.gitignore` matching is a work-tree-relative feature of git and
|
|
169
|
+
* works identically regardless of where GIT_DIR points, so the workspace's
|
|
170
|
+
* own `.gitignore` (node_modules, build output, etc.) is honored with no
|
|
171
|
+
* extra configuration.
|
|
172
|
+
*/
|
|
173
|
+
async stageAll(paths) {
|
|
174
|
+
const pathspecs = paths && paths.length > 0
|
|
175
|
+
? paths
|
|
176
|
+
: ['.', ':(exclude).goodvibes', ':(exclude).goodvibes/**'];
|
|
177
|
+
await this.git.raw(['add', '-A', '--', ...pathspecs]);
|
|
178
|
+
}
|
|
179
|
+
/** Write the currently-staged index out as a tree object, without committing. Returns the tree hash. */
|
|
180
|
+
async writeTree() {
|
|
181
|
+
return (await this.git.raw(['write-tree'])).trim();
|
|
182
|
+
}
|
|
183
|
+
/** Resolve `<commit>^{tree}` for an existing commit hash. */
|
|
184
|
+
async treeOf(commit) {
|
|
185
|
+
return (await this.git.raw(['rev-parse', `${commit}^{tree}`])).trim();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a commit object from a tree, deliberately WITHOUT a git parent
|
|
189
|
+
* (no `-p`) and WITHOUT moving any branch/HEAD.
|
|
190
|
+
*
|
|
191
|
+
* Checkpoint lineage is tracked exclusively via the manifest's `parentId`
|
|
192
|
+
* field (manager.ts) — never via git ancestry. This isn't just a style
|
|
193
|
+
* choice: if checkpoint commits chained via `-p` the way a normal git
|
|
194
|
+
* history does, deleting an OLD checkpoint's ref in `gc()` would free
|
|
195
|
+
* nothing, because that commit stays reachable through every NEWER
|
|
196
|
+
* checkpoint's parent pointer — the ref is gone but the commit (and any
|
|
197
|
+
* tree/blob objects unique to it) is still walkable from every surviving
|
|
198
|
+
* descendant. Parentless commits mean a checkpoint's own ref is the ONLY
|
|
199
|
+
* thing keeping its commit reachable, so once that ref is deleted,
|
|
200
|
+
* `git gc --prune=now` can genuinely reclaim it.
|
|
201
|
+
*
|
|
202
|
+
* The returned hash is only reachable once a ref is pointed at it via
|
|
203
|
+
* `updateRef`.
|
|
204
|
+
*/
|
|
205
|
+
async commitTree(treeHash, message) {
|
|
206
|
+
return (await this.git.raw(['commit-tree', treeHash, '-m', message])).trim();
|
|
207
|
+
}
|
|
208
|
+
async updateRef(refName, commit) {
|
|
209
|
+
await this.git.raw(['update-ref', refName, commit]);
|
|
210
|
+
}
|
|
211
|
+
async deleteRef(refName) {
|
|
212
|
+
await this.git.raw(['update-ref', '-d', refName]);
|
|
213
|
+
}
|
|
214
|
+
/** List every ref under CHECKPOINT_REF_PREFIX as `{ id, commit }`. */
|
|
215
|
+
async listCheckpointRefs() {
|
|
216
|
+
let out;
|
|
217
|
+
try {
|
|
218
|
+
out = await this.git.raw(['for-each-ref', '--format=%(refname) %(objectname)', CHECKPOINT_REF_PREFIX]);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
return out
|
|
224
|
+
.split('\n')
|
|
225
|
+
.map((line) => line.trim())
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
.map((line) => {
|
|
228
|
+
const [refName, commit] = line.split(' ');
|
|
229
|
+
const id = (refName ?? '').slice(CHECKPOINT_REF_PREFIX.length);
|
|
230
|
+
return { id, commit: commit ?? '' };
|
|
231
|
+
})
|
|
232
|
+
.filter((entry) => entry.id.length > 0 && entry.commit.length > 0);
|
|
233
|
+
}
|
|
234
|
+
/** Files tracked in a commit's tree (recursive, name-only). */
|
|
235
|
+
async listTrackedFiles(commitOrTree) {
|
|
236
|
+
const out = await this.git.raw(['ls-tree', '-r', '--name-only', commitOrTree]);
|
|
237
|
+
return out.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
238
|
+
}
|
|
239
|
+
/** Reset the side index to exactly match a commit's tree (does not touch the working tree). */
|
|
240
|
+
async readTreeReset(commit) {
|
|
241
|
+
await this.git.raw(['read-tree', '--reset', commit]);
|
|
242
|
+
}
|
|
243
|
+
/** Write every file currently in the side index out to the working tree, overwriting existing files. */
|
|
244
|
+
async checkoutIndexAll() {
|
|
245
|
+
await this.git.raw(['checkout-index', '-a', '-f']);
|
|
246
|
+
}
|
|
247
|
+
/** `git diff` between two commit-ish values. Omit `to` to diff against the live working tree. */
|
|
248
|
+
async diff(from, to) {
|
|
249
|
+
return this.git.raw(to ? ['diff', from, to] : ['diff', from]);
|
|
250
|
+
}
|
|
251
|
+
/** `git diff --stat` between two commit-ish values. Omit `to` to diff against the live working tree. */
|
|
252
|
+
async diffStat(from, to) {
|
|
253
|
+
return this.git.raw(to ? ['diff', '--stat', from, to] : ['diff', '--stat', from]);
|
|
254
|
+
}
|
|
255
|
+
/** `git diff --name-only` between two commit-ish values. Omit `to` to diff against the live working tree. */
|
|
256
|
+
async diffNameOnly(from, to) {
|
|
257
|
+
const out = await this.git.raw(to ? ['diff', '--name-only', from, to] : ['diff', '--name-only', from]);
|
|
258
|
+
return out.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
259
|
+
}
|
|
260
|
+
/** `git gc --prune=now` on the side repo. */
|
|
261
|
+
async gc() {
|
|
262
|
+
await this.git.raw(['gc', '--prune=now', '--quiet']);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts
|
|
3
|
+
*
|
|
4
|
+
* Types for the workspace checkpoint engine (`WorkspaceCheckpointManager`).
|
|
5
|
+
*
|
|
6
|
+
* NAMING: this is deliberately "WorkspaceCheckpoint" / `wcp_` ids, NOT
|
|
7
|
+
* "checkpoint" bare. "checkpoint" is already used for two unrelated concepts
|
|
8
|
+
* in this codebase:
|
|
9
|
+
* - compaction boundary commits (`cpt_` ids, ../../runtime/compaction/strategies/boundary-commit.ts)
|
|
10
|
+
* — a CONVERSATION snapshot (messages/tokens), not a filesystem snapshot.
|
|
11
|
+
* - the generic retention `CheckpointRecord` (../../runtime/retention/types.ts)
|
|
12
|
+
* — a size/age/count bookkeeping record reused by both the boundary-commit
|
|
13
|
+
* GC path and this module's GC path, but tracked in independent
|
|
14
|
+
* `RetentionPolicy` instances so pruning one never touches the other.
|
|
15
|
+
*
|
|
16
|
+
* A WorkspaceCheckpoint is a whole-workspace filesystem snapshot stored as a
|
|
17
|
+
* commit object in a hidden side git repository (see side-git.ts) — never a
|
|
18
|
+
* conversation snapshot, never mixed with the user's real git history.
|
|
19
|
+
*/
|
|
20
|
+
import type { RetentionClass } from '../../runtime/retention/types.js';
|
|
21
|
+
export type { RetentionClass };
|
|
22
|
+
/**
|
|
23
|
+
* What triggered a checkpoint to be created.
|
|
24
|
+
*
|
|
25
|
+
* - `turn` — automatic snapshot taken at a conversation turn boundary
|
|
26
|
+
* (TURN_COMPLETED / TURN_ERROR / TURN_CANCEL).
|
|
27
|
+
* - `agent-run` — automatic snapshot taken after a spawned agent's changes
|
|
28
|
+
* have been merged into the main workspace (AGENT_COMPLETED).
|
|
29
|
+
* - `manual` — explicit user-requested checkpoint (e.g. TUI `/checkpoint`).
|
|
30
|
+
*/
|
|
31
|
+
export type CheckpointKind = 'turn' | 'agent-run' | 'manual';
|
|
32
|
+
/**
|
|
33
|
+
* Metadata record for a single workspace checkpoint.
|
|
34
|
+
*
|
|
35
|
+
* Persisted in the manifest (`index.json`, via JsonFileStore) alongside the
|
|
36
|
+
* side git repo that actually holds the commit/blob/tree objects. The
|
|
37
|
+
* manifest exists because git commit messages are a lossy encoding for
|
|
38
|
+
* structured metadata and because `list()` / retention need it without
|
|
39
|
+
* reading git object contents.
|
|
40
|
+
*/
|
|
41
|
+
export interface WorkspaceCheckpoint {
|
|
42
|
+
/** Unique id, format `wcp_<ts36>_<rand8>`. Also the ref name suffix under refs/goodvibes/checkpoints/<id>. */
|
|
43
|
+
readonly id: string;
|
|
44
|
+
/** What triggered this checkpoint. */
|
|
45
|
+
readonly kind: CheckpointKind;
|
|
46
|
+
/** Human-readable label. Auto-generated for turn/agent-run kinds, user-supplied for manual. */
|
|
47
|
+
readonly label: string;
|
|
48
|
+
/** Unix timestamp (ms) when the checkpoint was created. */
|
|
49
|
+
readonly createdAt: number;
|
|
50
|
+
/** id of the checkpoint this one was created on top of, or null for the first checkpoint ever taken. */
|
|
51
|
+
readonly parentId: string | null;
|
|
52
|
+
/** Turn id, when kind === 'turn' (or when passed explicitly). */
|
|
53
|
+
readonly turnId?: string | undefined;
|
|
54
|
+
/** Agent id, when kind === 'agent-run' (or when passed explicitly). */
|
|
55
|
+
readonly agentId?: string | undefined;
|
|
56
|
+
/** Retention class controlling how long this checkpoint survives gc(). */
|
|
57
|
+
readonly retentionClass: RetentionClass;
|
|
58
|
+
/** Git commit hash (in the side repo's object store) this checkpoint points to. */
|
|
59
|
+
readonly commit: string;
|
|
60
|
+
/**
|
|
61
|
+
* Approximate bytes of NEW content this checkpoint introduced relative to
|
|
62
|
+
* its parent (unchanged files are deduped by git and cost ~0; this is not
|
|
63
|
+
* exact object-store accounting, it is the sum of on-disk sizes of the
|
|
64
|
+
* files that differ from the parent, read at commit time).
|
|
65
|
+
*/
|
|
66
|
+
readonly sizeBytes: number;
|
|
67
|
+
}
|
|
68
|
+
/** Result of `WorkspaceCheckpointManager.diff()`. */
|
|
69
|
+
export interface CheckpointDiff {
|
|
70
|
+
/** The checkpoint id (or ref) diffed from. */
|
|
71
|
+
readonly from: string;
|
|
72
|
+
/** The checkpoint id diffed to, or the literal string 'WORKING' when diffed against the live working tree. */
|
|
73
|
+
readonly to: string;
|
|
74
|
+
/** Paths that differ between `from` and `to`. */
|
|
75
|
+
readonly files: string[];
|
|
76
|
+
/** Unified diff text (`git diff`). */
|
|
77
|
+
readonly unifiedDiff: string;
|
|
78
|
+
/** `git diff --stat` text. */
|
|
79
|
+
readonly stat: string;
|
|
80
|
+
}
|
|
81
|
+
/** Result of `WorkspaceCheckpointManager.restore()`. */
|
|
82
|
+
export interface RestoreResult {
|
|
83
|
+
/** The checkpoint id that was restored. */
|
|
84
|
+
readonly checkpointId: string;
|
|
85
|
+
/**
|
|
86
|
+
* id of the safety checkpoint taken immediately before restoring, or null
|
|
87
|
+
* when `safetyCheckpoint: false` was passed, or when the working tree was
|
|
88
|
+
* already identical to the checkpoint being restored (no-op dedupe).
|
|
89
|
+
*/
|
|
90
|
+
readonly safetyCheckpointId: string | null;
|
|
91
|
+
/** Paths written or overwritten to match the restored checkpoint. */
|
|
92
|
+
readonly restoredFiles: string[];
|
|
93
|
+
/** Paths that existed (and were tracked by this engine) after the checkpoint but not in it, and were removed. */
|
|
94
|
+
readonly removedFiles: string[];
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/platform/workspace/checkpoint/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE7D;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,8GAA8G;IAC9G,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,+FAA+F;IAC/F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,wGAAwG;IACxG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8GAA8G;IAC9G,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,qEAAqE;IACrE,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IACjC,iHAAiH;IACjH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;CACjC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts
|
|
3
|
+
*
|
|
4
|
+
* Types for the workspace checkpoint engine (`WorkspaceCheckpointManager`).
|
|
5
|
+
*
|
|
6
|
+
* NAMING: this is deliberately "WorkspaceCheckpoint" / `wcp_` ids, NOT
|
|
7
|
+
* "checkpoint" bare. "checkpoint" is already used for two unrelated concepts
|
|
8
|
+
* in this codebase:
|
|
9
|
+
* - compaction boundary commits (`cpt_` ids, ../../runtime/compaction/strategies/boundary-commit.ts)
|
|
10
|
+
* — a CONVERSATION snapshot (messages/tokens), not a filesystem snapshot.
|
|
11
|
+
* - the generic retention `CheckpointRecord` (../../runtime/retention/types.ts)
|
|
12
|
+
* — a size/age/count bookkeeping record reused by both the boundary-commit
|
|
13
|
+
* GC path and this module's GC path, but tracked in independent
|
|
14
|
+
* `RetentionPolicy` instances so pruning one never touches the other.
|
|
15
|
+
*
|
|
16
|
+
* A WorkspaceCheckpoint is a whole-workspace filesystem snapshot stored as a
|
|
17
|
+
* commit object in a hidden side git repository (see side-git.ts) — never a
|
|
18
|
+
* conversation snapshot, never mixed with the user's real git history.
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export * from './daemon-home.js';
|
|
2
2
|
export * from './workspace-swap-manager.js';
|
|
3
|
+
export { WorkspaceCheckpointManager, type CreateCheckpointOptions, type RestoreOptions, type ListCheckpointsFilter, type WorkspaceCheckpointManagerOptions, type WorkspaceCheckpoint, type CheckpointKind, type CheckpointDiff, type RestoreResult, } from './checkpoint/index.js';
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platform/workspace/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platform/workspace/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,iCAAiC,EACtC,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.1",
|
|
4
4
|
"description": "TypeScript SDK for building GoodVibes operator, peer, web, mobile, and daemon-connected apps with typed contracts, auth, realtime events, and transport layers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodvibes",
|
|
@@ -453,14 +453,14 @@
|
|
|
453
453
|
"sideEffects": false,
|
|
454
454
|
"type": "module",
|
|
455
455
|
"dependencies": {
|
|
456
|
-
"@pellux/goodvibes-contracts": "0.
|
|
457
|
-
"@pellux/goodvibes-daemon-sdk": "0.
|
|
458
|
-
"@pellux/goodvibes-errors": "0.
|
|
459
|
-
"@pellux/goodvibes-operator-sdk": "0.
|
|
460
|
-
"@pellux/goodvibes-peer-sdk": "0.
|
|
461
|
-
"@pellux/goodvibes-transport-core": "0.
|
|
462
|
-
"@pellux/goodvibes-transport-http": "0.
|
|
463
|
-
"@pellux/goodvibes-transport-realtime": "0.
|
|
456
|
+
"@pellux/goodvibes-contracts": "0.37.1",
|
|
457
|
+
"@pellux/goodvibes-daemon-sdk": "0.37.1",
|
|
458
|
+
"@pellux/goodvibes-errors": "0.37.1",
|
|
459
|
+
"@pellux/goodvibes-operator-sdk": "0.37.1",
|
|
460
|
+
"@pellux/goodvibes-peer-sdk": "0.37.1",
|
|
461
|
+
"@pellux/goodvibes-transport-core": "0.37.1",
|
|
462
|
+
"@pellux/goodvibes-transport-http": "0.37.1",
|
|
463
|
+
"@pellux/goodvibes-transport-realtime": "0.37.1"
|
|
464
464
|
},
|
|
465
465
|
"optionalDependencies": {
|
|
466
466
|
"@agentclientprotocol/sdk": "^0.21.0",
|