@cnrai/pave 0.3.32 → 0.3.34
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/MARKETPLACE.md +406 -0
- package/README.md +218 -21
- package/build-binary.js +591 -0
- package/build-npm.js +537 -0
- package/build.js +230 -0
- package/check-binary.js +26 -0
- package/deploy.sh +95 -0
- package/index.js +5775 -0
- package/lib/agent-registry.js +1037 -0
- package/lib/args-parser.js +837 -0
- package/lib/blessed-widget-patched.js +93 -0
- package/lib/cli-markdown.js +590 -0
- package/lib/compaction.js +153 -0
- package/lib/duration.js +94 -0
- package/lib/hash.js +22 -0
- package/lib/marketplace.js +866 -0
- package/lib/memory-config.js +166 -0
- package/lib/skill-manager.js +891 -0
- package/lib/soul.js +31 -0
- package/lib/tool-output-formatter.js +180 -0
- package/package.json +35 -33
- package/start-pave.sh +149 -0
- package/status.js +271 -0
- package/test/abort-stream.test.js +445 -0
- package/test/agent-auto-compaction.test.js +552 -0
- package/test/agent-comm-abort.test.js +95 -0
- package/test/agent-comm.test.js +598 -0
- package/test/agent-inbox.test.js +576 -0
- package/test/agent-init.test.js +264 -0
- package/test/agent-interrupt.test.js +314 -0
- package/test/agent-lifecycle.test.js +520 -0
- package/test/agent-log-files.test.js +349 -0
- package/test/agent-mode.manual-test.js +392 -0
- package/test/agent-parsing.test.js +228 -0
- package/test/agent-post-stream-idle.test.js +762 -0
- package/test/agent-registry.test.js +359 -0
- package/test/agent-rm.test.js +442 -0
- package/test/agent-spawn.test.js +933 -0
- package/test/agent-status-api.test.js +624 -0
- package/test/agent-update.test.js +435 -0
- package/test/args-parser.test.js +391 -0
- package/test/auto-compaction-chat.manual-test.js +227 -0
- package/test/auto-compaction.test.js +941 -0
- package/test/build-config.test.js +120 -0
- package/test/build-npm.test.js +388 -0
- package/test/chat-command.test.js +137 -0
- package/test/chat-leading-lines.test.js +159 -0
- package/test/config-flag.test.js +272 -0
- package/test/cursor-drift.test.js +135 -0
- package/test/debug-require.js +23 -0
- package/test/dir-migration.test.js +323 -0
- package/test/duration.test.js +229 -0
- package/test/ghostty-term.test.js +202 -0
- package/test/http500-backoff.test.js +854 -0
- package/test/integration.test.js +86 -0
- package/test/memory-guard-env.test.js +220 -0
- package/test/pr233-fixes.test.js +259 -0
- package/test/run-agent-init.js +297 -0
- package/test/run-all.js +64 -0
- package/test/run-config-flag.js +159 -0
- package/test/run-cursor-drift.js +82 -0
- package/test/run-session-path.js +154 -0
- package/test/run-tests.js +643 -0
- package/test/sandbox-redirect.test.js +202 -0
- package/test/session-path.test.js +132 -0
- package/test/shebang-strip.test.js +241 -0
- package/test/soul-reinject.test.js +1027 -0
- package/test/soul-reread.test.js +281 -0
- package/test/tool-output-formatter.test.js +486 -0
- package/test/tool-output-gating.test.js +143 -0
- package/test/tool-states.test.js +167 -0
- package/test/tools-flag.test.js +65 -0
- package/test/tui-attach.test.js +1255 -0
- package/test/tui-compaction.test.js +354 -0
- package/test/tui-wrap.test.js +568 -0
- package/test-binary.js +52 -0
- package/test-binary2.js +36 -0
- package/LICENSE +0 -21
- package/pave.js +0 -2
- package/sandbox/SandboxRunner.js +0 -1
- package/sandbox/pave-run.js +0 -2
- package/sandbox/permission.js +0 -1
- package/sandbox/utils/yaml.js +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants and helpers for auto-compaction feature.
|
|
3
|
+
* This module has NO side effects when required - safe to import in tests.
|
|
4
|
+
*
|
|
5
|
+
* @module lib/compaction
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Allowlist of model IDs known to support compaction.
|
|
10
|
+
* This is a subset of models from opencode-lite/src/provider/models.js - only
|
|
11
|
+
* models verified to work well with compaction prompts are included.
|
|
12
|
+
* When an unknown model is specified, we fall back to a known model for compaction checks.
|
|
13
|
+
*/
|
|
14
|
+
const KNOWN_COMPACTION_MODELS = [
|
|
15
|
+
'claude-opus-4.5',
|
|
16
|
+
'claude-opus-4.6',
|
|
17
|
+
'claude-sonnet-4', // Canonical model advertised in CLI help
|
|
18
|
+
'claude-sonnet-4.5',
|
|
19
|
+
'claude-sonnet-4.6',
|
|
20
|
+
'claude-3-5-sonnet-20241022',
|
|
21
|
+
'claude-3-5-haiku-20241022',
|
|
22
|
+
'claude-3-opus-20240229',
|
|
23
|
+
'gpt-4',
|
|
24
|
+
'gpt-4-turbo',
|
|
25
|
+
'gpt-3.5-turbo',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Default provider ID when no provider prefix is given in the model string.
|
|
30
|
+
* Resolved at call time from env vars so tests/consumers can change env after require.
|
|
31
|
+
* Precedence: PAVE_COMPACT_MODEL > PAVE_MODEL > hardcoded default ('github-copilot')
|
|
32
|
+
*/
|
|
33
|
+
function getDefaultProviderIDForCompaction() {
|
|
34
|
+
const compactSpec = process.env.PAVE_COMPACT_MODEL;
|
|
35
|
+
if (compactSpec) {
|
|
36
|
+
// PAVE_COMPACT_MODEL is set - use its provider when both provider and model are non-empty.
|
|
37
|
+
if (compactSpec.includes('/')) {
|
|
38
|
+
const compactParts = compactSpec.split('/');
|
|
39
|
+
const compactProvider = (compactParts[0] || '').trim();
|
|
40
|
+
const compactModel = compactParts.slice(1).join('/').trim();
|
|
41
|
+
if (compactProvider && compactModel) return compactProvider;
|
|
42
|
+
// Empty provider but present model (e.g. "/model") -> treat like bare model ID
|
|
43
|
+
if (!compactProvider && compactModel) return 'github-copilot';
|
|
44
|
+
// Empty model (e.g. "provider/") -> fall through to PAVE_MODEL/default
|
|
45
|
+
} else {
|
|
46
|
+
// Bare model ID (no provider prefix) -> use hardcoded default provider without consulting PAVE_MODEL
|
|
47
|
+
return 'github-copilot';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const mainSpec = process.env.PAVE_MODEL;
|
|
51
|
+
if (mainSpec && mainSpec.includes('/')) {
|
|
52
|
+
const mainParts = mainSpec.split('/');
|
|
53
|
+
const mainProvider = (mainParts[0] || '').trim();
|
|
54
|
+
const mainModel = mainParts.slice(1).join('/').trim();
|
|
55
|
+
if (mainProvider && mainModel) return mainProvider;
|
|
56
|
+
// Empty provider but present model -> treat like bare model ID
|
|
57
|
+
if (!mainProvider && mainModel) return 'github-copilot';
|
|
58
|
+
// Malformed main spec -> fall through to hardcoded default
|
|
59
|
+
}
|
|
60
|
+
return 'github-copilot';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Default model ID for compaction operations.
|
|
65
|
+
* Resolved at call time from env vars so tests/consumers can change env after require.
|
|
66
|
+
* Precedence: PAVE_COMPACT_MODEL > PAVE_MODEL > hardcoded default ('claude-opus-4.5')
|
|
67
|
+
*/
|
|
68
|
+
function getDefaultModelIDForCompaction() {
|
|
69
|
+
const compactSpec = process.env.PAVE_COMPACT_MODEL;
|
|
70
|
+
if (compactSpec) {
|
|
71
|
+
let compactModel;
|
|
72
|
+
if (compactSpec.includes('/')) {
|
|
73
|
+
compactModel = compactSpec.split('/').slice(1).join('/').trim();
|
|
74
|
+
} else {
|
|
75
|
+
compactModel = compactSpec.trim();
|
|
76
|
+
}
|
|
77
|
+
if (compactModel) return compactModel;
|
|
78
|
+
// Malformed compact spec (e.g. "provider/") -> fall through to mainSpec/default
|
|
79
|
+
}
|
|
80
|
+
const mainSpec = process.env.PAVE_MODEL;
|
|
81
|
+
if (mainSpec) {
|
|
82
|
+
let mainModel;
|
|
83
|
+
if (mainSpec.includes('/')) {
|
|
84
|
+
mainModel = mainSpec.split('/').slice(1).join('/').trim();
|
|
85
|
+
} else {
|
|
86
|
+
mainModel = mainSpec.trim();
|
|
87
|
+
}
|
|
88
|
+
if (mainModel) return mainModel;
|
|
89
|
+
// Malformed main spec -> fall through to hardcoded default
|
|
90
|
+
}
|
|
91
|
+
return 'claude-opus-4.5';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse a model string into provider and model IDs for compaction operations.
|
|
96
|
+
* Model strings can be "provider/model" or just "model".
|
|
97
|
+
*
|
|
98
|
+
* @param {string} model - The model string to parse (e.g., "github-copilot/claude-opus-4.6" or "gpt-4")
|
|
99
|
+
* @returns {{ providerID: string, modelID: string, compactionModelID: string }}
|
|
100
|
+
*/
|
|
101
|
+
function parseModelForCompaction(model) {
|
|
102
|
+
// Resolve defaults at call time (not module-load time) so env var changes are respected
|
|
103
|
+
const defaultProviderID = getDefaultProviderIDForCompaction();
|
|
104
|
+
const defaultModelID = getDefaultModelIDForCompaction();
|
|
105
|
+
|
|
106
|
+
let providerID = defaultProviderID;
|
|
107
|
+
let modelID = defaultModelID;
|
|
108
|
+
|
|
109
|
+
if (model) {
|
|
110
|
+
if (model.includes('/')) {
|
|
111
|
+
const parts = model.split('/');
|
|
112
|
+
const parsedProvider = (parts[0] || '').trim();
|
|
113
|
+
const parsedModel = parts.slice(1).join('/').trim();
|
|
114
|
+
if (parsedProvider) providerID = parsedProvider;
|
|
115
|
+
// else: empty provider (e.g. "/model") -> keep default
|
|
116
|
+
if (parsedModel) modelID = parsedModel;
|
|
117
|
+
// else: empty model (e.g. "provider/") -> keep default
|
|
118
|
+
} else {
|
|
119
|
+
const trimmedModel = model.trim();
|
|
120
|
+
if (trimmedModel) modelID = trimmedModel;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Use a known model for compaction checks if the parsed model is not in the registry.
|
|
125
|
+
// Also validate that defaultModelID is known; fall back to hardcoded default if env var
|
|
126
|
+
// specifies an unlisted model (e.g. PAVE_COMPACT_MODEL=gpt-4o).
|
|
127
|
+
const HARDCODED_COMPACTION_DEFAULT = 'claude-opus-4.5';
|
|
128
|
+
const knownFallback = KNOWN_COMPACTION_MODELS.includes(defaultModelID)
|
|
129
|
+
? defaultModelID : HARDCODED_COMPACTION_DEFAULT;
|
|
130
|
+
const compactionModelID = KNOWN_COMPACTION_MODELS.includes(modelID) ? modelID : knownFallback;
|
|
131
|
+
|
|
132
|
+
return { providerID, modelID, compactionModelID };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
KNOWN_COMPACTION_MODELS,
|
|
137
|
+
// NOTE: DEFAULT_* are exported as getters so they reflect current env vars
|
|
138
|
+
// on each property access, e.g.:
|
|
139
|
+
// const compaction = require('./compaction');
|
|
140
|
+
// const modelIdNow = compaction.DEFAULT_MODEL_ID;
|
|
141
|
+
//
|
|
142
|
+
// Do NOT destructure these if you need dynamic behavior:
|
|
143
|
+
// const { DEFAULT_MODEL_ID } = require('./compaction');
|
|
144
|
+
// This will snapshot the value at require time and will not see later
|
|
145
|
+
// process.env changes. Prefer calling the functions directly instead:
|
|
146
|
+
// const { getDefaultModelIDForCompaction } = require('./compaction');
|
|
147
|
+
// const modelIdNow = getDefaultModelIDForCompaction();
|
|
148
|
+
get DEFAULT_PROVIDER_ID() { return getDefaultProviderIDForCompaction(); },
|
|
149
|
+
get DEFAULT_MODEL_ID() { return getDefaultModelIDForCompaction(); },
|
|
150
|
+
getDefaultProviderIDForCompaction,
|
|
151
|
+
getDefaultModelIDForCompaction,
|
|
152
|
+
parseModelForCompaction,
|
|
153
|
+
};
|
package/lib/duration.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration parsing and formatting utilities for PAVE agent mode
|
|
3
|
+
* Side-effect-free module that can be safely imported by tests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Maximum setTimeout delay (~24.8 days) - Node.js uses 32-bit signed integer for delays
|
|
7
|
+
const MAX_SETTIMEOUT_MS = 2147483647;
|
|
8
|
+
const MIN_SLEEP_MS = 1000; // Minimum 1 second to prevent tight loops
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse sleep duration string to milliseconds
|
|
12
|
+
* Supports: 5s (seconds), 5m (minutes), 1h (hours), 1d (days)
|
|
13
|
+
* @param {string|null|undefined} duration - Duration string (e.g., '30m', '1h', '5s'), or falsy value for default
|
|
14
|
+
* @returns {number} Duration in milliseconds (default: 5 minutes if not specified or empty)
|
|
15
|
+
*/
|
|
16
|
+
function parseSleepDuration(duration) {
|
|
17
|
+
if (!duration) return 5 * 60 * 1000; // Default: 5 minutes (handles null, undefined, empty string)
|
|
18
|
+
|
|
19
|
+
const match = duration.match(/^(\d+(?:\.\d+)?)\s*(s|sec|m|min|h|hr|hour|d|day)?$/i);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`Invalid sleep duration: ${duration}. Use format like '30m', '1h', '5s'`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const value = parseFloat(match[1]);
|
|
25
|
+
const unit = (match[2] || 'm').toLowerCase();
|
|
26
|
+
|
|
27
|
+
// Validate value is positive
|
|
28
|
+
if (value <= 0) {
|
|
29
|
+
throw new Error(`Sleep duration must be greater than 0. Got: ${duration}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let ms;
|
|
33
|
+
switch (unit) {
|
|
34
|
+
case 's':
|
|
35
|
+
case 'sec':
|
|
36
|
+
ms = value * 1000;
|
|
37
|
+
break;
|
|
38
|
+
case 'm':
|
|
39
|
+
case 'min':
|
|
40
|
+
ms = value * 60 * 1000;
|
|
41
|
+
break;
|
|
42
|
+
case 'h':
|
|
43
|
+
case 'hr':
|
|
44
|
+
case 'hour':
|
|
45
|
+
ms = value * 60 * 60 * 1000;
|
|
46
|
+
break;
|
|
47
|
+
case 'd':
|
|
48
|
+
case 'day':
|
|
49
|
+
ms = value * 24 * 60 * 60 * 1000;
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
ms = value * 60 * 1000; // Default to minutes
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate minimum sleep duration
|
|
56
|
+
if (ms < MIN_SLEEP_MS) {
|
|
57
|
+
throw new Error(`Sleep duration must be at least 1 second. Got: ${duration}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Validate maximum sleep duration (setTimeout limit)
|
|
61
|
+
if (ms > MAX_SETTIMEOUT_MS) {
|
|
62
|
+
throw new Error(`Sleep duration exceeds maximum of ~24.8 days. Got: ${duration}. Use a smaller value.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ms;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Format milliseconds to human-readable duration
|
|
70
|
+
* @param {number} ms - Duration in milliseconds
|
|
71
|
+
* @returns {string} Human-readable duration string
|
|
72
|
+
*/
|
|
73
|
+
function formatDuration(ms) {
|
|
74
|
+
if (ms < 1000) return `${ms}ms`;
|
|
75
|
+
if (ms < 60000) {
|
|
76
|
+
const seconds = ms / 1000;
|
|
77
|
+
const precision = Number.isInteger(seconds) ? 0 : 1;
|
|
78
|
+
return `${seconds.toFixed(precision)}s`;
|
|
79
|
+
}
|
|
80
|
+
if (ms < 3600000) {
|
|
81
|
+
const minutes = ms / 60000;
|
|
82
|
+
const precision = Number.isInteger(minutes) ? 0 : 1;
|
|
83
|
+
return `${minutes.toFixed(precision)}m`;
|
|
84
|
+
}
|
|
85
|
+
if (ms < 86400000) return `${(ms / 3600000).toFixed(1)}h`;
|
|
86
|
+
return `${(ms / 86400000).toFixed(1)}d`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
parseSleepDuration,
|
|
91
|
+
formatDuration,
|
|
92
|
+
MAX_SETTIMEOUT_MS,
|
|
93
|
+
MIN_SLEEP_MS,
|
|
94
|
+
};
|
package/lib/hash.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple string hash utility for SOUL content change detection.
|
|
3
|
+
*
|
|
4
|
+
* Uses a fast non-cryptographic hash (djb2) which is sufficient for
|
|
5
|
+
* detecting file content changes. Avoids depending on the 'crypto'
|
|
6
|
+
* module so that the same function can be used in sandboxed test
|
|
7
|
+
* environments where 'crypto' is not available.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} str - Input string to hash
|
|
10
|
+
* @returns {string} Hex string hash
|
|
11
|
+
*/
|
|
12
|
+
function simpleHash(str) {
|
|
13
|
+
let hash = 5381;
|
|
14
|
+
for (let i = 0; i < str.length; i++) {
|
|
15
|
+
// hash * 33 + char
|
|
16
|
+
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
|
|
17
|
+
}
|
|
18
|
+
// Convert to unsigned 32-bit hex string
|
|
19
|
+
return (hash >>> 0).toString(16);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { simpleHash };
|