clementine-agent 1.18.207 → 1.18.208
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/dist/agent/run-agent.js
CHANGED
|
@@ -441,7 +441,7 @@ export async function runAgent(prompt, opts) {
|
|
|
441
441
|
filePath: info.filePath,
|
|
442
442
|
contentBytes: info.contentBytes,
|
|
443
443
|
...(info.archivePath ? { archivePath: info.archivePath } : {}),
|
|
444
|
-
message: 'Large Write
|
|
444
|
+
message: 'Large Write content restored after placeholder Write to protect parent context.',
|
|
445
445
|
},
|
|
446
446
|
});
|
|
447
447
|
},
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
* The fix is the canonical Anthropic primitive: a `PostToolUse` hook that
|
|
23
23
|
* returns `hookSpecificOutput.updatedToolOutput` to replace the result
|
|
24
24
|
* before it reaches the model. A companion `PreToolUse` hook handles large
|
|
25
|
-
* `Write` inputs by
|
|
26
|
-
*
|
|
25
|
+
* `Write` inputs by swapping the native call to a small placeholder; the
|
|
26
|
+
* `PostToolUse` hook then restores the real artifact on disk after the
|
|
27
|
+
* placeholder write succeeds.
|
|
27
28
|
*
|
|
28
29
|
* Design properties
|
|
29
30
|
* ─────────────────
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
* The fix is the canonical Anthropic primitive: a `PostToolUse` hook that
|
|
23
23
|
* returns `hookSpecificOutput.updatedToolOutput` to replace the result
|
|
24
24
|
* before it reaches the model. A companion `PreToolUse` hook handles large
|
|
25
|
-
* `Write` inputs by
|
|
26
|
-
*
|
|
25
|
+
* `Write` inputs by swapping the native call to a small placeholder; the
|
|
26
|
+
* `PostToolUse` hook then restores the real artifact on disk after the
|
|
27
|
+
* placeholder write succeeds.
|
|
27
28
|
*
|
|
28
29
|
* Design properties
|
|
29
30
|
* ─────────────────
|
|
@@ -100,6 +101,7 @@ const VERBOSE_FIELDS = [
|
|
|
100
101
|
'message', 'transcript', 'raw', 'rawBody', 'rawMessage', 'contentText', 'plainText',
|
|
101
102
|
];
|
|
102
103
|
const LARGE_WRITE_INPUT_BYTES = 8_000;
|
|
104
|
+
const LARGE_WRITE_PLACEHOLDER = '[Clementine large-write guard placeholder: full content is restored after native Write succeeds.]';
|
|
103
105
|
function writeArchiveFile(baseDir, runId, toolUseId, toolName, suffix, payload) {
|
|
104
106
|
try {
|
|
105
107
|
const dir = join(baseDir, 'tool-archive', runId);
|
|
@@ -418,6 +420,7 @@ export function buildGuardHooks(opts) {
|
|
|
418
420
|
return { hooks: {}, stats };
|
|
419
421
|
}
|
|
420
422
|
const config = opts.config ?? defaultGuardConfig();
|
|
423
|
+
const pendingLargeWrites = new Map();
|
|
421
424
|
const preToolUse = async (input, toolUseID) => {
|
|
422
425
|
if (input.hook_event_name !== 'PreToolUse') {
|
|
423
426
|
return {};
|
|
@@ -431,20 +434,12 @@ export function buildGuardHooks(opts) {
|
|
|
431
434
|
if (!large)
|
|
432
435
|
return {};
|
|
433
436
|
const archivePath = writeArchiveFile(opts.archiveBaseDir ?? BASE_DIR, opts.runId, toolUseId, toolName, 'input', evt.tool_input);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
toolName,
|
|
441
|
-
toolUseId,
|
|
442
|
-
filePath: large.filePath,
|
|
443
|
-
contentBytes: large.contentBytes,
|
|
444
|
-
}, 'tool-output-guard: large Write out-of-band write failed; allowing native tool');
|
|
445
|
-
return {};
|
|
446
|
-
}
|
|
447
|
-
stats.largeWrites += 1;
|
|
437
|
+
pendingLargeWrites.set(toolUseId, {
|
|
438
|
+
filePath: large.filePath,
|
|
439
|
+
content: large.content,
|
|
440
|
+
contentBytes: large.contentBytes,
|
|
441
|
+
archivePath,
|
|
442
|
+
});
|
|
448
443
|
stats.bytesShed += Math.max(0, large.contentBytes - 400);
|
|
449
444
|
logger.info({
|
|
450
445
|
toolName,
|
|
@@ -452,33 +447,21 @@ export function buildGuardHooks(opts) {
|
|
|
452
447
|
filePath: large.filePath,
|
|
453
448
|
contentBytes: large.contentBytes,
|
|
454
449
|
archivePath,
|
|
455
|
-
}, 'tool-output-guard:
|
|
456
|
-
if (opts.onLargeWrite) {
|
|
457
|
-
try {
|
|
458
|
-
opts.onLargeWrite({
|
|
459
|
-
toolName,
|
|
460
|
-
toolUseId,
|
|
461
|
-
filePath: large.filePath,
|
|
462
|
-
contentBytes: large.contentBytes,
|
|
463
|
-
archivePath,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
catch { /* best-effort */ }
|
|
467
|
-
}
|
|
450
|
+
}, 'tool-output-guard: staged large Write with placeholder input');
|
|
468
451
|
const reason = [
|
|
469
|
-
`Clementine large-write guard
|
|
452
|
+
`Clementine large-write guard staged ${formatBytes(large.contentBytes)} for ${large.filePath}.`,
|
|
470
453
|
archivePath ? `Full original Write input archived at ${archivePath}.` : undefined,
|
|
471
|
-
'
|
|
454
|
+
'The native Write will use a small placeholder, then Clementine will restore the full content after the write succeeds. Continue with the remaining requested steps after this tool result.',
|
|
472
455
|
].filter(Boolean).join(' ');
|
|
473
456
|
return {
|
|
474
457
|
hookSpecificOutput: {
|
|
475
458
|
hookEventName: 'PreToolUse',
|
|
476
|
-
permissionDecision: '
|
|
459
|
+
permissionDecision: 'allow',
|
|
477
460
|
permissionDecisionReason: reason,
|
|
478
461
|
additionalContext: reason,
|
|
479
462
|
updatedInput: {
|
|
480
463
|
file_path: large.filePath,
|
|
481
|
-
content:
|
|
464
|
+
content: LARGE_WRITE_PLACEHOLDER,
|
|
482
465
|
},
|
|
483
466
|
},
|
|
484
467
|
};
|
|
@@ -494,6 +477,65 @@ export function buildGuardHooks(opts) {
|
|
|
494
477
|
const toolUseId = String(toolUseID ?? evt.tool_use_id ?? 'unknown');
|
|
495
478
|
const rawOutput = evt.tool_response;
|
|
496
479
|
stats.inspected += 1;
|
|
480
|
+
const pendingLargeWrite = toolName === 'Write' ? pendingLargeWrites.get(toolUseId) : undefined;
|
|
481
|
+
if (pendingLargeWrite) {
|
|
482
|
+
pendingLargeWrites.delete(toolUseId);
|
|
483
|
+
try {
|
|
484
|
+
writeLargeFileOutOfBand(pendingLargeWrite.filePath, pendingLargeWrite.content);
|
|
485
|
+
}
|
|
486
|
+
catch (err) {
|
|
487
|
+
logger.warn({
|
|
488
|
+
err,
|
|
489
|
+
toolName,
|
|
490
|
+
toolUseId,
|
|
491
|
+
filePath: pendingLargeWrite.filePath,
|
|
492
|
+
contentBytes: pendingLargeWrite.contentBytes,
|
|
493
|
+
archivePath: pendingLargeWrite.archivePath,
|
|
494
|
+
}, 'tool-output-guard: failed to restore large Write content after placeholder write');
|
|
495
|
+
return {
|
|
496
|
+
hookSpecificOutput: {
|
|
497
|
+
hookEventName: 'PostToolUse',
|
|
498
|
+
additionalContext: [
|
|
499
|
+
`Clementine could not restore the staged ${formatBytes(pendingLargeWrite.contentBytes)} Write content to ${pendingLargeWrite.filePath}.`,
|
|
500
|
+
pendingLargeWrite.archivePath ? `The original input is archived at ${pendingLargeWrite.archivePath}.` : undefined,
|
|
501
|
+
'Retry by writing the file in a smaller way or ask the owner for the archive path.',
|
|
502
|
+
].filter(Boolean).join(' '),
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
stats.largeWrites += 1;
|
|
507
|
+
logger.info({
|
|
508
|
+
toolName,
|
|
509
|
+
toolUseId,
|
|
510
|
+
filePath: pendingLargeWrite.filePath,
|
|
511
|
+
contentBytes: pendingLargeWrite.contentBytes,
|
|
512
|
+
archivePath: pendingLargeWrite.archivePath,
|
|
513
|
+
}, 'tool-output-guard: restored large Write content after placeholder write');
|
|
514
|
+
if (opts.onLargeWrite) {
|
|
515
|
+
try {
|
|
516
|
+
opts.onLargeWrite({
|
|
517
|
+
toolName,
|
|
518
|
+
toolUseId,
|
|
519
|
+
filePath: pendingLargeWrite.filePath,
|
|
520
|
+
contentBytes: pendingLargeWrite.contentBytes,
|
|
521
|
+
archivePath: pendingLargeWrite.archivePath,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
catch { /* best-effort */ }
|
|
525
|
+
}
|
|
526
|
+
const restoredMessage = [
|
|
527
|
+
`File created successfully at: ${pendingLargeWrite.filePath}`,
|
|
528
|
+
`(Clementine restored ${formatBytes(pendingLargeWrite.contentBytes)} of large generated content after native Write used a placeholder to protect context.)`,
|
|
529
|
+
pendingLargeWrite.archivePath ? `Original Write input archived at: ${pendingLargeWrite.archivePath}` : undefined,
|
|
530
|
+
].filter(Boolean).join(' ');
|
|
531
|
+
return {
|
|
532
|
+
hookSpecificOutput: {
|
|
533
|
+
hookEventName: 'PostToolUse',
|
|
534
|
+
additionalContext: `The full file content is present at ${pendingLargeWrite.filePath}. Do not rewrite it; continue with verification, deployment, or the next requested step.`,
|
|
535
|
+
updatedToolOutput: restoredMessage,
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
}
|
|
497
539
|
const usageRatio = Math.max(opts.usageRatio ? safeRatio(opts.usageRatio) : 0, stats.compactions > 0 ? 0.75 : 0);
|
|
498
540
|
const { softCap } = resolveCap(toolName, config, usageRatio);
|
|
499
541
|
const originalBytes = estimateBytes(rawOutput);
|