praxis-agent 0.53.0 → 0.53.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/README.md CHANGED
@@ -227,7 +227,11 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
227
227
  existing default behavior. This is a Praxis permission contract, not a claim
228
228
  of verified Claude Code 2.1.208 parity. Explicit concrete `--tools`
229
229
  selections load selected tools directly, while
230
- `--disallowedTools ToolSearch` restores the complete tool list.
230
+ `--disallowedTools ToolSearch` restores the complete tool list. For each
231
+ context assembly, Git status is refreshed from the caller-resolved cwd while
232
+ environment and memory remain lifecycle-stable; collection uses
233
+ `--no-optional-locks`, fails closed on repository/status errors, and bounds
234
+ the rendered status to 2,048 UTF-8 bytes.
231
235
  - **Provider-neutral models** — native Provider Registry/Vault routing, API
232
236
  adapters, an experimental Codex OAuth adapter, explicit capability checks,
233
237
  separate per-attempt connect, byte-idle, and absolute-total timeouts, typed
@@ -131,7 +131,7 @@ ${sections.join('\n\n')}`,
131
131
  tailSections.push({
132
132
  id: 'relocated-runtime-context',
133
133
  placement: 'first-user',
134
- stability: 'session',
134
+ stability: 'volatile',
135
135
  content: renderClaudeDynamicUserContext({
136
136
  environment: dynamic.environment,
137
137
  ...(dynamic.gitStatus ? { gitStatus: dynamic.gitStatus } : {}),
@@ -143,8 +143,19 @@ ${sections.join('\n\n')}`,
143
143
  id: 'runtime-context',
144
144
  placement: 'system',
145
145
  stability: 'session',
146
- content: renderClaudeDynamicSystemContext(dynamic),
146
+ content: renderClaudeDynamicSystemContext({
147
+ environment: dynamic.environment,
148
+ ...(dynamic.memory ? { memory: dynamic.memory } : {}),
149
+ }),
147
150
  });
151
+ if (dynamic.gitStatus) {
152
+ tailSections.push({
153
+ id: 'git-status',
154
+ placement: 'system',
155
+ stability: 'volatile',
156
+ content: dynamic.gitStatus,
157
+ });
158
+ }
148
159
  }
149
160
  }
150
161
  const composition = this.composer.compose({
@@ -227,15 +238,22 @@ ${sections.join('\n\n')}`,
227
238
  throw new Error('Dynamic context loader is unavailable');
228
239
  if (!snapshot)
229
240
  return load(cwd);
230
- if (!snapshot.dynamic) {
231
- const pending = load(cwd);
232
- snapshot.dynamic = pending;
241
+ const current = load(cwd);
242
+ if (!snapshot.stableDynamic) {
243
+ const pending = current.then(({ environment, memory }) => ({
244
+ environment,
245
+ ...(memory ? { memory } : {}),
246
+ }));
247
+ snapshot.stableDynamic = pending;
233
248
  void pending.catch(() => {
234
- if (snapshot.dynamic === pending)
235
- delete snapshot.dynamic;
249
+ if (snapshot.stableDynamic === pending)
250
+ delete snapshot.stableDynamic;
236
251
  });
237
252
  }
238
- return snapshot.dynamic;
253
+ return Promise.all([current, snapshot.stableDynamic]).then(([fresh, stable]) => ({
254
+ ...stable,
255
+ ...(fresh.gitStatus ? { gitStatus: fresh.gitStatus } : {}),
256
+ }));
239
257
  }
240
258
  loadMcpInstructions(snapshot) {
241
259
  const load = this.options.loadMcpInstructions;
@@ -1,7 +1,8 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { basename } from 'node:path';
3
3
  import { platform, release, type } from 'node:os';
4
- const MAX_GIT_OUTPUT_BYTES = 128 * 1024;
4
+ const MAX_GIT_OUTPUT_BYTES = 2_048;
5
+ const MAX_GIT_STATUS_BYTES = 2_048;
5
6
  const MAX_GIT_ERROR_BYTES = 8 * 1024;
6
7
  const GIT_TIMEOUT_MS = 5_000;
7
8
  function collectBounded(chunks, chunk, state, limit) {
@@ -16,6 +17,22 @@ function collectBounded(chunks, chunk, state, limit) {
16
17
  if (accepted.length < chunk.length)
17
18
  state.truncated = true;
18
19
  }
20
+ function boundUtf8(value, limit, marker) {
21
+ if (Buffer.byteLength(value, 'utf8') <= limit)
22
+ return value;
23
+ const markerBytes = Buffer.byteLength(marker, 'utf8');
24
+ const prefixLimit = Math.max(0, limit - markerBytes);
25
+ let bytes = 0;
26
+ let prefix = '';
27
+ for (const character of value) {
28
+ const characterBytes = Buffer.byteLength(character, 'utf8');
29
+ if (bytes + characterBytes > prefixLimit)
30
+ break;
31
+ prefix += character;
32
+ bytes += characterBytes;
33
+ }
34
+ return `${prefix}${marker}`;
35
+ }
19
36
  function defaultRunGit(cwd, args) {
20
37
  return new Promise((resolve, reject) => {
21
38
  const child = spawn('git', ['-C', cwd, ...args], {
@@ -93,9 +110,16 @@ async function renderGitStatus(runGit) {
93
110
  optionalGit(runGit, ['branch', '--show-current']),
94
111
  optionalGit(runGit, ['branch', '--format=%(refname:short)']),
95
112
  optionalGit(runGit, ['config', 'user.name']),
96
- optionalGit(runGit, ['status', '--short', '--untracked-files=all']),
113
+ optionalGit(runGit, [
114
+ '--no-optional-locks',
115
+ 'status',
116
+ '--short',
117
+ '--untracked-files=all',
118
+ ]),
97
119
  optionalGit(runGit, ['log', '-5', '--oneline']),
98
120
  ]);
121
+ if (!status.available)
122
+ return undefined;
99
123
  const branch = branchValue.output || 'HEAD';
100
124
  const sections = [
101
125
  '# gitStatus',
@@ -108,18 +132,16 @@ async function renderGitStatus(runGit) {
108
132
  if (user.available && user.output) {
109
133
  sections.push('', `Git user: ${user.output}`);
110
134
  }
111
- const renderedStatus = !status.available
112
- ? 'Unavailable'
113
- : status.output
114
- ? `${status.output}${status.truncated ? '\n... [truncated]' : ''}`
115
- : status.truncated
116
- ? '... [truncated]'
117
- : 'Clean';
135
+ const renderedStatus = status.output
136
+ ? `${status.output}${status.truncated ? '\n... [truncated]' : ''}`
137
+ : status.truncated
138
+ ? '... [truncated]'
139
+ : 'Clean';
118
140
  sections.push('', 'Status:', renderedStatus);
119
141
  if (commits.available && commits.output) {
120
142
  sections.push('', 'Recent commits:', commits.output);
121
143
  }
122
- return sections.join('\n');
144
+ return boundUtf8(sections.join('\n'), MAX_GIT_STATUS_BYTES, '\n... [truncated]');
123
145
  }
124
146
  export async function loadClaudeDynamicContext(options) {
125
147
  const configuredRunGit = options.runGit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.53.0",
3
+ "version": "0.53.1",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",