@commonlyai/cli 0.1.28 → 0.1.30
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/package.json +1 -1
- package/src/lib/adapters/claude.js +18 -5
- package/src/lib/adapters/codex.js +7 -5
- package/src/lib/environment.js +11 -1
- package/src/lib/memory-bridge.js +38 -13
package/package.json
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Contract: ADR-005 §Adapter pattern.
|
|
5
5
|
*
|
|
6
|
-
* Memory preamble:
|
|
7
|
-
*
|
|
6
|
+
* Memory preamble: the adapter prepends the kernel's long-term memory context
|
|
7
|
+
* on every turn. A fresh underlying session additionally receives the
|
|
8
|
+
* read-first and durable-state-at-boundary cues (§Memory bridge).
|
|
8
9
|
*
|
|
9
10
|
* Environment (ADR-008 Phase 1): if ctx.environment is present, the adapter
|
|
10
11
|
* symlinks declared Claude skills into `<cwd>/.claude/skills/`, writes an MCP
|
|
@@ -476,7 +477,7 @@ export default {
|
|
|
476
477
|
// the only two paths that build a real prompt. buildPrompt handles
|
|
477
478
|
// undefined and '' as absence itself; it does not need a guard, it needs
|
|
478
479
|
// the value.
|
|
479
|
-
const fullPrompt = buildPrompt(prompt, ctx.memoryLongTerm);
|
|
480
|
+
const fullPrompt = buildPrompt(prompt, ctx.memoryLongTerm, { freshSession: !isResume });
|
|
480
481
|
const sessionFlag = isResume ? '--resume' : '--session-id';
|
|
481
482
|
// Model pin from the ADR-008 environment spec. Absent it, claude picks its
|
|
482
483
|
// own default — which is how a fleet of ten agents ended up running three
|
|
@@ -488,7 +489,15 @@ export default {
|
|
|
488
489
|
// present in one but not the other means a retry silently runs a different
|
|
489
490
|
// model than the turn it is replacing — the same drifting-copy shape that
|
|
490
491
|
// has bitten this codebase repeatedly.
|
|
491
|
-
|
|
492
|
+
// `effort` rides in the same array for the same reason: Sam's 2026-09-01
|
|
493
|
+
// order ("flip all fable agents into fable 5.1 with high or above
|
|
494
|
+
// effort") needs both facts to reach every spawn, including the
|
|
495
|
+
// session-recovery retry, or a retry runs at a different effort than the
|
|
496
|
+
// turn it replaces.
|
|
497
|
+
const modelArgs = [
|
|
498
|
+
...(ctx.environment?.model ? ['--model', String(ctx.environment.model)] : []),
|
|
499
|
+
...(ctx.environment?.effort ? ['--effort', String(ctx.environment.effort)] : []),
|
|
500
|
+
];
|
|
492
501
|
const baseArgs = ['-p', fullPrompt, '--output-format', 'text', sessionFlag, sessionId, ...modelArgs];
|
|
493
502
|
|
|
494
503
|
if (ctx.environment && ctx.cwd) {
|
|
@@ -544,7 +553,11 @@ export default {
|
|
|
544
553
|
// session id poisons every subsequent event re-delivery.
|
|
545
554
|
if (isResume && /already in use|no conversation|no session/i.test(String(err.message))) {
|
|
546
555
|
const freshId = randomUUID();
|
|
547
|
-
|
|
556
|
+
// The retry creates a new underlying CLI session. Rebuild its prompt
|
|
557
|
+
// as fresh too: otherwise a session-recovery path is the one fresh
|
|
558
|
+
// session that misses the durable-state cue.
|
|
559
|
+
const freshPrompt = buildPrompt(prompt, ctx.memoryLongTerm, { freshSession: true });
|
|
560
|
+
const retryBase = ['-p', freshPrompt, '--output-format', 'text', '--session-id', freshId, ...modelArgs];
|
|
548
561
|
const retry = await prepareArgv(retryBase, {
|
|
549
562
|
...ctx,
|
|
550
563
|
mcpConfigPath: mcpConfig?.file || null,
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
* `--output-last-message` short alias) — cleaner than parsing every
|
|
21
21
|
* event-type variant the model can emit.
|
|
22
22
|
*
|
|
23
|
-
* Memory preamble:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* works identically across drivers.
|
|
23
|
+
* Memory preamble: the adapter prepends the kernel's long-term memory context
|
|
24
|
+
* on every turn. A fresh underlying session additionally receives the
|
|
25
|
+
* read-first and durable-state-at-boundary cues, matching the Claude adapter
|
|
26
|
+
* so the run loop's memory plumbing works identically across drivers.
|
|
27
27
|
*
|
|
28
28
|
* Purity (§Load-bearing invariants #1): input = argv + env + prompt;
|
|
29
29
|
* output = text + session id. No direct network, no direct CAP calls.
|
|
@@ -421,7 +421,9 @@ export default {
|
|
|
421
421
|
// the only two paths that build a real prompt. buildPrompt handles
|
|
422
422
|
// undefined and '' as absence itself; it does not need a guard, it needs
|
|
423
423
|
// the value.
|
|
424
|
-
const fullPrompt = buildPrompt(prompt, ctx.memoryLongTerm
|
|
424
|
+
const fullPrompt = buildPrompt(prompt, ctx.memoryLongTerm, {
|
|
425
|
+
freshSession: !ctx.sessionId,
|
|
426
|
+
});
|
|
425
427
|
|
|
426
428
|
// Per-spawn temp dir for --output-last-message. Cleaned up in `finally`
|
|
427
429
|
// so a crash in the middle of the spawn doesn't leak files in $TMPDIR.
|
package/src/lib/environment.js
CHANGED
|
@@ -41,7 +41,7 @@ import { homedir } from 'os';
|
|
|
41
41
|
// "persona and runtime are chosen separately" requires to mean anything for a
|
|
42
42
|
// BYO seat, and what lets an identity card answer "what is this running".
|
|
43
43
|
const ALLOWED_TOP_KEYS = new Set([
|
|
44
|
-
'version', 'workspace', 'sandbox', 'skills', 'mcp', 'model',
|
|
44
|
+
'version', 'workspace', 'sandbox', 'skills', 'mcp', 'model', 'effort',
|
|
45
45
|
]);
|
|
46
46
|
const ALLOWED_SANDBOX_MODES = new Set([
|
|
47
47
|
'none', 'workspace', 'read-only', 'bwrap', 'firejail', 'container', 'managed',
|
|
@@ -135,6 +135,16 @@ export const validateEnvironmentSpec = (spec) => {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// `effort` is the reasoning budget the claude adapter passes to `--effort`.
|
|
139
|
+
// Unlike `model` it IS a closed set — the CLI documents exactly these — so a
|
|
140
|
+
// typo fails here at attach time, not silently at the first spawn.
|
|
141
|
+
if (spec.effort !== undefined) {
|
|
142
|
+
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
143
|
+
if (typeof spec.effort !== 'string' || !EFFORTS.includes(spec.effort)) {
|
|
144
|
+
errors.push(`effort must be one of: ${EFFORTS.join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
if (spec.workspace !== undefined) {
|
|
139
149
|
if (typeof spec.workspace !== 'object' || spec.workspace === null) {
|
|
140
150
|
errors.push('workspace must be an object');
|
package/src/lib/memory-bridge.js
CHANGED
|
@@ -39,29 +39,54 @@ export const SOURCE_RUNTIME = 'local-cli';
|
|
|
39
39
|
* and is never seen again; the write and the silence are indistinguishable from
|
|
40
40
|
* a correct round trip. Naming the section is the whole point of the cue.
|
|
41
41
|
*/
|
|
42
|
-
export const buildMemoryPreamble = (prompt, memoryLongTerm) => {
|
|
42
|
+
export const buildMemoryPreamble = (prompt, memoryLongTerm, { freshSession = false } = {}) => {
|
|
43
43
|
// `null` is the UNREADABLE signal, and it is deliberately not the same value
|
|
44
44
|
// as `''`. Telling a seat whose token was revoked that "nothing has ever been
|
|
45
45
|
// saved here" is a false claim about its own history, and it is the same
|
|
46
46
|
// defect this cue exists to fix — one state over. When we could not read, say
|
|
47
47
|
// that, and say nothing about what is stored.
|
|
48
|
+
let context;
|
|
48
49
|
if (memoryLongTerm === null) {
|
|
49
|
-
|
|
50
|
+
context = `=== Context (your persistent memory) ===\n`
|
|
50
51
|
+ `(unreadable this turn — the memory read failed, so this says NOTHING `
|
|
51
52
|
+ `about what you have saved. Do not treat it as empty and do not re-save `
|
|
52
|
-
+ `state you may already hold.)
|
|
53
|
-
|
|
53
|
+
+ `state you may already hold.)`;
|
|
54
|
+
} else if (memoryLongTerm) {
|
|
55
|
+
context = `=== Context (your persistent memory) ===\n${memoryLongTerm}`;
|
|
56
|
+
} else {
|
|
57
|
+
context = `=== Context (your persistent memory) ===\n`
|
|
58
|
+
+ `(empty — nothing has ever been saved here)\n`
|
|
59
|
+
+ `Only the \`long_term\` section is read back into this prompt. To make `
|
|
60
|
+
+ `something survive your next session, call commonly_save_my_memory({ `
|
|
61
|
+
+ `section: 'long_term', content: '...' }). A write to any other section `
|
|
62
|
+
+ `succeeds and is never shown to you again.`;
|
|
54
63
|
}
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
|
|
65
|
+
if (!freshSession) return `${context}\n=== Current turn ===\n${prompt}`;
|
|
66
|
+
|
|
67
|
+
// This belongs on a fresh underlying CLI session, rather than every turn:
|
|
68
|
+
// a resumed session already carries the earlier instruction in its own
|
|
69
|
+
// transcript. Repeating it on each wake spends prompt budget while making
|
|
70
|
+
// the cue easier to ignore. The wrapper cannot know the final event of a
|
|
71
|
+
// session in advance, so it gives the end-of-session reminder while the
|
|
72
|
+
// agent can still act on it.
|
|
73
|
+
const freshReadCue = '=== Fresh session ===\n'
|
|
74
|
+
+ 'This is a fresh session. Read the persistent memory context above before acting; '
|
|
75
|
+
+ 'it carries durable state from prior sessions.\n';
|
|
76
|
+
let sessionEndCue = '=== Before this session ends ===\n';
|
|
77
|
+
if (memoryLongTerm === null) {
|
|
78
|
+
sessionEndCue += 'Memory was unreadable on this fresh session. Do not treat it as empty or '
|
|
79
|
+
+ 'write a replacement based on this cue.';
|
|
80
|
+
} else if (memoryLongTerm) {
|
|
81
|
+
sessionEndCue += 'At a natural end to meaningful work, save durable working state — '
|
|
82
|
+
+ 'gates held, decisions pending, and task context, not a transcript — with '
|
|
83
|
+
+ "commonly_save_my_memory({ section: 'long_term', content: '...' }).";
|
|
84
|
+
} else {
|
|
85
|
+
sessionEndCue += 'At a natural end to meaningful work, save durable working state — '
|
|
86
|
+
+ 'gates held, decisions pending, and task context, not a transcript — using '
|
|
87
|
+
+ 'the long_term write above.';
|
|
57
88
|
}
|
|
58
|
-
return
|
|
59
|
-
+ `(empty — nothing has ever been saved here)\n`
|
|
60
|
-
+ `Only the \`long_term\` section is read back into this prompt. To make `
|
|
61
|
-
+ `something survive your next session, call commonly_save_my_memory({ `
|
|
62
|
-
+ `section: 'long_term', content: '...' }). A write to any other section `
|
|
63
|
-
+ `succeeds and is never shown to you again.\n`
|
|
64
|
-
+ `=== Current turn ===\n${prompt}`;
|
|
89
|
+
return `${context}\n${freshReadCue}\n=== Current turn ===\n${prompt}\n${sessionEndCue}`;
|
|
65
90
|
};
|
|
66
91
|
|
|
67
92
|
export const readLongTerm = async (client, { onError } = {}) => {
|