@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI \u2014 connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * Contract: ADR-005 §Adapter pattern.
5
5
  *
6
- * Memory preamble: if ctx.memoryLongTerm is non-empty, the adapter prepends
7
- * it to the prompt as a system-context preamble (§Memory bridge).
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
- const modelArgs = ctx.environment?.model ? ['--model', String(ctx.environment.model)] : [];
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
- const retryBase = ['-p', fullPrompt, '--output-format', 'text', '--session-id', freshId, ...modelArgs];
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: if ctx.memoryLongTerm is non-empty, the adapter prepends
24
- * it to the prompt as a system-context preamble (§Memory bridge), matching
25
- * the claude adapter's shape so the run loop's per-event memory plumbing
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.
@@ -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');
@@ -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
- return `=== Context (your persistent memory) ===\n`
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.)\n`
53
- + `=== Current turn ===\n${prompt}`;
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
- if (memoryLongTerm) {
56
- return `=== Context (your persistent memory) ===\n${memoryLongTerm}\n=== Current turn ===\n${prompt}`;
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 `=== Context (your persistent memory) ===\n`
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 } = {}) => {