@north-light/api 0.3.419 → 0.3.421
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/api/dto/docs.d.ts +7 -0
- package/dist/api/dto/nodes.d.ts +1 -0
- package/dist/core/command-plugins/bundle.d.ts +3 -0
- package/dist/core/command-plugins/bundle.js +16 -16
- package/dist/core/config.js +2 -1
- package/dist/core/fs-utils.d.ts +7 -0
- package/dist/core/fs-utils.js +16 -0
- package/dist/core/keybindings/catalog.d.ts +1 -1
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/manifest.d.ts +15 -0
- package/dist/core/manifest.js +24 -2
- package/dist/core/user-settings.d.ts +15 -0
- package/dist/core/user-settings.js +21 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/dist/api/dto/docs.d.ts
CHANGED
|
@@ -132,4 +132,11 @@ export interface ReconcilePackagesDTO {
|
|
|
132
132
|
path: string;
|
|
133
133
|
error: string;
|
|
134
134
|
}>;
|
|
135
|
+
/** plugin directories with a damaged manifest or command file, moved aside to `<scope>/quarantine/plugins/` */
|
|
136
|
+
quarantined?: Array<{
|
|
137
|
+
name: string;
|
|
138
|
+
file: string;
|
|
139
|
+
reason: string;
|
|
140
|
+
movedTo?: string;
|
|
141
|
+
}>;
|
|
135
142
|
}
|
package/dist/api/dto/nodes.d.ts
CHANGED
|
@@ -103,6 +103,9 @@ export declare function isInstalledGeneration(name: string, scope: Scope, root:
|
|
|
103
103
|
export declare function recordCheckedGeneration(scope: Scope, name: string, etag: string | undefined, options: {
|
|
104
104
|
swapped: boolean;
|
|
105
105
|
}): void;
|
|
106
|
+
/** Every file lands by temp file plus rename, fsynced, so a crash mid-write leaves the previous bytes or
|
|
107
|
+
* the whole new ones — never a 0-byte `plugin.json` or `commands.json` that crashes the next boot. */
|
|
108
|
+
export declare function writeStagedBundle(stagingRoot: string, manifest: PluginManifest, bundle: ValidatedBundle): void;
|
|
106
109
|
export interface InstallBundleArchiveOptions<M> {
|
|
107
110
|
scope: Scope;
|
|
108
111
|
/** The scope root whose `tmp/` holds staging, `.prev` and the swap lock. */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Parser } from 'tar';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { chmodSync, renameSync
|
|
3
|
+
import { chmodSync, renameSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
5
5
|
import { validateCommandManifest } from '../../api/command-manifest/manifest.js';
|
|
6
6
|
import { ensureScopeInitialized, invalidPluginKindsReasons, readConfig, updateConfig, updateState } from '../config.js';
|
|
7
7
|
import { general } from '../errors.js';
|
|
8
|
-
import { atomicWriteJson, ensureDir, nowIso, pathExists, removePath } from '../fs-utils.js';
|
|
8
|
+
import { atomicWriteJson, atomicWriteText, ensureDir, nowIso, pathExists, removePath, syncDirectory, syncTree } from '../fs-utils.js';
|
|
9
9
|
import { invalidPageComponentsReasons, normalizePageComponents } from '../human/page-catalog.js';
|
|
10
10
|
import { validateMemoryExtensionDeclarations } from '../plugin-extensions.js';
|
|
11
11
|
import { validateHttpPluginTransport } from './endpoint.js';
|
|
@@ -481,25 +481,24 @@ function stagedPath(stagingRoot, relative) {
|
|
|
481
481
|
}
|
|
482
482
|
return path;
|
|
483
483
|
}
|
|
484
|
-
|
|
485
|
-
|
|
484
|
+
/** Every file lands by temp file plus rename, fsynced, so a crash mid-write leaves the previous bytes or
|
|
485
|
+
* the whole new ones — never a 0-byte `plugin.json` or `commands.json` that crashes the next boot. */
|
|
486
|
+
export function writeStagedBundle(stagingRoot, manifest, bundle) {
|
|
487
|
+
const write = (path, content, mode = 0o644) => atomicWriteText(path, content, { mode, durable: true });
|
|
488
|
+
atomicWriteJson(stagedPath(stagingRoot, '.crouter-plugin/plugin.json'), manifest, { durable: true });
|
|
486
489
|
for (const directory of bundle.directories)
|
|
487
490
|
ensureDir(stagedPath(stagingRoot, directory.path));
|
|
488
|
-
|
|
489
|
-
for (const document of bundle.memory)
|
|
490
|
-
|
|
491
|
-
ensureDir(resolve(target, '..'));
|
|
492
|
-
writeFileSync(target, document.bytes);
|
|
493
|
-
}
|
|
491
|
+
write(stagedPath(stagingRoot, 'commands.json'), bundle.commandsBytes);
|
|
492
|
+
for (const document of bundle.memory)
|
|
493
|
+
write(stagedPath(stagingRoot, document.path), document.bytes);
|
|
494
494
|
if (bundle.hooks !== undefined) {
|
|
495
|
-
|
|
496
|
-
for (const file of bundle.hooks.files)
|
|
497
|
-
|
|
498
|
-
ensureDir(resolve(target, '..'));
|
|
499
|
-
writeFileSync(target, file.bytes);
|
|
500
|
-
}
|
|
495
|
+
write(stagedPath(stagingRoot, 'hooks.json'), bundle.hooks.manifest);
|
|
496
|
+
for (const file of bundle.hooks.files)
|
|
497
|
+
write(stagedPath(stagingRoot, file.path), file.bytes);
|
|
501
498
|
chmodSync(stagedPath(stagingRoot, 'hooks/dispatch'), 0o755);
|
|
502
499
|
}
|
|
500
|
+
// The staged tree reaches disk before the directory rename publishes it.
|
|
501
|
+
syncTree(stagingRoot);
|
|
503
502
|
}
|
|
504
503
|
/**
|
|
505
504
|
* Validate `raw`, stage it beside the package root, swap it in, then record
|
|
@@ -565,6 +564,7 @@ export async function installBundleArchive(name, raw, options) {
|
|
|
565
564
|
renameSync(previous, root);
|
|
566
565
|
throw error;
|
|
567
566
|
}
|
|
567
|
+
syncDirectory(dirname(root));
|
|
568
568
|
if (movedPrevious)
|
|
569
569
|
removePath(previous);
|
|
570
570
|
}
|
package/dist/core/config.js
CHANGED
|
@@ -833,6 +833,7 @@ function mergeConfig(partial) {
|
|
|
833
833
|
const fold_finished_tools = typeof partial.fold_finished_tools === 'boolean' ? partial.fold_finished_tools : defaults.fold_finished_tools;
|
|
834
834
|
const summarize_tool_calls = typeof partial.summarize_tool_calls === 'boolean' ? partial.summarize_tool_calls : defaults.summarize_tool_calls;
|
|
835
835
|
const detailed_tool_recaps = typeof partial.detailed_tool_recaps === 'boolean' ? partial.detailed_tool_recaps : defaults.detailed_tool_recaps;
|
|
836
|
+
const consolidate_replies = typeof partial.consolidate_replies === 'boolean' ? partial.consolidate_replies : defaults.consolidate_replies;
|
|
836
837
|
const keybindings = normalizeKeybindings(partial.keybindings);
|
|
837
838
|
const tmux_passthrough = normalizeTmuxPassthrough(partial.tmux_passthrough);
|
|
838
839
|
const modelLadders = mergeModelLadders(partial.modelLadders);
|
|
@@ -856,7 +857,7 @@ function mergeConfig(partial) {
|
|
|
856
857
|
const spawnEnv = mergeSpawnEnv(partial.spawnEnv, defaults.spawnEnv?.allow);
|
|
857
858
|
const bin = normalizeBin(partial.bin);
|
|
858
859
|
const humanActions = normalizeHumanActions(partial.humanActions);
|
|
859
|
-
return { bin, humanActions, schema_version, marketplaces, plugins, auto_update, max_panes_per_window, auto_open_child_viewers, brokerThresholds, lifecycle, completion_bell, working_gerunds, whip_mode, page_components, page_surface, whip_messages, whip_message_mode, mouse_mode_default, live_cycles, condensed_history, bash_tool_purpose, fold_finished_tools, summarize_tool_calls, detailed_tool_recaps, keybindings, tmux_passthrough, modelLadders, providerOptions, ...(Object.keys(modelRoutes).length > 0 ? { modelRoutes } : {}), ...(Object.keys(routes.legacy).length > 0 ? { legacyModelRoutes: routes.legacy } : {}), ...(modelRouting !== undefined ? { modelRouting } : {}), kinds, remoteCanvas, api, ...(paths ? { paths } : {}), ...(store ? { store } : {}), ...(runtime ? { runtime } : {}), spawnEnv };
|
|
860
|
+
return { bin, humanActions, schema_version, marketplaces, plugins, auto_update, max_panes_per_window, auto_open_child_viewers, brokerThresholds, lifecycle, completion_bell, working_gerunds, whip_mode, page_components, page_surface, whip_messages, whip_message_mode, mouse_mode_default, live_cycles, condensed_history, bash_tool_purpose, fold_finished_tools, summarize_tool_calls, detailed_tool_recaps, consolidate_replies, keybindings, tmux_passthrough, modelLadders, providerOptions, ...(Object.keys(modelRoutes).length > 0 ? { modelRoutes } : {}), ...(Object.keys(routes.legacy).length > 0 ? { legacyModelRoutes: routes.legacy } : {}), ...(modelRouting !== undefined ? { modelRouting } : {}), kinds, remoteCanvas, api, ...(paths ? { paths } : {}), ...(store ? { store } : {}), ...(runtime ? { runtime } : {}), spawnEnv };
|
|
860
861
|
}
|
|
861
862
|
/** Raw (un-defaulted) partial config for one scope, or null if the scope has
|
|
862
863
|
* no root or no config.json. Used by `readMergedLaunchConfig` to layer
|
package/dist/core/fs-utils.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface AtomicWriteOptions {
|
|
|
13
13
|
mode?: number;
|
|
14
14
|
/** Require the parent to exist, rather than recreate a removed project root. */
|
|
15
15
|
createParent?: boolean;
|
|
16
|
+
/** Fsync the temp file before the rename and the parent directory after it. Without this a power loss
|
|
17
|
+
* or a killed VM can persist the rename but not the data — the 0-byte file a plain
|
|
18
|
+
* temp-plus-rename still leaves behind. */
|
|
19
|
+
durable?: boolean;
|
|
16
20
|
}
|
|
17
21
|
export declare function atomicWriteJson(path: string, data: unknown, opts?: AtomicWriteOptions): void;
|
|
18
22
|
/** Force a file's written bytes to stable storage. */
|
|
@@ -23,6 +27,9 @@ export declare function syncDirectory(path: string): void;
|
|
|
23
27
|
* tmux inbox watcher, or crtrd serving a ticket read — sees either the old
|
|
24
28
|
* bytes or the new ones and never a truncated file mid-write. */
|
|
25
29
|
export declare function atomicWriteText(path: string, content: string | Uint8Array, opts?: AtomicWriteOptions): void;
|
|
30
|
+
/** Fsync every file and directory under `root` (and `root` itself), so a tree about to be renamed into place
|
|
31
|
+
* reaches stable storage before the rename can. */
|
|
32
|
+
export declare function syncTree(root: string): void;
|
|
26
33
|
export declare function readJson<T = unknown>(path: string): T;
|
|
27
34
|
export declare function readJsonIfExists<T = unknown>(path: string): T | null;
|
|
28
35
|
/** Missing or corrupt reads as absent, so a crash artifact never throws. */
|
package/dist/core/fs-utils.js
CHANGED
|
@@ -69,13 +69,29 @@ export function atomicWriteText(path, content, opts) {
|
|
|
69
69
|
try {
|
|
70
70
|
writeFileSync(temp, content, { mode });
|
|
71
71
|
chmodSync(temp, mode);
|
|
72
|
+
if (opts?.durable === true)
|
|
73
|
+
syncFile(temp);
|
|
72
74
|
renameSync(temp, path);
|
|
75
|
+
if (opts?.durable === true)
|
|
76
|
+
syncDirectory(dirname(path));
|
|
73
77
|
}
|
|
74
78
|
finally {
|
|
75
79
|
if (existsSync(temp))
|
|
76
80
|
rmSync(temp, { force: true });
|
|
77
81
|
}
|
|
78
82
|
}
|
|
83
|
+
/** Fsync every file and directory under `root` (and `root` itself), so a tree about to be renamed into place
|
|
84
|
+
* reaches stable storage before the rename can. */
|
|
85
|
+
export function syncTree(root) {
|
|
86
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
87
|
+
const child = join(root, entry.name);
|
|
88
|
+
if (entry.isDirectory())
|
|
89
|
+
syncTree(child);
|
|
90
|
+
else if (entry.isFile())
|
|
91
|
+
syncFile(child);
|
|
92
|
+
}
|
|
93
|
+
syncDirectory(root);
|
|
94
|
+
}
|
|
79
95
|
export function readJson(path) {
|
|
80
96
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
81
97
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BindingDefinition, SafetyRequirement } from './types.js';
|
|
2
2
|
import type { AttachBindingId, AttachMenuBindingId } from './attach-control.js';
|
|
3
|
-
type TmuxMenuAction = 'open' | 'attach' | 'show' | 'view-settings' | 'provider-options' | 'fold-tools' | 'metadata' | 'promote' | 'demote-mode' | 'resume' | 'fork' | 'demote' | 'make-resident' | 'detach' | 'close-subtree' | 'background-bash' | 'file-review' | 'pin' | 'reload-viewer' | 'recap' | 'context' | 'context-admin' | 'providers' | 'plugins' | 'analytics' | 'graph' | 'focus-manager' | 'copy-id' | 'issues';
|
|
3
|
+
type TmuxMenuAction = 'open' | 'attach' | 'show' | 'view-settings' | 'provider-options' | 'fold-tools' | 'show-working' | 'metadata' | 'promote' | 'demote-mode' | 'resume' | 'fork' | 'demote' | 'make-resident' | 'detach' | 'close-subtree' | 'background-bash' | 'file-review' | 'pin' | 'reload-viewer' | 'recap' | 'context' | 'context-admin' | 'providers' | 'plugins' | 'analytics' | 'graph' | 'focus-manager' | 'copy-id' | 'issues';
|
|
4
4
|
type TmuxMenuEntryAction = Exclude<TmuxMenuAction, 'open' | 'attach'> | `issue.${'idea' | 'bug'}`;
|
|
5
5
|
type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'revive' | 'focus-manager' | 'close-subtree' | 'dismiss';
|
|
6
6
|
type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4 | 5}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'profile' | 'residents' | 'preview' | 'report' | 'pin' | 'search' | 'revive-now' | 'focus' | 'focus-background' | 'yank-id';
|
|
@@ -52,6 +52,7 @@ export const BINDING_CATALOG = Object.freeze([
|
|
|
52
52
|
...menuEntry('show', 'Inspect node', 'Open the focused node’s inspection menu.', ['i'], ['tmux.menu']),
|
|
53
53
|
...menuEntry('view-settings', 'Display settings', 'Open the viewer display-settings menu.', ['z'], ['tmux.menu']),
|
|
54
54
|
...menuEntry('fold-tools', 'Fold finished tool calls', 'Fold every finished tool call in the transcript to its single call line.', ['f'], ['tmux.menu.view-settings']),
|
|
55
|
+
...menuEntry('show-working', 'Show working', 'With consolidated replies on, show or hide the messages and tool calls behind each reply.', ['w'], ['tmux.menu.view-settings']),
|
|
55
56
|
...menuEntry('metadata', 'Show metadata', 'Show the focused node’s metadata dossier.', ['m'], ['tmux.menu.show']),
|
|
56
57
|
...menuEntry('context', 'Browse context', 'Browse the focused node context.', ['c'], ['tmux.menu.show']),
|
|
57
58
|
...menuEntry('providers', 'Open settings', 'Open the crouter settings panel.', [','], ['tmux.menu']),
|
package/dist/core/manifest.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import type { MarketplaceManifest, PluginManifest } from '../types.js';
|
|
2
2
|
export declare function pluginManifestPath(pluginRoot: string): string;
|
|
3
3
|
export declare function marketplaceManifestPath(mktRoot: string): string;
|
|
4
|
+
/** What a manifest file holds: `absent` (no file, or JSON `null`), `ok`, or `broken` —
|
|
5
|
+
* present but unreadable, not JSON (a 0-byte file from a write cut short), or not an object. */
|
|
6
|
+
export type ManifestRead<T> = {
|
|
7
|
+
status: 'absent';
|
|
8
|
+
} | {
|
|
9
|
+
status: 'ok';
|
|
10
|
+
manifest: T;
|
|
11
|
+
} | {
|
|
12
|
+
status: 'broken';
|
|
13
|
+
error: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function readPluginManifestChecked(pluginRoot: string): ManifestRead<PluginManifest>;
|
|
16
|
+
/** The plugin's manifest, or null when it has none or it is unreadable: one damaged plugin
|
|
17
|
+
* directory must read as "not installed", never throw through whoever lists plugins. The daemon
|
|
18
|
+
* quarantines such a directory (`quarantineBrokenPlugins`); `readPluginManifestChecked` says why. */
|
|
4
19
|
export declare function readPluginManifest(pluginRoot: string): PluginManifest | null;
|
|
5
20
|
export declare function readMarketplaceManifest(mktRoot: string): MarketplaceManifest | null;
|
package/dist/core/manifest.js
CHANGED
|
@@ -7,9 +7,31 @@ export function pluginManifestPath(pluginRoot) {
|
|
|
7
7
|
export function marketplaceManifestPath(mktRoot) {
|
|
8
8
|
return join(mktRoot, MARKETPLACE_MANIFEST_DIR, MARKETPLACE_MANIFEST_FILE);
|
|
9
9
|
}
|
|
10
|
+
function readManifestChecked(path) {
|
|
11
|
+
let value;
|
|
12
|
+
try {
|
|
13
|
+
value = readJsonIfExists(path);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return { status: 'broken', error: error instanceof Error ? error.message : String(error) };
|
|
17
|
+
}
|
|
18
|
+
if (value === null)
|
|
19
|
+
return { status: 'absent' };
|
|
20
|
+
if (typeof value !== 'object' || Array.isArray(value))
|
|
21
|
+
return { status: 'broken', error: 'manifest is not a JSON object' };
|
|
22
|
+
return { status: 'ok', manifest: value };
|
|
23
|
+
}
|
|
24
|
+
export function readPluginManifestChecked(pluginRoot) {
|
|
25
|
+
return readManifestChecked(pluginManifestPath(pluginRoot));
|
|
26
|
+
}
|
|
27
|
+
/** The plugin's manifest, or null when it has none or it is unreadable: one damaged plugin
|
|
28
|
+
* directory must read as "not installed", never throw through whoever lists plugins. The daemon
|
|
29
|
+
* quarantines such a directory (`quarantineBrokenPlugins`); `readPluginManifestChecked` says why. */
|
|
10
30
|
export function readPluginManifest(pluginRoot) {
|
|
11
|
-
|
|
31
|
+
const read = readPluginManifestChecked(pluginRoot);
|
|
32
|
+
return read.status === 'ok' ? read.manifest : null;
|
|
12
33
|
}
|
|
13
34
|
export function readMarketplaceManifest(mktRoot) {
|
|
14
|
-
|
|
35
|
+
const read = readManifestChecked(marketplaceManifestPath(mktRoot));
|
|
36
|
+
return read.status === 'ok' ? read.manifest : null;
|
|
15
37
|
}
|
|
@@ -290,6 +290,21 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
|
|
|
290
290
|
setFromString: (config: ScopeConfig, raw: string) => string | null;
|
|
291
291
|
};
|
|
292
292
|
};
|
|
293
|
+
consolidate_replies: {
|
|
294
|
+
surface: "settings";
|
|
295
|
+
setting: {
|
|
296
|
+
key: "consolidate_replies";
|
|
297
|
+
label: string;
|
|
298
|
+
description: string;
|
|
299
|
+
valueConstraint: string;
|
|
300
|
+
applies: string;
|
|
301
|
+
group: "display";
|
|
302
|
+
order: number;
|
|
303
|
+
format: (config: ScopeConfig) => "on" | "off";
|
|
304
|
+
cycle: (config: ScopeConfig) => void;
|
|
305
|
+
setFromString: (config: ScopeConfig, raw: string) => string | null;
|
|
306
|
+
};
|
|
307
|
+
};
|
|
293
308
|
keybindings: {
|
|
294
309
|
surface: "specialized";
|
|
295
310
|
owner: string;
|
|
@@ -391,6 +391,27 @@ export const CONFIG_SETTINGS_COVERAGE = {
|
|
|
391
391
|
},
|
|
392
392
|
},
|
|
393
393
|
},
|
|
394
|
+
consolidate_replies: {
|
|
395
|
+
surface: 'settings',
|
|
396
|
+
setting: {
|
|
397
|
+
key: 'consolidate_replies',
|
|
398
|
+
label: 'Consolidate replies',
|
|
399
|
+
description: 'Experiment: show one rewritten agent reply per message you type, with the working folded behind it (/show-working).',
|
|
400
|
+
valueConstraint: 'true|false',
|
|
401
|
+
applies: 'Applies on the next attach; the broker reads it per turn.',
|
|
402
|
+
group: 'display',
|
|
403
|
+
order: 36.5,
|
|
404
|
+
format: (config) => config.consolidate_replies ? 'on' : 'off',
|
|
405
|
+
cycle: (config) => { config.consolidate_replies = !config.consolidate_replies; },
|
|
406
|
+
setFromString: (config, raw) => {
|
|
407
|
+
const value = booleanFromString(raw, 'consolidate_replies');
|
|
408
|
+
if (typeof value === 'string')
|
|
409
|
+
return value;
|
|
410
|
+
config.consolidate_replies = value;
|
|
411
|
+
return null;
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
},
|
|
394
415
|
keybindings: { surface: 'specialized', owner: 'Keybindings tab' },
|
|
395
416
|
tmux_passthrough: { surface: 'specialized', owner: 'Keybindings tab' },
|
|
396
417
|
providerOptions: {
|
package/dist/types.d.ts
CHANGED
|
@@ -341,6 +341,12 @@ export interface ScopeConfig {
|
|
|
341
341
|
/** Show individual document reads and edited files in a folded tool recap.
|
|
342
342
|
* Off shows the compact activity-count line instead. Default true. */
|
|
343
343
|
detailed_tool_recaps: boolean;
|
|
344
|
+
/** Experiment: show one consolidated agent reply per message the person typed.
|
|
345
|
+
* The broker rewrites a turn's prose messages into one reply with a light
|
|
346
|
+
* model; the attach viewer folds the rest of the turn behind a working row
|
|
347
|
+
* (`/show-working`, Alt+C → z → w). Session and model context are untouched.
|
|
348
|
+
* Default false. */
|
|
349
|
+
consolidate_replies: boolean;
|
|
344
350
|
keybindings: KeybindingOverrides;
|
|
345
351
|
/** Attach-viewer actions whose effective gesture may replace an occupied tmux
|
|
346
352
|
* root binding only while the active pane is an attach viewer. Stored by
|
package/dist/types.js
CHANGED
|
@@ -100,6 +100,7 @@ export function defaultScopeConfig() {
|
|
|
100
100
|
fold_finished_tools: true,
|
|
101
101
|
summarize_tool_calls: true,
|
|
102
102
|
detailed_tool_recaps: true,
|
|
103
|
+
consolidate_replies: false,
|
|
103
104
|
keybindings: {},
|
|
104
105
|
tmux_passthrough: [],
|
|
105
106
|
modelLadders: defaultModelLaddersConfig(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/api",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.421",
|
|
4
4
|
"description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/api/index.js",
|