@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.
Files changed (83) hide show
  1. package/MARKETPLACE.md +406 -0
  2. package/README.md +218 -21
  3. package/build-binary.js +591 -0
  4. package/build-npm.js +537 -0
  5. package/build.js +230 -0
  6. package/check-binary.js +26 -0
  7. package/deploy.sh +95 -0
  8. package/index.js +5775 -0
  9. package/lib/agent-registry.js +1037 -0
  10. package/lib/args-parser.js +837 -0
  11. package/lib/blessed-widget-patched.js +93 -0
  12. package/lib/cli-markdown.js +590 -0
  13. package/lib/compaction.js +153 -0
  14. package/lib/duration.js +94 -0
  15. package/lib/hash.js +22 -0
  16. package/lib/marketplace.js +866 -0
  17. package/lib/memory-config.js +166 -0
  18. package/lib/skill-manager.js +891 -0
  19. package/lib/soul.js +31 -0
  20. package/lib/tool-output-formatter.js +180 -0
  21. package/package.json +35 -33
  22. package/start-pave.sh +149 -0
  23. package/status.js +271 -0
  24. package/test/abort-stream.test.js +445 -0
  25. package/test/agent-auto-compaction.test.js +552 -0
  26. package/test/agent-comm-abort.test.js +95 -0
  27. package/test/agent-comm.test.js +598 -0
  28. package/test/agent-inbox.test.js +576 -0
  29. package/test/agent-init.test.js +264 -0
  30. package/test/agent-interrupt.test.js +314 -0
  31. package/test/agent-lifecycle.test.js +520 -0
  32. package/test/agent-log-files.test.js +349 -0
  33. package/test/agent-mode.manual-test.js +392 -0
  34. package/test/agent-parsing.test.js +228 -0
  35. package/test/agent-post-stream-idle.test.js +762 -0
  36. package/test/agent-registry.test.js +359 -0
  37. package/test/agent-rm.test.js +442 -0
  38. package/test/agent-spawn.test.js +933 -0
  39. package/test/agent-status-api.test.js +624 -0
  40. package/test/agent-update.test.js +435 -0
  41. package/test/args-parser.test.js +391 -0
  42. package/test/auto-compaction-chat.manual-test.js +227 -0
  43. package/test/auto-compaction.test.js +941 -0
  44. package/test/build-config.test.js +120 -0
  45. package/test/build-npm.test.js +388 -0
  46. package/test/chat-command.test.js +137 -0
  47. package/test/chat-leading-lines.test.js +159 -0
  48. package/test/config-flag.test.js +272 -0
  49. package/test/cursor-drift.test.js +135 -0
  50. package/test/debug-require.js +23 -0
  51. package/test/dir-migration.test.js +323 -0
  52. package/test/duration.test.js +229 -0
  53. package/test/ghostty-term.test.js +202 -0
  54. package/test/http500-backoff.test.js +854 -0
  55. package/test/integration.test.js +86 -0
  56. package/test/memory-guard-env.test.js +220 -0
  57. package/test/pr233-fixes.test.js +259 -0
  58. package/test/run-agent-init.js +297 -0
  59. package/test/run-all.js +64 -0
  60. package/test/run-config-flag.js +159 -0
  61. package/test/run-cursor-drift.js +82 -0
  62. package/test/run-session-path.js +154 -0
  63. package/test/run-tests.js +643 -0
  64. package/test/sandbox-redirect.test.js +202 -0
  65. package/test/session-path.test.js +132 -0
  66. package/test/shebang-strip.test.js +241 -0
  67. package/test/soul-reinject.test.js +1027 -0
  68. package/test/soul-reread.test.js +281 -0
  69. package/test/tool-output-formatter.test.js +486 -0
  70. package/test/tool-output-gating.test.js +143 -0
  71. package/test/tool-states.test.js +167 -0
  72. package/test/tools-flag.test.js +65 -0
  73. package/test/tui-attach.test.js +1255 -0
  74. package/test/tui-compaction.test.js +354 -0
  75. package/test/tui-wrap.test.js +568 -0
  76. package/test-binary.js +52 -0
  77. package/test-binary2.js +36 -0
  78. package/LICENSE +0 -21
  79. package/pave.js +0 -2
  80. package/sandbox/SandboxRunner.js +0 -1
  81. package/sandbox/pave-run.js +0 -2
  82. package/sandbox/permission.js +0 -1
  83. 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
+ };
@@ -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 };