@alisio/core 0.1.0-alpha.7 → 0.1.0-alpha.9
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/application.d.ts +13 -0
- package/dist/application.js +64 -1
- package/dist/config.d.ts +47 -0
- package/dist/config.js +95 -0
- package/dist/core/compaction.d.ts +28 -0
- package/dist/core/compaction.js +126 -31
- package/dist/core/runner.d.ts +18 -0
- package/dist/core/runner.js +30 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/plugins/host.d.ts +5 -0
- package/dist/plugins/host.js +7 -0
- package/dist/resources/context.d.ts +5 -0
- package/dist/resources/context.js +7 -0
- package/dist/startup/screen.js +6 -5
- package/package.json +1 -1
package/dist/application.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AvailableProviderModel, ModelInfo, ModelProvider, Plugin, PluginCategory, ProviderConfigurationValue, ResolvedProviderModel, RunEvent } from "@alisio/sdk";
|
|
2
|
+
import { type SettableSettingKey } from "./config.ts";
|
|
2
3
|
import type { ApprovalHandler } from "./core/contracts.ts";
|
|
3
4
|
import { ToolRegistry } from "./core/registry.ts";
|
|
4
5
|
import { AgentRunner } from "./core/runner.ts";
|
|
@@ -163,6 +164,18 @@ export declare function createApplication(options?: AppOptions): Promise<{
|
|
|
163
164
|
revokeGlobalMcpConsent(): Promise<void>;
|
|
164
165
|
/** Persist an MCP toggle in its defining layer, then safely update this process. */
|
|
165
166
|
setMcpEnabled(id: string, enabled: boolean, connect?: boolean): Promise<import("./index.ts").McpServerInfo>;
|
|
167
|
+
/**
|
|
168
|
+
* Persist one user-facing setting to the GLOBAL user config (`<config home>/config.json`)
|
|
169
|
+
* and apply it to this process where the value can change live. Compaction and limits land
|
|
170
|
+
* in the runner (honored by the next run/compact), `context.*` lands in the project context
|
|
171
|
+
* (next instructions load), `websearch.provider` mutates the shared config object the search
|
|
172
|
+
* chain reads per call, `pluginHooks.timeoutMs` lands in the plugin host (next hook call),
|
|
173
|
+
* and `tui.*` is persisted for the host to apply. Nothing in this set requires a restart.
|
|
174
|
+
* `mcp.allow` is deliberately not routed here: it flows through
|
|
175
|
+
* `rememberGlobalMcpConsent`/`revokeGlobalMcpConsent` instead.
|
|
176
|
+
* Throws under `--read-only`. Returns the written config file.
|
|
177
|
+
*/
|
|
178
|
+
updateSetting(key: SettableSettingKey, value: unknown): Promise<string>;
|
|
166
179
|
provider: ActiveProvider;
|
|
167
180
|
providers: ProviderRegistry;
|
|
168
181
|
providerSettings: ProviderSettingsStore;
|
package/dist/application.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { configFile, configHome, loadConfigWithProvenance, overridesSavedProviderProfile, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.js";
|
|
3
|
+
import { configFile, configHome, loadConfigWithProvenance, overridesSavedProviderProfile, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.js";
|
|
4
4
|
import { completeText } from "./core/compaction.js";
|
|
5
5
|
import { ToolRegistry } from "./core/registry.js";
|
|
6
6
|
import { AgentRunner } from "./core/runner.js";
|
|
@@ -729,6 +729,69 @@ export async function createApplication(options = {}) {
|
|
|
729
729
|
: undefined);
|
|
730
730
|
return mcp.info(id);
|
|
731
731
|
},
|
|
732
|
+
/**
|
|
733
|
+
* Persist one user-facing setting to the GLOBAL user config (`<config home>/config.json`)
|
|
734
|
+
* and apply it to this process where the value can change live. Compaction and limits land
|
|
735
|
+
* in the runner (honored by the next run/compact), `context.*` lands in the project context
|
|
736
|
+
* (next instructions load), `websearch.provider` mutates the shared config object the search
|
|
737
|
+
* chain reads per call, `pluginHooks.timeoutMs` lands in the plugin host (next hook call),
|
|
738
|
+
* and `tui.*` is persisted for the host to apply. Nothing in this set requires a restart.
|
|
739
|
+
* `mcp.allow` is deliberately not routed here: it flows through
|
|
740
|
+
* `rememberGlobalMcpConsent`/`revokeGlobalMcpConsent` instead.
|
|
741
|
+
* Throws under `--read-only`. Returns the written config file.
|
|
742
|
+
*/
|
|
743
|
+
async updateSetting(key, value) {
|
|
744
|
+
if (options.readOnly)
|
|
745
|
+
throw new Error("Settings changes are unavailable under --read-only");
|
|
746
|
+
const file = await setConfigValue({ key, value });
|
|
747
|
+
const compactionLeaf = (leaf) => ({ [leaf]: value });
|
|
748
|
+
const limitLeaf = (leaf) => ({ [leaf]: value });
|
|
749
|
+
switch (key) {
|
|
750
|
+
case "compaction.auto":
|
|
751
|
+
case "compaction.threshold":
|
|
752
|
+
case "compaction.keepTurns":
|
|
753
|
+
case "compaction.maxOutputTokens": {
|
|
754
|
+
const patch = compactionLeaf(key.slice("compaction.".length));
|
|
755
|
+
config.compaction = { ...config.compaction, ...patch };
|
|
756
|
+
runner.applySettings({ compaction: patch });
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
case "context.claudeMdFallback":
|
|
760
|
+
config.context = { ...config.context, claudeMdFallback: value === true };
|
|
761
|
+
context.update({ claudeMdFallback: value === true });
|
|
762
|
+
break;
|
|
763
|
+
case "context.maxBytes":
|
|
764
|
+
config.context = { ...config.context, maxBytes: Number(value) };
|
|
765
|
+
context.update({ maxBytes: Number(value) });
|
|
766
|
+
break;
|
|
767
|
+
case "limits.maxTurns":
|
|
768
|
+
case "limits.maxOutputTokens":
|
|
769
|
+
case "limits.maxContextChars":
|
|
770
|
+
case "limits.timeoutMs": {
|
|
771
|
+
const patch = limitLeaf(key.slice("limits.".length));
|
|
772
|
+
config.limits = { ...config.limits, ...patch };
|
|
773
|
+
runner.applySettings(patch);
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
case "pluginHooks.timeoutMs":
|
|
777
|
+
config.pluginHooks = { ...config.pluginHooks, timeoutMs: Number(value) };
|
|
778
|
+
plugins.applyTimeoutSettings({ hookTimeoutMs: Number(value) });
|
|
779
|
+
break;
|
|
780
|
+
case "tui.paddingX":
|
|
781
|
+
config.tui = { ...config.tui, paddingX: Number(value) };
|
|
782
|
+
break;
|
|
783
|
+
case "tui.skillSlashCommands":
|
|
784
|
+
config.tui = { ...config.tui, skillSlashCommands: value === true };
|
|
785
|
+
break;
|
|
786
|
+
case "websearch.provider":
|
|
787
|
+
// Mutated in place (same object identity) so the tool chain's per-call
|
|
788
|
+
// `websearchCtx.config` sees the new provider on the very next search call.
|
|
789
|
+
config.websearch.provider =
|
|
790
|
+
value === undefined ? undefined : value;
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
return file;
|
|
794
|
+
},
|
|
732
795
|
provider,
|
|
733
796
|
providers,
|
|
734
797
|
providerSettings,
|
package/dist/config.d.ts
CHANGED
|
@@ -172,6 +172,10 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
|
|
|
172
172
|
maxOutputTokens: z.ZodDefault<z.ZodNumber>;
|
|
173
173
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
174
174
|
}, z.core.$strip>>;
|
|
175
|
+
tui: z.ZodDefault<z.ZodObject<{
|
|
176
|
+
paddingX: z.ZodDefault<z.ZodNumber>;
|
|
177
|
+
skillSlashCommands: z.ZodDefault<z.ZodBoolean>;
|
|
178
|
+
}, z.core.$strict>>;
|
|
175
179
|
}, z.core.$strict>, z.ZodTransform<{
|
|
176
180
|
schemaVersion: 1;
|
|
177
181
|
provider: {
|
|
@@ -223,6 +227,10 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
|
|
|
223
227
|
maxOutputTokens: number;
|
|
224
228
|
maxTokens?: number | undefined;
|
|
225
229
|
};
|
|
230
|
+
tui: {
|
|
231
|
+
paddingX: number;
|
|
232
|
+
skillSlashCommands: boolean;
|
|
233
|
+
};
|
|
226
234
|
mcp: {
|
|
227
235
|
allow?: boolean | undefined;
|
|
228
236
|
servers: {
|
|
@@ -357,6 +365,10 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
|
|
|
357
365
|
maxOutputTokens: number;
|
|
358
366
|
maxTokens?: number | undefined;
|
|
359
367
|
};
|
|
368
|
+
tui: {
|
|
369
|
+
paddingX: number;
|
|
370
|
+
skillSlashCommands: boolean;
|
|
371
|
+
};
|
|
360
372
|
}>>;
|
|
361
373
|
export type Config = z.infer<typeof configSchema>;
|
|
362
374
|
export type ServerConfig = z.infer<typeof serverSchema>;
|
|
@@ -416,6 +428,41 @@ export declare function setMcpServerEnabled(input: {
|
|
|
416
428
|
export declare function setGlobalMcpAllow(input: {
|
|
417
429
|
allow: boolean;
|
|
418
430
|
}): Promise<string>;
|
|
431
|
+
declare const SETTABLE_KEYS: {
|
|
432
|
+
readonly "compaction.auto": z.ZodDefault<z.ZodBoolean>;
|
|
433
|
+
readonly "compaction.threshold": z.ZodDefault<z.ZodNumber>;
|
|
434
|
+
readonly "compaction.keepTurns": z.ZodDefault<z.ZodNumber>;
|
|
435
|
+
readonly "compaction.maxOutputTokens": z.ZodDefault<z.ZodNumber>;
|
|
436
|
+
readonly "context.claudeMdFallback": z.ZodDefault<z.ZodBoolean>;
|
|
437
|
+
readonly "context.maxBytes": z.ZodDefault<z.ZodNumber>;
|
|
438
|
+
readonly "limits.maxTurns": z.ZodDefault<z.ZodNumber>;
|
|
439
|
+
readonly "limits.maxOutputTokens": z.ZodDefault<z.ZodNumber>;
|
|
440
|
+
readonly "limits.maxContextChars": z.ZodDefault<z.ZodNumber>;
|
|
441
|
+
readonly "limits.timeoutMs": z.ZodDefault<z.ZodNumber>;
|
|
442
|
+
readonly "pluginHooks.timeoutMs": z.ZodDefault<z.ZodNumber>;
|
|
443
|
+
readonly "tui.paddingX": z.ZodDefault<z.ZodNumber>;
|
|
444
|
+
readonly "tui.skillSlashCommands": z.ZodDefault<z.ZodBoolean>;
|
|
445
|
+
readonly "websearch.provider": z.ZodOptional<z.ZodEnum<{
|
|
446
|
+
brave: "brave";
|
|
447
|
+
"duckduckgo-instant": "duckduckgo-instant";
|
|
448
|
+
native: "native";
|
|
449
|
+
searxng: "searxng";
|
|
450
|
+
serpapi: "serpapi";
|
|
451
|
+
tavily: "tavily";
|
|
452
|
+
}>>;
|
|
453
|
+
};
|
|
454
|
+
export type SettableSettingKey = keyof typeof SETTABLE_KEYS;
|
|
455
|
+
export declare function isSettableSettingKey(key: string): key is SettableSettingKey;
|
|
456
|
+
/**
|
|
457
|
+
* Atomically sets one user-facing setting in the global config file (`<config home>/config.json`)
|
|
458
|
+
* while preserving every unrelated JSON field. The value is validated against the same zod leaf
|
|
459
|
+
* used by `configSchema`, so a write can never produce a config the loader would reject. The
|
|
460
|
+
* caller is responsible for applying the change to the running process (see `application.ts`).
|
|
461
|
+
*/
|
|
462
|
+
export declare function setConfigValue(input: {
|
|
463
|
+
key: SettableSettingKey;
|
|
464
|
+
value: unknown;
|
|
465
|
+
}): Promise<string>;
|
|
419
466
|
/** Atomically updates one project plugin override while preserving every unrelated JSON field. */
|
|
420
467
|
export declare function setProjectPluginEnabled(input: {
|
|
421
468
|
workspace: string;
|
package/dist/config.js
CHANGED
|
@@ -164,6 +164,18 @@ const configObjectSchema = z
|
|
|
164
164
|
maxContextChars: 160000,
|
|
165
165
|
maxOutputTokens: 4096,
|
|
166
166
|
})),
|
|
167
|
+
tui: z
|
|
168
|
+
.object({
|
|
169
|
+
/** Horizontal padding (columns) around the editor input box. */
|
|
170
|
+
paddingX: z.number().int().min(0).max(4).default(1),
|
|
171
|
+
/**
|
|
172
|
+
* Offer effective skills as first-class `skill:<id>` editor slash-autocomplete entries.
|
|
173
|
+
* Off hides those entries; the `/skills` manager and its argument completion stay available.
|
|
174
|
+
*/
|
|
175
|
+
skillSlashCommands: z.boolean().default(true),
|
|
176
|
+
})
|
|
177
|
+
.strict()
|
|
178
|
+
.default(() => ({ paddingX: 1, skillSlashCommands: true })),
|
|
167
179
|
})
|
|
168
180
|
.strict()
|
|
169
181
|
.superRefine((config, context) => {
|
|
@@ -398,6 +410,89 @@ export async function setGlobalMcpAllow(input) {
|
|
|
398
410
|
}
|
|
399
411
|
return file;
|
|
400
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* The user-facing settings writable through the atomic `setConfigValue` writer, with their zod
|
|
415
|
+
* leaf validators derived from the config schema. Only these keys are accepted; every other
|
|
416
|
+
* config path stays out of reach of the settings menu (unknown keys are rejected, never created).
|
|
417
|
+
*/
|
|
418
|
+
const SETTABLE_SECTIONS = {
|
|
419
|
+
compaction: configObjectSchema.shape.compaction.removeDefault(),
|
|
420
|
+
context: configObjectSchema.shape.context.removeDefault(),
|
|
421
|
+
limits: configObjectSchema.shape.limits.removeDefault(),
|
|
422
|
+
pluginHooks: configObjectSchema.shape.pluginHooks.removeDefault(),
|
|
423
|
+
tui: configObjectSchema.shape.tui.removeDefault(),
|
|
424
|
+
websearch: configObjectSchema.shape.websearch.removeDefault(),
|
|
425
|
+
};
|
|
426
|
+
const SETTABLE_KEYS = {
|
|
427
|
+
"compaction.auto": SETTABLE_SECTIONS.compaction.shape.auto,
|
|
428
|
+
"compaction.threshold": SETTABLE_SECTIONS.compaction.shape.threshold,
|
|
429
|
+
"compaction.keepTurns": SETTABLE_SECTIONS.compaction.shape.keepTurns,
|
|
430
|
+
"compaction.maxOutputTokens": SETTABLE_SECTIONS.compaction.shape.maxOutputTokens,
|
|
431
|
+
"context.claudeMdFallback": SETTABLE_SECTIONS.context.shape.claudeMdFallback,
|
|
432
|
+
"context.maxBytes": SETTABLE_SECTIONS.context.shape.maxBytes,
|
|
433
|
+
"limits.maxTurns": SETTABLE_SECTIONS.limits.shape.maxTurns,
|
|
434
|
+
"limits.maxOutputTokens": SETTABLE_SECTIONS.limits.shape.maxOutputTokens,
|
|
435
|
+
"limits.maxContextChars": SETTABLE_SECTIONS.limits.shape.maxContextChars,
|
|
436
|
+
"limits.timeoutMs": SETTABLE_SECTIONS.limits.shape.timeoutMs,
|
|
437
|
+
"pluginHooks.timeoutMs": SETTABLE_SECTIONS.pluginHooks.shape.timeoutMs,
|
|
438
|
+
"tui.paddingX": SETTABLE_SECTIONS.tui.shape.paddingX,
|
|
439
|
+
"tui.skillSlashCommands": SETTABLE_SECTIONS.tui.shape.skillSlashCommands,
|
|
440
|
+
"websearch.provider": SETTABLE_SECTIONS.websearch.shape.provider,
|
|
441
|
+
};
|
|
442
|
+
export function isSettableSettingKey(key) {
|
|
443
|
+
return Object.prototype.hasOwnProperty.call(SETTABLE_KEYS, key);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Atomically sets one user-facing setting in the global config file (`<config home>/config.json`)
|
|
447
|
+
* while preserving every unrelated JSON field. The value is validated against the same zod leaf
|
|
448
|
+
* used by `configSchema`, so a write can never produce a config the loader would reject. The
|
|
449
|
+
* caller is responsible for applying the change to the running process (see `application.ts`).
|
|
450
|
+
*/
|
|
451
|
+
export async function setConfigValue(input) {
|
|
452
|
+
const validator = SETTABLE_KEYS[input.key];
|
|
453
|
+
if (!validator)
|
|
454
|
+
throw new Error(`Unknown setting key: ${input.key}`);
|
|
455
|
+
const parsed = validator.safeParse(input.value);
|
|
456
|
+
if (!parsed.success) {
|
|
457
|
+
const detail = parsed.error.issues[0];
|
|
458
|
+
throw new Error(detail
|
|
459
|
+
? `Invalid value for ${input.key}: ${detail.path.join(".") || "value"} ${detail.message}`
|
|
460
|
+
: `Invalid value for ${input.key}`);
|
|
461
|
+
}
|
|
462
|
+
const file = join(configHome(), "config.json");
|
|
463
|
+
let raw = {};
|
|
464
|
+
if (await exists(file)) {
|
|
465
|
+
const existing = JSON.parse(await readFile(file, "utf8"));
|
|
466
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing))
|
|
467
|
+
throw new Error("Global Alisio configuration must be a JSON object");
|
|
468
|
+
raw = existing;
|
|
469
|
+
}
|
|
470
|
+
const [section, leaf] = input.key.split(".");
|
|
471
|
+
const container = raw[section] && typeof raw[section] === "object" && !Array.isArray(raw[section])
|
|
472
|
+
? raw[section]
|
|
473
|
+
: {};
|
|
474
|
+
raw[section] = { ...container, [leaf]: parsed.data };
|
|
475
|
+
raw.schemaVersion ??= 1;
|
|
476
|
+
await mkdir(dirname(file), { recursive: true, mode: 0o700 });
|
|
477
|
+
const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
478
|
+
const handle = await open(temporary, "wx", 0o600);
|
|
479
|
+
try {
|
|
480
|
+
await handle.writeFile(`${JSON.stringify(raw, null, 2)}\n`, "utf8");
|
|
481
|
+
await handle.sync();
|
|
482
|
+
}
|
|
483
|
+
finally {
|
|
484
|
+
await handle.close();
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
await rename(temporary, file);
|
|
488
|
+
await chmod(file, 0o600);
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
await unlink(temporary).catch(() => { });
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
return file;
|
|
495
|
+
}
|
|
401
496
|
/** Atomically updates one project plugin override while preserving every unrelated JSON field. */
|
|
402
497
|
export async function setProjectPluginEnabled(input) {
|
|
403
498
|
const file = join(input.workspace, ".alisio", "config.json");
|
|
@@ -69,6 +69,34 @@ export interface MessageReduction {
|
|
|
69
69
|
* Checkpoint summaries (`summary: true`) are bounded by design and never truncated.
|
|
70
70
|
*/
|
|
71
71
|
export declare function reduceMessageSizes(messages: Message[], options?: MessageReductionOptions): MessageReduction;
|
|
72
|
+
export interface BudgetReductionOptions extends MessageReductionOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Descending per-round caps for tool result text, replacing the default
|
|
75
|
+
* 8192 → 4096 → 2048 → 1024 → 512 sequence.
|
|
76
|
+
*/
|
|
77
|
+
toolCapRounds?: number[];
|
|
78
|
+
/**
|
|
79
|
+
* Descending per-round caps for user/assistant text, replacing the default
|
|
80
|
+
* 16384 → 8192 → 4096 → 2048 → 1024 sequence.
|
|
81
|
+
*/
|
|
82
|
+
textCapRounds?: number[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Budget-based reduction: iteratively clips the LARGEST retained content (tool results in
|
|
86
|
+
* practice, then long texts) in descending cap rounds until the serialized transcript fits
|
|
87
|
+
* `targetChars` or the minimum caps are reached. Unlike `reduceMessageSizes` (which only clips
|
|
88
|
+
* messages whose INDIVIDUAL size exceeds its cap), this also handles sessions with many MEDIUM
|
|
89
|
+
* tool results (for example MCP outputs of a few thousand characters each) that stay under the
|
|
90
|
+
* per-message caps but together exhaust the context budget.
|
|
91
|
+
*
|
|
92
|
+
* The invariants match `reduceMessageSizes`: only message content changes (roles, call IDs,
|
|
93
|
+
* order and message boundaries stay identical, so a tool call is never separated from its
|
|
94
|
+
* results), checkpoint summaries are never truncated, and every cut carries the
|
|
95
|
+
* `… [truncated by context budget]` marker. Within a round the most expensive messages are
|
|
96
|
+
* clipped first (ties by position), so the smallest number of changes achieves the target; the
|
|
97
|
+
* order and the round caps make the result a deterministic total order of the input.
|
|
98
|
+
*/
|
|
99
|
+
export declare function reduceMessagesToBudget(messages: Message[], targetChars: number, options?: BudgetReductionOptions): MessageReduction;
|
|
72
100
|
/**
|
|
73
101
|
* Plain-text transcript used as summarizer input. Provider continuation data (for example
|
|
74
102
|
* encrypted reasoning) is opaque and is intentionally not included.
|
package/dist/core/compaction.js
CHANGED
|
@@ -97,6 +97,41 @@ export const TRUNCATION_MARKER = "… [truncated by context budget]";
|
|
|
97
97
|
export const MAX_TOOL_RESULT_CHARS = 8_000;
|
|
98
98
|
/** Default per-message cap for kept user/assistant text after a failed compaction. */
|
|
99
99
|
export const MAX_TEXT_CHARS = 16_000;
|
|
100
|
+
/**
|
|
101
|
+
* Cuts ONE message's content down to the given per-message caps. Returns the same reference when
|
|
102
|
+
* nothing changes, so callers can detect a no-op by identity. See `reduceMessageSizes` for the
|
|
103
|
+
* invariants (roles/callIds/boundaries untouched, summaries never truncated).
|
|
104
|
+
*/
|
|
105
|
+
function clampMessage(message, maxToolResultChars, maxTextChars) {
|
|
106
|
+
if (message.role === "user") {
|
|
107
|
+
if (message.summary || message.text.length <= maxTextChars)
|
|
108
|
+
return message;
|
|
109
|
+
return { ...message, text: `${message.text.slice(0, maxTextChars)}\n${TRUNCATION_MARKER}` };
|
|
110
|
+
}
|
|
111
|
+
if (message.role === "assistant") {
|
|
112
|
+
if (message.text.length <= maxTextChars)
|
|
113
|
+
return message;
|
|
114
|
+
return { ...message, text: `${message.text.slice(0, maxTextChars)}\n${TRUNCATION_MARKER}` };
|
|
115
|
+
}
|
|
116
|
+
let used = 0;
|
|
117
|
+
let changed = false;
|
|
118
|
+
const content = message.result.content.map((part) => {
|
|
119
|
+
const room = maxToolResultChars - used;
|
|
120
|
+
const text = room <= 0
|
|
121
|
+
? TRUNCATION_MARKER
|
|
122
|
+
: part.text.length > room
|
|
123
|
+
? `${part.text.slice(0, room)}\n${TRUNCATION_MARKER}`
|
|
124
|
+
: part.text;
|
|
125
|
+
if (text !== part.text) {
|
|
126
|
+
changed = true;
|
|
127
|
+
used = maxToolResultChars;
|
|
128
|
+
}
|
|
129
|
+
else
|
|
130
|
+
used += part.text.length;
|
|
131
|
+
return { type: "text", text };
|
|
132
|
+
});
|
|
133
|
+
return changed ? { ...message, result: { ...message.result, content } } : message;
|
|
134
|
+
}
|
|
100
135
|
/**
|
|
101
136
|
* Cuts oversized retained content so the session fits the context-budget hard limit after a
|
|
102
137
|
* compaction could not. Only message CONTENT changes (user/assistant `text` and the text parts
|
|
@@ -109,42 +144,102 @@ export function reduceMessageSizes(messages, options = {}) {
|
|
|
109
144
|
const maxTextChars = options.maxTextChars ?? MAX_TEXT_CHARS;
|
|
110
145
|
let truncated = 0;
|
|
111
146
|
const reduced = messages.map((m) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return m;
|
|
115
|
-
truncated++;
|
|
116
|
-
return { ...m, text: `${m.text.slice(0, maxTextChars)}\n${TRUNCATION_MARKER}` };
|
|
117
|
-
}
|
|
118
|
-
if (m.role === "assistant") {
|
|
119
|
-
if (m.text.length <= maxTextChars)
|
|
120
|
-
return m;
|
|
121
|
-
truncated++;
|
|
122
|
-
return { ...m, text: `${m.text.slice(0, maxTextChars)}\n${TRUNCATION_MARKER}` };
|
|
123
|
-
}
|
|
124
|
-
let used = 0;
|
|
125
|
-
let changed = false;
|
|
126
|
-
const content = m.result.content.map((part) => {
|
|
127
|
-
const room = maxToolResultChars - used;
|
|
128
|
-
const text = room <= 0
|
|
129
|
-
? TRUNCATION_MARKER
|
|
130
|
-
: part.text.length > room
|
|
131
|
-
? `${part.text.slice(0, room)}\n${TRUNCATION_MARKER}`
|
|
132
|
-
: part.text;
|
|
133
|
-
if (text !== part.text) {
|
|
134
|
-
changed = true;
|
|
135
|
-
used = maxToolResultChars;
|
|
136
|
-
}
|
|
137
|
-
else
|
|
138
|
-
used += part.text.length;
|
|
139
|
-
return { type: "text", text };
|
|
140
|
-
});
|
|
141
|
-
if (!changed)
|
|
147
|
+
const cut = clampMessage(m, maxToolResultChars, maxTextChars);
|
|
148
|
+
if (cut === m)
|
|
142
149
|
return m;
|
|
143
150
|
truncated++;
|
|
144
|
-
return
|
|
151
|
+
return cut;
|
|
145
152
|
});
|
|
146
153
|
return truncated ? { messages: reduced, truncated } : { messages, truncated: 0 };
|
|
147
154
|
}
|
|
155
|
+
/** Descending per-round tool-result caps used by `reduceMessagesToBudget` by default. */
|
|
156
|
+
const DEFAULT_TOOL_CAP_ROUNDS = [MAX_TOOL_RESULT_CHARS, 4_096, 2_048, 1_024, 512];
|
|
157
|
+
/** Descending per-round user/assistant text caps used by `reduceMessagesToBudget` by default. */
|
|
158
|
+
const DEFAULT_TEXT_CAP_ROUNDS = [MAX_TEXT_CHARS, 8_192, 4_096, 2_048, 1_024];
|
|
159
|
+
/** A strictly descending cap sequence starting at `start`, followed by the smaller defaults. */
|
|
160
|
+
function capRounds(start, defaults) {
|
|
161
|
+
return [start, ...defaults.filter((c) => c < start)];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Budget-based reduction: iteratively clips the LARGEST retained content (tool results in
|
|
165
|
+
* practice, then long texts) in descending cap rounds until the serialized transcript fits
|
|
166
|
+
* `targetChars` or the minimum caps are reached. Unlike `reduceMessageSizes` (which only clips
|
|
167
|
+
* messages whose INDIVIDUAL size exceeds its cap), this also handles sessions with many MEDIUM
|
|
168
|
+
* tool results (for example MCP outputs of a few thousand characters each) that stay under the
|
|
169
|
+
* per-message caps but together exhaust the context budget.
|
|
170
|
+
*
|
|
171
|
+
* The invariants match `reduceMessageSizes`: only message content changes (roles, call IDs,
|
|
172
|
+
* order and message boundaries stay identical, so a tool call is never separated from its
|
|
173
|
+
* results), checkpoint summaries are never truncated, and every cut carries the
|
|
174
|
+
* `… [truncated by context budget]` marker. Within a round the most expensive messages are
|
|
175
|
+
* clipped first (ties by position), so the smallest number of changes achieves the target; the
|
|
176
|
+
* order and the round caps make the result a deterministic total order of the input.
|
|
177
|
+
*/
|
|
178
|
+
export function reduceMessagesToBudget(messages, targetChars, options = {}) {
|
|
179
|
+
const target = Math.max(1, Math.floor(targetChars));
|
|
180
|
+
const toolCapRounds = options.toolCapRounds ??
|
|
181
|
+
capRounds(options.maxToolResultChars ?? MAX_TOOL_RESULT_CHARS, DEFAULT_TOOL_CAP_ROUNDS);
|
|
182
|
+
const textCapRounds = options.textCapRounds ??
|
|
183
|
+
capRounds(options.maxTextChars ?? MAX_TEXT_CHARS, DEFAULT_TEXT_CAP_ROUNDS);
|
|
184
|
+
const rounds = Math.max(toolCapRounds.length, textCapRounds.length);
|
|
185
|
+
// Working transcript; `messages` keeps the untouched originals for re-clipping each round.
|
|
186
|
+
const working = [...messages];
|
|
187
|
+
const sizeOf = (m) => JSON.stringify(m).length;
|
|
188
|
+
const sizes = working.map(sizeOf);
|
|
189
|
+
// Serialized array length == sum of message lengths + n-1 commas + 2 brackets (+n+1 total).
|
|
190
|
+
let size = sizes.reduce((a, b) => a + b, 0) + working.length + 1;
|
|
191
|
+
if (size <= target)
|
|
192
|
+
return { messages, truncated: 0 };
|
|
193
|
+
const clipped = new Set();
|
|
194
|
+
for (let round = 0; round < rounds && size > target; round++) {
|
|
195
|
+
const toolCap = toolCapRounds[Math.min(round, toolCapRounds.length - 1)];
|
|
196
|
+
const textCap = textCapRounds[Math.min(round, textCapRounds.length - 1)];
|
|
197
|
+
if (toolCap === undefined || textCap === undefined)
|
|
198
|
+
break;
|
|
199
|
+
let shrank = true;
|
|
200
|
+
while (size > target && shrank) {
|
|
201
|
+
shrank = false;
|
|
202
|
+
// Largest (by current serialized size) clip-able message first; ties by position.
|
|
203
|
+
let best = -1, bestSize = -1;
|
|
204
|
+
for (let i = 0; i < working.length; i++) {
|
|
205
|
+
const current = sizes[i];
|
|
206
|
+
if (current === undefined || current <= bestSize)
|
|
207
|
+
continue;
|
|
208
|
+
const m = messages[i];
|
|
209
|
+
if (!m)
|
|
210
|
+
continue;
|
|
211
|
+
const over = m.role === "user"
|
|
212
|
+
? !m.summary && m.text.length > textCap
|
|
213
|
+
: m.role === "assistant"
|
|
214
|
+
? m.text.length > textCap
|
|
215
|
+
: m.role === "tool" &&
|
|
216
|
+
m.result.content.reduce((a, p) => a + p.text.length, 0) > toolCap;
|
|
217
|
+
if (over) {
|
|
218
|
+
best = i;
|
|
219
|
+
bestSize = current;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (best < 0)
|
|
223
|
+
break;
|
|
224
|
+
const original = messages[best];
|
|
225
|
+
const oldSize = sizes[best];
|
|
226
|
+
if (!original || oldSize === undefined)
|
|
227
|
+
break;
|
|
228
|
+
// Re-clip from the ORIGINAL content so earlier markers are never embedded mid-result.
|
|
229
|
+
const cut = clampMessage(original, toolCap, textCap);
|
|
230
|
+
const newSize = sizeOf(cut);
|
|
231
|
+
const delta = oldSize - newSize;
|
|
232
|
+
sizes[best] = newSize;
|
|
233
|
+
if (delta <= 0)
|
|
234
|
+
continue; // already at this round's caps; no room to shrink further
|
|
235
|
+
working[best] = cut;
|
|
236
|
+
clipped.add(best);
|
|
237
|
+
size -= delta;
|
|
238
|
+
shrank = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return clipped.size ? { messages: working, truncated: clipped.size } : { messages, truncated: 0 };
|
|
242
|
+
}
|
|
148
243
|
/**
|
|
149
244
|
* Plain-text transcript used as summarizer input. Provider continuation data (for example
|
|
150
245
|
* encrypted reasoning) is opaque and is intentionally not included.
|
package/dist/core/runner.d.ts
CHANGED
|
@@ -43,6 +43,19 @@ export interface RunnerOptions {
|
|
|
43
43
|
*/
|
|
44
44
|
nativeTools?: Array<Record<string, unknown>>;
|
|
45
45
|
}
|
|
46
|
+
/** The settings a running TUI can change live; merged into the runner options on apply. */
|
|
47
|
+
export interface RunnerSettingsPatch {
|
|
48
|
+
/** Compaction settings; merged over the current ones (any subset). */
|
|
49
|
+
compaction?: Partial<CompactionSettings>;
|
|
50
|
+
/** Maximum turns per run. */
|
|
51
|
+
maxTurns?: number;
|
|
52
|
+
/** Per-call max output tokens budget. */
|
|
53
|
+
maxOutputTokens?: number;
|
|
54
|
+
/** Post-compaction context budget in characters. */
|
|
55
|
+
maxContextChars?: number;
|
|
56
|
+
/** Per-run timeout in milliseconds; the next run honors it. */
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
}
|
|
46
59
|
/**
|
|
47
60
|
* Per-run overrides used by embedders and child sessions. Callers must only NARROW: `policy`
|
|
48
61
|
* and `toolFilter` are applied on top of the runner's policy and tools.
|
|
@@ -92,6 +105,11 @@ export declare class AgentRunner {
|
|
|
92
105
|
get policy(): Readonly<Policy>;
|
|
93
106
|
/** Whether write/process tools can be approved interactively. */
|
|
94
107
|
get approvals(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Live-update runnable settings so the next `run`/`compact` call honors them without restarting
|
|
110
|
+
* Alisio. Every patch field is merged over the current options; unrelated settings are kept.
|
|
111
|
+
*/
|
|
112
|
+
applySettings(patch: RunnerSettingsPatch): void;
|
|
95
113
|
private emitter;
|
|
96
114
|
/** Tools offered to the model under the current policy (and optional run narrowing). */
|
|
97
115
|
availableTools(run?: RunOptions): ToolDefinition[];
|
package/dist/core/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { textResult, } from "@alisio/sdk";
|
|
2
|
-
import { checkpointInstructions, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction,
|
|
2
|
+
import { checkpointInstructions, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, reduceMessagesToBudget, shouldCompactContext, summarize, summaryMessage, } from "./compaction.js";
|
|
3
3
|
/**
|
|
4
4
|
* Default cumulative token budget for one run: several context windows (each turn re-sends the
|
|
5
5
|
* context), clamped; 1M tokens when the window is unknown.
|
|
@@ -11,6 +11,12 @@ export function defaultTokenBudget(contextWindow) {
|
|
|
11
11
|
}
|
|
12
12
|
/** Extension output appended to history is capped regardless of what hooks return. */
|
|
13
13
|
const MAX_INJECT_CHARS = 24_000;
|
|
14
|
+
/**
|
|
15
|
+
* Floor for the post-compaction transcript target: retained messages are never reduced below
|
|
16
|
+
* this many characters, so a pathological session (instructions + tools alone crowding out the
|
|
17
|
+
* limit) fails with an actionable error instead of silently losing the whole history.
|
|
18
|
+
*/
|
|
19
|
+
const MIN_MESSAGE_BUDGET_FLOOR = 4_000;
|
|
14
20
|
const allowed = (policy, effect) => effect === "read" || effect === "internal" || !!policy[effect];
|
|
15
21
|
export class AgentRunner {
|
|
16
22
|
options;
|
|
@@ -31,6 +37,18 @@ export class AgentRunner {
|
|
|
31
37
|
get approvals() {
|
|
32
38
|
return !!this.options.approve;
|
|
33
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Live-update runnable settings so the next `run`/`compact` call honors them without restarting
|
|
42
|
+
* Alisio. Every patch field is merged over the current options; unrelated settings are kept.
|
|
43
|
+
*/
|
|
44
|
+
applySettings(patch) {
|
|
45
|
+
const { compaction, ...rest } = patch;
|
|
46
|
+
this.options = {
|
|
47
|
+
...this.options,
|
|
48
|
+
...rest,
|
|
49
|
+
...(compaction ? { compaction: { ...this.options.compaction, ...compaction } } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
34
52
|
emitter(sessionId) {
|
|
35
53
|
const runId = crypto.randomUUID();
|
|
36
54
|
let seq = 0;
|
|
@@ -358,13 +376,18 @@ export class AgentRunner {
|
|
|
358
376
|
messages = o.store.messages(sessionId);
|
|
359
377
|
}
|
|
360
378
|
// A compaction that still leaves the session over the hard limit (typically a kept tail
|
|
361
|
-
// holding huge tool outputs) is reduced in place instead of failing outright:
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
379
|
+
// holding huge tool outputs) is reduced in place instead of failing outright: retained
|
|
380
|
+
// content is clipped against a TOTAL character target for the transcript
|
|
381
|
+
// (`limit − instructions − tools`), largest items first, tool results before texts, in
|
|
382
|
+
// descending cap rounds. That also covers sessions with many MEDIUM results (e.g. MCP
|
|
383
|
+
// outputs of a few thousand chars each) that individually stay under the per-message
|
|
384
|
+
// caps; roles/callIds/boundaries stay untouched, so the transcript remains valid and
|
|
385
|
+
// replayable. Only if even the reduction floor cannot fit (instructions + tools alone
|
|
386
|
+
// crowd out the limit) the run fails with an actionable error; the reduction is
|
|
387
|
+
// persisted either way, so the session stays usable for later prompts.
|
|
366
388
|
if (chars() > limit) {
|
|
367
|
-
const
|
|
389
|
+
const targetChars = Math.max(MIN_MESSAGE_BUDGET_FLOOR, limit - instructions.length - toolsText.length);
|
|
390
|
+
const reduction = reduceMessagesToBudget(messages, targetChars);
|
|
368
391
|
if (reduction.truncated > 0) {
|
|
369
392
|
o.store.overwrite(sessionId, reduction.messages);
|
|
370
393
|
messages = reduction.messages;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** @alisio/core public API: embed the Alisio agent programmatically. */
|
|
2
2
|
export * from "@alisio/sdk";
|
|
3
3
|
export { type AppOptions, type BuiltinContext, type BuiltinPlugin, createApplication, enabledBuiltins, type PluginCatalogEntry, type PluginCatalogStatus, } from "./application.ts";
|
|
4
|
-
export { type Config, type ConfigLoadResult, type ConfigProvenance, configFile, configHome, configSchema, type LoadedConfig, loadConfig, loadConfigWithProvenance, type McpServerSource, overridesSavedProviderProfile, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.ts";
|
|
4
|
+
export { type Config, type ConfigLoadResult, type ConfigProvenance, configFile, configHome, configSchema, isSettableSettingKey, type LoadedConfig, loadConfig, loadConfigWithProvenance, type McpServerSource, overridesSavedProviderProfile, type SettableSettingKey, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.ts";
|
|
5
5
|
export { type ContextBudget, checkpointInstructions, completeText, effectiveContextBudget, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, renderCheckpoint, serializeForSummary, shouldCompact, shouldCompactContext, summarize, } from "./core/compaction.ts";
|
|
6
6
|
export type { ApprovalDecision, ApprovalHandler, ApprovalRequest, ContextSource, HookFailure, Policy, RunnerExtensions, Session, SessionStore, } from "./core/contracts.ts";
|
|
7
7
|
export { ToolRegistry } from "./core/registry.ts";
|
|
8
|
-
export { AgentRunner, type CompactionResult, defaultTokenBudget, type RunnerOptions, type RunOptions, } from "./core/runner.ts";
|
|
8
|
+
export { AgentRunner, type CompactionResult, defaultTokenBudget, type RunnerOptions, type RunnerSettingsPatch, type RunOptions, } from "./core/runner.ts";
|
|
9
9
|
export { type ExtensionConflict, ExtensionRegistry } from "./extensions/registry.ts";
|
|
10
10
|
export { HerdrBridge } from "./integrations/herdr.ts";
|
|
11
11
|
export { McpConnector, type McpServerInfo, type McpStatus, type McpToolInfo, } from "./mcp/connector.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @alisio/core public API: embed the Alisio agent programmatically. */
|
|
2
2
|
export * from "@alisio/sdk";
|
|
3
3
|
export { createApplication, enabledBuiltins, } from "./application.js";
|
|
4
|
-
export { configFile, configHome, configSchema, loadConfig, loadConfigWithProvenance, overridesSavedProviderProfile, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.js";
|
|
4
|
+
export { configFile, configHome, configSchema, isSettableSettingKey, loadConfig, loadConfigWithProvenance, overridesSavedProviderProfile, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.js";
|
|
5
5
|
export { checkpointInstructions, completeText, effectiveContextBudget, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, renderCheckpoint, serializeForSummary, shouldCompact, shouldCompactContext, summarize, } from "./core/compaction.js";
|
|
6
6
|
export { ToolRegistry } from "./core/registry.js";
|
|
7
7
|
export { AgentRunner, defaultTokenBudget, } from "./core/runner.js";
|
package/dist/plugins/host.d.ts
CHANGED
|
@@ -91,6 +91,11 @@ export declare class PluginHost implements RunnerExtensions {
|
|
|
91
91
|
getState(plugin: string, key: string): unknown;
|
|
92
92
|
setState(plugin: string, key: string, value: unknown): void;
|
|
93
93
|
}, options?: PluginHostOptions, providers?: ProviderRegistry | undefined);
|
|
94
|
+
/**
|
|
95
|
+
* Live-update host-enforced hook timeouts so the NEXT hook call honors them without restarting.
|
|
96
|
+
* Keeps the same options object reference the constructors of new hooks read at call time.
|
|
97
|
+
*/
|
|
98
|
+
applyTimeoutSettings(patch: Partial<PluginHostOptions>): void;
|
|
94
99
|
/** Binds the provider-agnostic completion service once a provider exists. */
|
|
95
100
|
setCompleter(fn: (request: CompletionRequest & {
|
|
96
101
|
signal: AbortSignal;
|
package/dist/plugins/host.js
CHANGED
|
@@ -93,6 +93,13 @@ export class PluginHost {
|
|
|
93
93
|
this.options = options;
|
|
94
94
|
this.providers = providers;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Live-update host-enforced hook timeouts so the NEXT hook call honors them without restarting.
|
|
98
|
+
* Keeps the same options object reference the constructors of new hooks read at call time.
|
|
99
|
+
*/
|
|
100
|
+
applyTimeoutSettings(patch) {
|
|
101
|
+
this.options = { ...this.options, ...patch };
|
|
102
|
+
}
|
|
96
103
|
/** Binds the provider-agnostic completion service once a provider exists. */
|
|
97
104
|
setCompleter(fn) {
|
|
98
105
|
this.completer = fn;
|
|
@@ -22,6 +22,11 @@ export declare class ProjectContext implements ContextSource {
|
|
|
22
22
|
/** Per session: nested instruction file path → fingerprint already attached. */
|
|
23
23
|
private attached;
|
|
24
24
|
constructor(workspace: string, options?: ProjectContextOptions);
|
|
25
|
+
/**
|
|
26
|
+
* Live-update options so the next `instructions()` call honors them without restarting.
|
|
27
|
+
* Only runtime-read fields matter (`claudeMdFallback`, `maxBytes`); `cwd` stays fixed.
|
|
28
|
+
*/
|
|
29
|
+
update(options: Partial<ProjectContextOptions>): void;
|
|
25
30
|
private names;
|
|
26
31
|
private readDir;
|
|
27
32
|
/** Directories from the workspace root down to the directory containing `path`. */
|
|
@@ -28,6 +28,13 @@ export class ProjectContext {
|
|
|
28
28
|
this.options = options;
|
|
29
29
|
this.cwd = options.cwd ?? workspace;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Live-update options so the next `instructions()` call honors them without restarting.
|
|
33
|
+
* Only runtime-read fields matter (`claudeMdFallback`, `maxBytes`); `cwd` stays fixed.
|
|
34
|
+
*/
|
|
35
|
+
update(options) {
|
|
36
|
+
this.options = { ...this.options, ...options };
|
|
37
|
+
}
|
|
31
38
|
names() {
|
|
32
39
|
return [
|
|
33
40
|
["AGENTS.override.md", "override"],
|
package/dist/startup/screen.js
CHANGED
|
@@ -42,6 +42,7 @@ export function welcomeSection(ctx) {
|
|
|
42
42
|
export function infoSection(ctx, width) {
|
|
43
43
|
const t = ctx.terminal;
|
|
44
44
|
const label = sgr(t.color, "2");
|
|
45
|
+
const pad = " ".repeat(2);
|
|
45
46
|
const rows = [];
|
|
46
47
|
if (ctx.model)
|
|
47
48
|
rows.push(["model", ctx.model]);
|
|
@@ -54,9 +55,9 @@ export function infoSection(ctx, width) {
|
|
|
54
55
|
rows.push(["plugins", plugins ? `${plugins} loaded` : "none"]);
|
|
55
56
|
const labelWidth = Math.min(12, Math.max(...rows.map(([l]) => l.length)) + 1);
|
|
56
57
|
return rows.map(([l, v]) => {
|
|
57
|
-
const room = Math.max(1, width - labelWidth - 1);
|
|
58
|
+
const room = Math.max(1, width - labelWidth - 1 - 4);
|
|
58
59
|
const value = l === "cwd" ? shortenPath(v, room) : truncate(v, room);
|
|
59
|
-
return `${label(padEnd(l, labelWidth))} ${value}`;
|
|
60
|
+
return `${pad}${label(padEnd(l, labelWidth))} ${value}${pad}`;
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
export function pluginsSection(ctx) {
|
|
@@ -72,12 +73,12 @@ export function pluginsSection(ctx) {
|
|
|
72
73
|
.filter((p) => !p.categories?.includes("model-provider"))
|
|
73
74
|
.map((p) => `${p.id}${p.builtin ? " (builtin)" : ""}`),
|
|
74
75
|
];
|
|
75
|
-
return [
|
|
76
|
+
return [` ${sgr(t.color, "2")("with")} ${names.join(", ")} `];
|
|
76
77
|
}
|
|
77
78
|
export function tipsSection(ctx) {
|
|
78
79
|
const t = ctx.terminal;
|
|
79
80
|
const bullet = sgr(t.color, "33")(glyph(t, "›", ">"));
|
|
80
|
-
return ctx.tips.map((tip) =>
|
|
81
|
+
return ctx.tips.map((tip) => ` ${bullet} Tip: ${tip} `);
|
|
81
82
|
}
|
|
82
83
|
export function mascotSection(ctx) {
|
|
83
84
|
return [ctx.mascot.render({ terminal: ctx.terminal, version: ctx.version })].flat();
|
|
@@ -110,7 +111,7 @@ export const DefaultStartupScreen = {
|
|
|
110
111
|
...pluginsSection(ctx),
|
|
111
112
|
];
|
|
112
113
|
const body = side ? composeSideBySide(mascot, info, columns) : [...mascot, ...info];
|
|
113
|
-
const rule = sgr(ctx.terminal.color, "2")(glyph(ctx.terminal, "─", "-").repeat(
|
|
114
|
+
const rule = sgr(ctx.terminal.color, "2")(glyph(ctx.terminal, "─", "-").repeat(columns - 2));
|
|
114
115
|
return [...body, "", ...tipsSection(ctx), rule].map((line) => truncate(line, columns));
|
|
115
116
|
},
|
|
116
117
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.9",
|
|
4
4
|
"description": "Embeddable provider-agnostic Alisio agent core: tool loop, context compaction, local tools, MCP, plugin host and SQLite session store. Runs on Node >=22.16 and Bun.",
|
|
5
5
|
"author": "Gustavo Gutiérrez",
|
|
6
6
|
"license": "MIT",
|