@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.3
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/CHANGELOG.md +75 -6
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/extension-flags.d.ts +36 -0
- package/dist/types/config/config-file.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +23 -0
- package/dist/types/config/keybindings.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +88 -65
- package/dist/types/edit/hashline/diff.d.ts +3 -3
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
- package/dist/types/eval/agent-bridge.d.ts +25 -0
- package/dist/types/eval/backend.d.ts +17 -2
- package/dist/types/eval/budget-bridge.d.ts +29 -0
- package/dist/types/eval/idle-timeout.d.ts +28 -0
- package/dist/types/eval/js/executor.d.ts +8 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +13 -0
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
- package/dist/types/extensibility/plugins/manager.d.ts +12 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/memory-backend/index.d.ts +1 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +3 -3
- package/dist/types/mnemopi/backend.d.ts +4 -0
- package/dist/types/mnemopi/config.d.ts +29 -0
- package/dist/types/mnemopi/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -2
- package/dist/types/modes/components/omfg-panel.d.ts +19 -0
- package/dist/types/modes/controllers/command-controller.d.ts +7 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
- package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
- package/dist/types/modes/gradient-highlight.d.ts +5 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -3
- package/dist/types/modes/magic-keywords.d.ts +14 -0
- package/dist/types/modes/markdown-prose.d.ts +27 -0
- package/dist/types/modes/orchestrate.d.ts +7 -2
- package/dist/types/modes/shared.d.ts +1 -1
- package/dist/types/modes/turn-budget.d.ts +18 -0
- package/dist/types/modes/types.d.ts +7 -3
- package/dist/types/modes/ultrathink.d.ts +7 -2
- package/dist/types/modes/workflow.d.ts +15 -0
- package/dist/types/sdk.d.ts +13 -3
- package/dist/types/session/agent-session.d.ts +40 -17
- package/dist/types/session/session-manager.d.ts +18 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/session/shake-types.d.ts +24 -0
- package/dist/types/task/executor.d.ts +2 -2
- package/dist/types/tiny/models.d.ts +15 -1
- package/dist/types/tiny/title-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +19 -3
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/package.json +10 -10
- package/src/autoresearch/tools/run-experiment.ts +45 -113
- package/src/cli/args.ts +39 -16
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/plugin-cli.ts +11 -2
- package/src/config/config-file.ts +98 -13
- package/src/config/file-lock.ts +60 -17
- package/src/config/keybindings.ts +78 -27
- package/src/config/model-registry.ts +7 -1
- package/src/config/settings-schema.ts +94 -67
- package/src/config/settings.ts +12 -0
- package/src/edit/hashline/diff.ts +81 -24
- package/src/edit/renderer.ts +16 -12
- package/src/eval/__tests__/agent-bridge.test.ts +433 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/idle-timeout.test.ts +66 -0
- package/src/eval/__tests__/shared-executors.test.ts +21 -0
- package/src/eval/agent-bridge.ts +295 -0
- package/src/eval/backend.ts +17 -2
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/idle-timeout.ts +80 -0
- package/src/eval/js/executor.ts +35 -7
- package/src/eval/js/index.ts +2 -1
- package/src/eval/js/shared/prelude.txt +85 -1
- package/src/eval/js/tool-bridge.ts +9 -0
- package/src/eval/py/executor.ts +41 -14
- package/src/eval/py/index.ts +2 -1
- package/src/eval/py/prelude.py +132 -1
- package/src/exec/bash-executor.ts +2 -3
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/runner.ts +12 -2
- package/src/extensibility/plugins/git-url.ts +90 -4
- package/src/extensibility/plugins/manager.ts +103 -7
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +88 -88
- package/src/main.ts +44 -55
- package/src/memory-backend/index.ts +1 -1
- package/src/memory-backend/resolve.ts +3 -3
- package/src/memory-backend/types.ts +3 -3
- package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
- package/src/{mnemosyne → mnemopi}/config.ts +36 -36
- package/src/{mnemosyne → mnemopi}/state.ts +67 -67
- package/src/modes/components/agent-dashboard.ts +6 -6
- package/src/modes/components/custom-editor.ts +4 -11
- package/src/modes/components/extensions/state-manager.ts +3 -4
- package/src/modes/components/footer.ts +8 -9
- package/src/modes/components/hook-selector.ts +86 -20
- package/src/modes/components/oauth-selector.ts +93 -21
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/settings-defs.ts +2 -2
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +38 -19
- package/src/modes/components/tree-selector.ts +4 -3
- package/src/modes/components/user-message-selector.ts +94 -19
- package/src/modes/components/user-message.ts +8 -1
- package/src/modes/controllers/command-controller.ts +57 -0
- package/src/modes/controllers/event-controller.ts +60 -2
- package/src/modes/controllers/input-controller.ts +14 -11
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/gradient-highlight.ts +23 -6
- package/src/modes/interactive-mode.ts +41 -7
- package/src/modes/magic-keywords.ts +20 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/orchestrate.ts +17 -11
- package/src/modes/shared.ts +3 -11
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/ultrathink.ts +16 -10
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/workflow.ts +42 -0
- package/src/prompts/system/omfg-user.md +51 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +70 -0
- package/src/prompts/tools/eval.md +13 -1
- package/src/prompts/tools/memory-edit.md +1 -1
- package/src/sdk.ts +63 -33
- package/src/session/agent-session.ts +373 -56
- package/src/session/session-manager.ts +32 -0
- package/src/session/session-storage.ts +68 -8
- package/src/session/shake-types.ts +44 -0
- package/src/slash-commands/builtin-registry.ts +41 -16
- package/src/task/executor.ts +3 -3
- package/src/task/index.ts +6 -6
- package/src/tiny/models.ts +30 -2
- package/src/tiny/title-protocol.ts +11 -1
- package/src/tiny/worker.ts +19 -7
- package/src/tools/eval.ts +202 -26
- package/src/tools/grouped-file-output.ts +9 -2
- package/src/tools/index.ts +17 -5
- package/src/tools/memory-edit.ts +4 -4
- package/src/tools/memory-recall.ts +5 -5
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +4 -4
- package/src/tools/render-utils.ts +2 -1
- package/src/tools/search.ts +480 -76
- package/dist/types/mnemosyne/backend.d.ts +0 -4
- package/dist/types/mnemosyne/config.d.ts +0 -29
- package/dist/types/mnemosyne/state.d.ts +0 -72
- /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
- /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { ToolSession } from "../../tools";
|
|
3
3
|
import { ToolError } from "../../tools/tool-errors";
|
|
4
|
+
import { EVAL_AGENT_BRIDGE_NAME, runEvalAgent } from "../agent-bridge";
|
|
5
|
+
import { EVAL_BUDGET_BRIDGE_NAME, type EvalBudgetResult, runEvalBudget } from "../budget-bridge";
|
|
4
6
|
import { EVAL_LLM_BRIDGE_NAME, runEvalLlm } from "../llm-bridge";
|
|
5
7
|
import type { JsStatusEvent } from "./shared/types";
|
|
6
8
|
|
|
@@ -14,6 +16,7 @@ interface ToolBridgeOptions {
|
|
|
14
16
|
|
|
15
17
|
type ToolValue =
|
|
16
18
|
| string
|
|
19
|
+
| EvalBudgetResult
|
|
17
20
|
| {
|
|
18
21
|
text: string;
|
|
19
22
|
details?: unknown;
|
|
@@ -105,6 +108,12 @@ export async function callSessionTool(name: string, args: unknown, options: Tool
|
|
|
105
108
|
if (name === EVAL_LLM_BRIDGE_NAME) {
|
|
106
109
|
return await runEvalLlm(args, options);
|
|
107
110
|
}
|
|
111
|
+
if (name === EVAL_AGENT_BRIDGE_NAME) {
|
|
112
|
+
return await runEvalAgent(args, options);
|
|
113
|
+
}
|
|
114
|
+
if (name === EVAL_BUDGET_BRIDGE_NAME) {
|
|
115
|
+
return await runEvalBudget(args, options);
|
|
116
|
+
}
|
|
108
117
|
const tool = getTool(options.session, name);
|
|
109
118
|
const normalizedArgs = normalizeArgs(args);
|
|
110
119
|
const toolCallId = `js-${name}-${crypto.randomUUID()}`;
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface PythonExecutorOptions {
|
|
|
25
25
|
timeoutMs?: number;
|
|
26
26
|
/** Absolute wall-clock deadline in milliseconds since epoch */
|
|
27
27
|
deadlineMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Inactivity budget (ms). Used only for timeout-annotation text when the
|
|
30
|
+
* caller drives cancellation via an idle-aware `signal` instead of a
|
|
31
|
+
* wall-clock `deadlineMs`/`timeoutMs`. Does not arm a timer.
|
|
32
|
+
*/
|
|
33
|
+
idleTimeoutMs?: number;
|
|
28
34
|
/** Callback for streaming output chunks (already sanitized) */
|
|
29
35
|
onChunk?: (chunk: string) => Promise<void> | void;
|
|
30
36
|
/** AbortSignal for cancellation */
|
|
@@ -57,6 +63,13 @@ export interface PythonExecutorOptions {
|
|
|
57
63
|
toolSession?: ToolSession;
|
|
58
64
|
/** Callback for status events emitted by tool bridge invocations. */
|
|
59
65
|
emitStatus?: (event: JsStatusEvent) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Live status events streamed as they are emitted (both host-side bridge
|
|
68
|
+
* helpers like `agent()` and kernel-side `display`/`log`/`phase`). Mirrors
|
|
69
|
+
* what lands in `displayOutputs` so callers can render progress before the
|
|
70
|
+
* cell finishes.
|
|
71
|
+
*/
|
|
72
|
+
onStatus?: (event: JsStatusEvent) => void;
|
|
60
73
|
/** @internal Bridge session id, set by `executePython` before delegating. */
|
|
61
74
|
bridgeSessionId?: string;
|
|
62
75
|
/** @internal Bridge endpoint info, set by `executePython` before delegating. */
|
|
@@ -218,18 +231,20 @@ async function waitForPromiseWithCancellation<T>(
|
|
|
218
231
|
// Result formatting
|
|
219
232
|
// ---------------------------------------------------------------------------
|
|
220
233
|
|
|
221
|
-
function formatTimeoutAnnotation(timeoutMs?: number): string | undefined {
|
|
234
|
+
function formatTimeoutAnnotation(timeoutMs?: number, idle = false): string | undefined {
|
|
235
|
+
const suffix = idle ? " of inactivity" : "";
|
|
222
236
|
if (timeoutMs === undefined) return "Command timed out";
|
|
223
237
|
const secs = Math.max(1, Math.round(timeoutMs / 1000));
|
|
224
|
-
return `Command timed out after ${secs} seconds`;
|
|
238
|
+
return `Command timed out after ${secs} seconds${suffix}`;
|
|
225
239
|
}
|
|
226
240
|
|
|
227
|
-
function formatKernelTimeoutAnnotation(timeoutMs: number | undefined, kernelKilled: boolean): string {
|
|
241
|
+
function formatKernelTimeoutAnnotation(timeoutMs: number | undefined, kernelKilled: boolean, idle = false): string {
|
|
228
242
|
const secs = timeoutMs === undefined ? undefined : Math.max(1, Math.round(timeoutMs / 1000));
|
|
243
|
+
const suffix = idle ? " of inactivity" : "";
|
|
229
244
|
if (kernelKilled) {
|
|
230
|
-
return
|
|
245
|
+
return `eval cell timed out${suffix} and the kernel was unresponsive to interrupt; the kernel has been killed and will be recreated on the next call.`;
|
|
231
246
|
}
|
|
232
|
-
const duration = secs === undefined ? "the configured timeout" : `${secs}s`;
|
|
247
|
+
const duration = secs === undefined ? "the configured timeout" : `${secs}s${suffix}`;
|
|
233
248
|
return `eval cell timed out after ${duration}; kernel interrupted but remains running. Reset the kernel via { reset: true } if state appears corrupted.`;
|
|
234
249
|
}
|
|
235
250
|
|
|
@@ -473,12 +488,18 @@ async function executeWithKernel(
|
|
|
473
488
|
const displayOutputs: KernelDisplayOutput[] = [];
|
|
474
489
|
const deadlineMs = getExecutionDeadlineMs(options);
|
|
475
490
|
let executionTimeoutMs: number | undefined;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
491
|
+
// Idle mode: the caller (eval tool) drives cancellation via an idle-aware
|
|
492
|
+
// signal and passes no wall-clock deadline, so annotate timeouts with the
|
|
493
|
+
// configured inactivity budget rather than a remaining-deadline figure.
|
|
494
|
+
const idleMode = deadlineMs === undefined && options?.idleTimeoutMs !== undefined;
|
|
495
|
+
|
|
496
|
+
// Collect every display output and, for status events, stream them live so
|
|
497
|
+
// long-running bridge helpers (e.g. `agent()`) surface progress mid-cell.
|
|
498
|
+
const collectDisplay = (output: KernelDisplayOutput) => {
|
|
499
|
+
displayOutputs.push(output);
|
|
500
|
+
if (output.type === "status") options?.onStatus?.(output.event);
|
|
501
|
+
};
|
|
502
|
+
const emitStatus = options?.emitStatus ?? ((event: JsStatusEvent) => collectDisplay({ type: "status", event }));
|
|
482
503
|
const runId = `py-${crypto.randomUUID()}`;
|
|
483
504
|
const unregisterBridge =
|
|
484
505
|
options?.toolSession && options?.bridgeSessionId
|
|
@@ -498,12 +519,16 @@ async function executeWithKernel(
|
|
|
498
519
|
signal: options?.signal,
|
|
499
520
|
timeoutMs: executionTimeoutMs,
|
|
500
521
|
onChunk: text => sink.push(text),
|
|
501
|
-
onDisplay: output =>
|
|
522
|
+
onDisplay: output => collectDisplay(output),
|
|
502
523
|
});
|
|
503
524
|
|
|
504
525
|
if (result.cancelled) {
|
|
505
526
|
const annotation = result.timedOut
|
|
506
|
-
? formatKernelTimeoutAnnotation(
|
|
527
|
+
? formatKernelTimeoutAnnotation(
|
|
528
|
+
executionTimeoutMs ?? options?.idleTimeoutMs,
|
|
529
|
+
result.kernelKilled ?? false,
|
|
530
|
+
idleMode,
|
|
531
|
+
)
|
|
507
532
|
: undefined;
|
|
508
533
|
return {
|
|
509
534
|
exitCode: undefined,
|
|
@@ -540,7 +565,9 @@ async function executeWithKernel(
|
|
|
540
565
|
cancelled: true,
|
|
541
566
|
displayOutputs,
|
|
542
567
|
stdinRequested: false,
|
|
543
|
-
...(await sink.dump(
|
|
568
|
+
...(await sink.dump(
|
|
569
|
+
timedOut ? formatTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs, idleMode) : undefined,
|
|
570
|
+
)),
|
|
544
571
|
};
|
|
545
572
|
}
|
|
546
573
|
const error = err instanceof Error ? err : new Error(String(err));
|
package/src/eval/py/index.ts
CHANGED
|
@@ -28,7 +28,7 @@ export default {
|
|
|
28
28
|
const kernelMode = readSetting<PythonExecutorOptions["kernelMode"]>(opts.session, "python.kernelMode");
|
|
29
29
|
const executorOptions: PythonExecutorOptions = {
|
|
30
30
|
cwd: opts.cwd,
|
|
31
|
-
|
|
31
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
32
32
|
signal: opts.signal,
|
|
33
33
|
sessionId: namespaceSessionId(opts.sessionId),
|
|
34
34
|
kernelMode,
|
|
@@ -39,6 +39,7 @@ export default {
|
|
|
39
39
|
artifactPath: opts.artifactPath,
|
|
40
40
|
artifactId: opts.artifactId,
|
|
41
41
|
onChunk: opts.onChunk,
|
|
42
|
+
onStatus: opts.onStatus,
|
|
42
43
|
toolSession: opts.session,
|
|
43
44
|
};
|
|
44
45
|
const result = await executePython(code, executorOptions);
|
package/src/eval/py/prelude.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
if "__omp_prelude_loaded__" not in globals():
|
|
4
4
|
__omp_prelude_loaded__ = True
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
import os, json
|
|
6
|
+
import os, json, math
|
|
7
7
|
|
|
8
8
|
# __omp_display is injected by runner.py before the prelude executes; it
|
|
9
9
|
# mirrors IPython's display() semantics with the same MIME bundle output.
|
|
@@ -479,3 +479,134 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
479
479
|
res = _bridge_call("__llm__", args)
|
|
480
480
|
text = res.get("text") if isinstance(res, dict) else res
|
|
481
481
|
return json.loads(text) if schema is not None else text
|
|
482
|
+
|
|
483
|
+
def agent(prompt, *, agent_type="task", model=None, context=None, label=None, schema=None):
|
|
484
|
+
"""Run a subagent and return its final output.
|
|
485
|
+
|
|
486
|
+
`agent_type` selects the subagent definition (default "task"). Pass
|
|
487
|
+
`model` to override that agent's model, `context` for shared background,
|
|
488
|
+
`label` for the output artifact id, and `schema` to request structured
|
|
489
|
+
JSON output; when `schema` is supplied the parsed object is returned.
|
|
490
|
+
"""
|
|
491
|
+
args = {"prompt": prompt}
|
|
492
|
+
if agent_type is not None:
|
|
493
|
+
args["agentType"] = agent_type
|
|
494
|
+
if model is not None:
|
|
495
|
+
args["model"] = model
|
|
496
|
+
if context is not None:
|
|
497
|
+
args["context"] = context
|
|
498
|
+
if label is not None:
|
|
499
|
+
args["label"] = label
|
|
500
|
+
if schema is not None:
|
|
501
|
+
args["schema"] = schema
|
|
502
|
+
res = _bridge_call("__agent__", args)
|
|
503
|
+
text = res.get("text") if isinstance(res, dict) else res
|
|
504
|
+
return json.loads(text) if schema is not None else text
|
|
505
|
+
|
|
506
|
+
def _normalize_concurrency(value):
|
|
507
|
+
"""Clamp a concurrency hint to [1, 16], defaulting to 4 on bad input."""
|
|
508
|
+
try:
|
|
509
|
+
n = int(value)
|
|
510
|
+
except (TypeError, ValueError):
|
|
511
|
+
n = 4
|
|
512
|
+
return max(1, min(16, n))
|
|
513
|
+
|
|
514
|
+
def _pool_map(items, fn, concurrency):
|
|
515
|
+
"""Run ``fn`` over ``items`` through a bounded thread pool.
|
|
516
|
+
|
|
517
|
+
Preserves input order, barriers until every task settles, and raises the
|
|
518
|
+
lowest-index exception if any task failed. Each task runs inside a copy
|
|
519
|
+
of the submitting thread's context so the ``_CURRENT_RID`` ContextVar
|
|
520
|
+
propagates and bridge calls (agent(), tool.*, etc.) keep working.
|
|
521
|
+
"""
|
|
522
|
+
import concurrent.futures, contextvars
|
|
523
|
+
items = list(items)
|
|
524
|
+
if not items:
|
|
525
|
+
return []
|
|
526
|
+
workers = min(_normalize_concurrency(concurrency), len(items))
|
|
527
|
+
results = [None] * len(items)
|
|
528
|
+
errors = {}
|
|
529
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool:
|
|
530
|
+
futures = {}
|
|
531
|
+
for i, item in enumerate(items):
|
|
532
|
+
ctx = contextvars.copy_context()
|
|
533
|
+
futures[pool.submit(ctx.run, fn, item)] = i
|
|
534
|
+
for fut in concurrent.futures.as_completed(futures):
|
|
535
|
+
i = futures[fut]
|
|
536
|
+
try:
|
|
537
|
+
results[i] = fut.result()
|
|
538
|
+
except BaseException as exc: # noqa: BLE001 - propagate to caller
|
|
539
|
+
errors[i] = exc
|
|
540
|
+
if errors:
|
|
541
|
+
raise errors[min(errors)]
|
|
542
|
+
return results
|
|
543
|
+
|
|
544
|
+
def parallel(thunks, *, concurrency=4):
|
|
545
|
+
"""Run zero-arg callables through a bounded pool, preserving input order.
|
|
546
|
+
|
|
547
|
+
Barriers until all finish; re-raises the lowest-index exception if any
|
|
548
|
+
thunk raised.
|
|
549
|
+
"""
|
|
550
|
+
thunks = list(thunks)
|
|
551
|
+
for t in thunks:
|
|
552
|
+
if not callable(t):
|
|
553
|
+
raise TypeError("parallel() expects an iterable of zero-arg callables")
|
|
554
|
+
return _pool_map(thunks, lambda t: t(), concurrency)
|
|
555
|
+
|
|
556
|
+
def pipeline(items, *stages, concurrency=4):
|
|
557
|
+
"""Map items left-to-right through one-arg stage callables.
|
|
558
|
+
|
|
559
|
+
Every item clears stage N before any item enters stage N+1 (barrier per
|
|
560
|
+
stage). Stage 1 receives the original item; later stages receive the
|
|
561
|
+
previous stage's result.
|
|
562
|
+
"""
|
|
563
|
+
current = list(items)
|
|
564
|
+
for stage in stages:
|
|
565
|
+
if not callable(stage):
|
|
566
|
+
raise TypeError("pipeline() stages must be callables")
|
|
567
|
+
current = _pool_map(current, stage, concurrency)
|
|
568
|
+
return current
|
|
569
|
+
|
|
570
|
+
def log(message):
|
|
571
|
+
"""Emit a status ``log`` event for TUI rendering."""
|
|
572
|
+
_emit_status("log", message=str(message))
|
|
573
|
+
return None
|
|
574
|
+
|
|
575
|
+
def phase(title):
|
|
576
|
+
"""Record the current readable phase and emit a status ``phase`` event."""
|
|
577
|
+
globals()["__omp_current_phase__"] = str(title)
|
|
578
|
+
_emit_status("phase", title=str(title))
|
|
579
|
+
return None
|
|
580
|
+
|
|
581
|
+
class _Budget:
|
|
582
|
+
"""Live view of the host Goal Mode token budget via the host bridge."""
|
|
583
|
+
|
|
584
|
+
@property
|
|
585
|
+
def total(self):
|
|
586
|
+
snap = _bridge_call("__budget__", {})
|
|
587
|
+
return (snap or {}).get("total")
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def hard(self):
|
|
591
|
+
snap = _bridge_call("__budget__", {})
|
|
592
|
+
return bool((snap or {}).get("hard"))
|
|
593
|
+
|
|
594
|
+
def spent(self):
|
|
595
|
+
snap = _bridge_call("__budget__", {})
|
|
596
|
+
return int((snap or {}).get("spent") or 0)
|
|
597
|
+
|
|
598
|
+
def remaining(self):
|
|
599
|
+
snap = _bridge_call("__budget__", {}) or {}
|
|
600
|
+
total = snap.get("total")
|
|
601
|
+
if total is None:
|
|
602
|
+
return math.inf
|
|
603
|
+
return max(0, total - int(snap.get("spent") or 0))
|
|
604
|
+
|
|
605
|
+
def __repr__(self):
|
|
606
|
+
try:
|
|
607
|
+
snap = _bridge_call("__budget__", {}) or {}
|
|
608
|
+
return f"<budget total={snap.get('total')} spent={snap.get('spent')}>"
|
|
609
|
+
except Exception:
|
|
610
|
+
return "<budget unavailable>"
|
|
611
|
+
|
|
612
|
+
budget = _Budget()
|
|
@@ -16,6 +16,7 @@ export interface BashExecutorOptions {
|
|
|
16
16
|
cwd?: string;
|
|
17
17
|
timeout?: number;
|
|
18
18
|
onChunk?: (chunk: string) => void;
|
|
19
|
+
chunkThrottleMs?: number;
|
|
19
20
|
signal?: AbortSignal;
|
|
20
21
|
/** Session key suffix to isolate shell sessions per agent */
|
|
21
22
|
sessionKey?: string;
|
|
@@ -97,9 +98,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
97
98
|
artifactId: options?.artifactId,
|
|
98
99
|
headBytes: resolveOutputSinkHeadBytes(settings),
|
|
99
100
|
maxColumns: resolveOutputMaxColumns(settings),
|
|
100
|
-
|
|
101
|
-
// event loop when commands produce massive output (e.g. seq 1 50M).
|
|
102
|
-
chunkThrottleMs: options?.onChunk ? 50 : 0,
|
|
101
|
+
chunkThrottleMs: options?.onChunk ? (options.chunkThrottleMs ?? 50) : 0,
|
|
103
102
|
});
|
|
104
103
|
|
|
105
104
|
// sink.push() is synchronous — buffer management, counters, and onChunk
|
|
@@ -101,11 +101,11 @@ export type CustomToolSessionEvent =
|
|
|
101
101
|
| {
|
|
102
102
|
reason: "auto_compaction_start";
|
|
103
103
|
trigger: "threshold" | "overflow" | "idle" | "incomplete";
|
|
104
|
-
action: "context-full" | "handoff";
|
|
104
|
+
action: "context-full" | "handoff" | "shake" | "shake-summary";
|
|
105
105
|
}
|
|
106
106
|
| {
|
|
107
107
|
reason: "auto_compaction_end";
|
|
108
|
-
action: "context-full" | "handoff";
|
|
108
|
+
action: "context-full" | "handoff" | "shake" | "shake-summary";
|
|
109
109
|
result: CompactionResult | undefined;
|
|
110
110
|
aborted: boolean;
|
|
111
111
|
willRetry: boolean;
|
|
@@ -315,9 +315,15 @@ export class ExtensionRunner {
|
|
|
315
315
|
return tools;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Aggregate the registered CLI flags across a set of extensions (last write
|
|
320
|
+
* wins on name collision). Static so callers that need the flag set before a
|
|
321
|
+
* runner exists — e.g. the CLI resolving `@file`/flag args before session
|
|
322
|
+
* creation — share this exact logic instead of duplicating it.
|
|
323
|
+
*/
|
|
324
|
+
static aggregateFlags(extensions: readonly Extension[]): Map<string, ExtensionFlag> {
|
|
319
325
|
const allFlags = new Map<string, ExtensionFlag>();
|
|
320
|
-
for (const ext of
|
|
326
|
+
for (const ext of extensions) {
|
|
321
327
|
for (const [name, flag] of ext.flags) {
|
|
322
328
|
allFlags.set(name, flag);
|
|
323
329
|
}
|
|
@@ -325,6 +331,10 @@ export class ExtensionRunner {
|
|
|
325
331
|
return allFlags;
|
|
326
332
|
}
|
|
327
333
|
|
|
334
|
+
getFlags(): Map<string, ExtensionFlag> {
|
|
335
|
+
return ExtensionRunner.aggregateFlags(this.extensions);
|
|
336
|
+
}
|
|
337
|
+
|
|
328
338
|
getFlagValues(): Map<string, boolean | string> {
|
|
329
339
|
return new Map(this.runtime.flagValues);
|
|
330
340
|
}
|
|
@@ -25,6 +25,29 @@ const KNOWN_HOSTS: Record<string, (pathname: string, hash: string) => { user: st
|
|
|
25
25
|
"codeberg.org": extractStandard,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Namespaced shorthand prefixes accepted by `omp plugin install`, mapped to
|
|
30
|
+
* their canonical host. `PluginManager.install` normalizes non-GitHub prefixes
|
|
31
|
+
* before invoking bun because bun only treats `github:` as a hosted shorthand.
|
|
32
|
+
*/
|
|
33
|
+
const SHORTHAND_PREFIXES: Record<string, string> = {
|
|
34
|
+
github: "github.com",
|
|
35
|
+
gitlab: "gitlab.com",
|
|
36
|
+
bitbucket: "bitbucket.org",
|
|
37
|
+
codeberg: "codeberg.org",
|
|
38
|
+
sourcehut: "git.sr.ht",
|
|
39
|
+
srht: "git.sr.ht",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `<prefix>:<user>/<repo>[.git][#<ref>]` shape. `<repo>` is non-greedy so the
|
|
44
|
+
* optional `.git` suffix and `#ref` tail bind tightly; `<repo>` may itself
|
|
45
|
+
* contain `/` to support nested GitLab groups (`gitlab:group/sub/project`).
|
|
46
|
+
* `<user>` rejects `/`, `:`, `#` to keep protocol URLs (`https://…`) and
|
|
47
|
+
* scp-like SSH (`git@github.com:user/repo`) out of this path.
|
|
48
|
+
*/
|
|
49
|
+
const SHORTHAND_RE = /^([a-z]+):([^/:#]+)\/([^#]+?)(?:\.git)?(?:#(.+))?$/i;
|
|
50
|
+
|
|
28
51
|
function stripUrlCredentials(url: string): string {
|
|
29
52
|
if (!url.includes("://")) return url;
|
|
30
53
|
try {
|
|
@@ -209,14 +232,55 @@ function parseGenericGitUrl(url: string): GitSource | null {
|
|
|
209
232
|
return { type: "git", repo, host, path: normalizedPath, ref, pinned: Boolean(ref) };
|
|
210
233
|
}
|
|
211
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Match an npm/bun-style namespaced shorthand (`github:user/repo`, optionally
|
|
237
|
+
* `…#ref` or `….git`). Returns null for protocol URLs and any prefix not in
|
|
238
|
+
* `SHORTHAND_PREFIXES` so the caller can fall through to the generic paths.
|
|
239
|
+
*/
|
|
240
|
+
function tryNamespacedShorthand(trimmed: string): GitSource | null {
|
|
241
|
+
// Cheap gate: bail out before touching protocol URLs (`https://`, `ssh://`,
|
|
242
|
+
// `git://`) where the char after the colon is always `/`. The shorthand we
|
|
243
|
+
// care about never starts with `<scheme>://`.
|
|
244
|
+
if (!/^[a-z]+:[^/]/i.test(trimmed)) return null;
|
|
245
|
+
const match = trimmed.match(SHORTHAND_RE);
|
|
246
|
+
if (!match) return null;
|
|
247
|
+
const prefix = (match[1] ?? "").toLowerCase();
|
|
248
|
+
const host = SHORTHAND_PREFIXES[prefix];
|
|
249
|
+
if (!host) return null;
|
|
250
|
+
const user = match[2] ?? "";
|
|
251
|
+
const repoPath = match[3] ?? "";
|
|
252
|
+
if (!user || !repoPath) return null;
|
|
253
|
+
const ref = match[4];
|
|
254
|
+
if (ref) {
|
|
255
|
+
try {
|
|
256
|
+
decodeURIComponent(ref);
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const fullPath = `${user}/${repoPath}`;
|
|
262
|
+
return {
|
|
263
|
+
type: "git",
|
|
264
|
+
repo: `https://${host}/${fullPath}`,
|
|
265
|
+
host,
|
|
266
|
+
path: fullPath,
|
|
267
|
+
ref: ref || undefined,
|
|
268
|
+
pinned: Boolean(ref),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
212
272
|
/**
|
|
213
273
|
* Parse git source into a GitSource.
|
|
214
274
|
*
|
|
215
275
|
* Rules:
|
|
216
|
-
* -
|
|
276
|
+
* - Namespaced shorthand (`github:user/repo`, `gitlab:`, `bitbucket:`,
|
|
277
|
+
* `codeberg:`, `sourcehut:`/`srht:`) is accepted directly; installers should
|
|
278
|
+
* normalize entries that bun does not understand natively.
|
|
279
|
+
* - With `git:` prefix, accept generic shorthand forms.
|
|
217
280
|
* - Without `git:` prefix, only accept explicit protocol URLs.
|
|
218
281
|
*
|
|
219
282
|
* Handles:
|
|
283
|
+
* - `github:user/repo[#ref]`-style namespaced shorthand
|
|
220
284
|
* - `git:` prefixed URLs (`git:github.com/user/repo`)
|
|
221
285
|
* - SSH SCP-like URLs (`git:git@github.com:user/repo`)
|
|
222
286
|
* - HTTPS/HTTP/SSH/git protocol URLs
|
|
@@ -227,10 +291,23 @@ function parseGenericGitUrl(url: string): GitSource | null {
|
|
|
227
291
|
*/
|
|
228
292
|
export function parseGitUrl(source: string): GitSource | null {
|
|
229
293
|
const trimmed = source.trim();
|
|
230
|
-
const hasGitPrefix = /^git:(?!\/\/)/i.test(trimmed);
|
|
231
|
-
const url = hasGitPrefix ? trimmed.slice(4).trim() : trimmed;
|
|
232
294
|
|
|
233
|
-
|
|
295
|
+
const shorthand = tryNamespacedShorthand(trimmed);
|
|
296
|
+
if (shorthand) return shorthand;
|
|
297
|
+
|
|
298
|
+
// Strip the `git+` URL prefix that npm/bun accept (`git+https://…`,
|
|
299
|
+
// `git+ssh://…`, `git+git://…`). The rest of the pipeline only deals with
|
|
300
|
+
// bare schemes.
|
|
301
|
+
const stripped = /^git\+/i.test(trimmed) ? trimmed.slice(4) : trimmed;
|
|
302
|
+
|
|
303
|
+
const hasGitPrefix = /^git:(?!\/\/)/i.test(stripped);
|
|
304
|
+
const url = hasGitPrefix ? stripped.slice(4).trim() : stripped;
|
|
305
|
+
|
|
306
|
+
// Accept: explicit protocol URL, `git:` shorthand, or scp-like SSH
|
|
307
|
+
// (`git@host:user/repo`). The scp form is unambiguous — no local path
|
|
308
|
+
// starts with `git@` — and matches the syntax that `git clone` itself
|
|
309
|
+
// accepts, which `bun install` forwards through.
|
|
310
|
+
if (!hasGitPrefix && !/^(https?|ssh|git):\/\//i.test(url) && !/^git@[^:]+:.+\/.+/i.test(url)) {
|
|
234
311
|
return null;
|
|
235
312
|
}
|
|
236
313
|
|
|
@@ -279,3 +356,12 @@ export function parseGitUrl(source: string): GitSource | null {
|
|
|
279
356
|
|
|
280
357
|
return parseGenericGitUrl(url);
|
|
281
358
|
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Returns true if the spec is parseable as a git source (protocol URL,
|
|
362
|
+
* scp-like SSH wrapped in `git:`, plain `git:` shorthand, or namespaced
|
|
363
|
+
* shorthand like `github:user/repo`). The inverse of "this is an npm spec".
|
|
364
|
+
*/
|
|
365
|
+
export function isGitSpec(spec: string): boolean {
|
|
366
|
+
return parseGitUrl(spec) !== null;
|
|
367
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isEnoent,
|
|
11
11
|
logger,
|
|
12
12
|
} from "@oh-my-pi/pi-utils";
|
|
13
|
+
import { type GitSource, parseGitUrl } from "./git-url";
|
|
13
14
|
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
14
15
|
import type {
|
|
15
16
|
DoctorCheck,
|
|
@@ -29,8 +30,14 @@ import type {
|
|
|
29
30
|
/** Valid npm package name pattern (scoped and unscoped, with optional version) */
|
|
30
31
|
const VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[a-z0-9-._^~>=<]+)?$/i;
|
|
31
32
|
|
|
33
|
+
/** Characters that are never valid in any plugin install spec — git or npm. */
|
|
34
|
+
const SHELL_METACHARS = /[;&|`$(){}<>\\\n\r\t]/;
|
|
35
|
+
|
|
32
36
|
/**
|
|
33
|
-
* Validate package name to prevent command injection.
|
|
37
|
+
* Validate package name to prevent command injection. npm specs only — git
|
|
38
|
+
* specs (`github:user/repo`, `https://github.com/...`, ...) MUST go through
|
|
39
|
+
* {@link validateGitSpec} instead because they contain characters npm rejects
|
|
40
|
+
* (`:`, `/`, `#`, `+`, `@` in non-version positions).
|
|
34
41
|
*/
|
|
35
42
|
function validatePackageName(name: string): void {
|
|
36
43
|
// Remove version specifier for validation
|
|
@@ -44,6 +51,29 @@ function validatePackageName(name: string): void {
|
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Validate a git install spec — accepts `:`, `/`, `#`, `+`, `.`, `-`, `_`,
|
|
56
|
+
* `~`, `@` (which would all fail {@link validatePackageName}) but rejects
|
|
57
|
+
* shell metacharacters so the spec stays safe when forwarded to bun install.
|
|
58
|
+
* `Bun.spawn` does not invoke a shell, but defense-in-depth keeps things
|
|
59
|
+
* obvious for future readers.
|
|
60
|
+
*/
|
|
61
|
+
function validateGitSpec(spec: string): void {
|
|
62
|
+
if (SHELL_METACHARS.test(spec)) {
|
|
63
|
+
throw new Error(`Invalid characters in plugin source: ${spec}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function gitInstallSpec(original: string, source: GitSource): string {
|
|
68
|
+
if (/^github:/i.test(original) || !/^[a-z]+:[^/]/i.test(original)) {
|
|
69
|
+
return original;
|
|
70
|
+
}
|
|
71
|
+
if (!source.ref || source.repo.includes("#")) {
|
|
72
|
+
return source.repo;
|
|
73
|
+
}
|
|
74
|
+
return `${source.repo}#${source.ref}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
// =============================================================================
|
|
48
78
|
// Plugin Manager
|
|
49
79
|
// =============================================================================
|
|
@@ -127,12 +157,39 @@ export class PluginManager {
|
|
|
127
157
|
}
|
|
128
158
|
}
|
|
129
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Read the `dependencies` map from `plugins/package.json`. Returns an empty
|
|
162
|
+
* object when the file does not exist yet so callers can diff `before`
|
|
163
|
+
* against `after` to discover the package bun just installed under its
|
|
164
|
+
* real name (git specs do not encode the package name in the spec itself).
|
|
165
|
+
*/
|
|
166
|
+
async #readDeps(pkgJsonPath: string): Promise<Record<string, string>> {
|
|
167
|
+
try {
|
|
168
|
+
const json = await Bun.file(pkgJsonPath).json();
|
|
169
|
+
return (json.dependencies as Record<string, string>) ?? {};
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (isEnoent(err)) return {};
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
130
176
|
// ==========================================================================
|
|
131
177
|
// Install / Uninstall
|
|
132
178
|
// ==========================================================================
|
|
133
179
|
|
|
134
180
|
/**
|
|
135
|
-
* Install a plugin
|
|
181
|
+
* Install a plugin with optional feature selection.
|
|
182
|
+
*
|
|
183
|
+
* Accepts:
|
|
184
|
+
* - npm specs: `pkg`, `pkg@1.2.3`, `@scope/pkg`, `pkg[features]`
|
|
185
|
+
* - namespaced git shorthand: `github:user/repo[#ref]`, `gitlab:`, `bitbucket:`,
|
|
186
|
+
* `codeberg:`, `sourcehut:`/`srht:`
|
|
187
|
+
* - full git URLs: `https://github.com/user/repo`, `git@github.com:user/repo`,
|
|
188
|
+
* `ssh://…`, `git+https://…`
|
|
189
|
+
*
|
|
190
|
+
* For git specs the package name is not knowable from the spec, so the
|
|
191
|
+
* installer diffs `plugins/package.json` `dependencies` before and after
|
|
192
|
+
* to find the newly added key.
|
|
136
193
|
*
|
|
137
194
|
* @param specString - Package specifier with optional features: "pkg", "pkg[feat]", "pkg[*]", "pkg[]"
|
|
138
195
|
* @param options - Install options
|
|
@@ -140,7 +197,12 @@ export class PluginManager {
|
|
|
140
197
|
*/
|
|
141
198
|
async install(specString: string, options: InstallOptions = {}): Promise<InstalledPlugin> {
|
|
142
199
|
const spec = parsePluginSpec(specString);
|
|
143
|
-
|
|
200
|
+
const gitSource = parseGitUrl(spec.packageName);
|
|
201
|
+
if (gitSource) {
|
|
202
|
+
validateGitSpec(spec.packageName);
|
|
203
|
+
} else {
|
|
204
|
+
validatePackageName(spec.packageName);
|
|
205
|
+
}
|
|
144
206
|
|
|
145
207
|
await this.#ensurePackageJson();
|
|
146
208
|
|
|
@@ -154,9 +216,12 @@ export class PluginManager {
|
|
|
154
216
|
enabled: true,
|
|
155
217
|
};
|
|
156
218
|
}
|
|
219
|
+
const pkgJsonPath = getPluginsPackageJson();
|
|
220
|
+
const depsBefore = gitSource ? await this.#readDeps(pkgJsonPath) : {};
|
|
221
|
+
const packageInstallSpec = gitSource ? gitInstallSpec(spec.packageName, gitSource) : spec.packageName;
|
|
157
222
|
|
|
158
223
|
// Run npm install
|
|
159
|
-
const proc = Bun.spawn(["bun", "install",
|
|
224
|
+
const proc = Bun.spawn(["bun", "install", packageInstallSpec], {
|
|
160
225
|
cwd: getPluginsDir(),
|
|
161
226
|
stdin: "ignore",
|
|
162
227
|
stdout: "pipe",
|
|
@@ -169,9 +234,40 @@ export class PluginManager {
|
|
|
169
234
|
const stderr = await new Response(proc.stderr).text();
|
|
170
235
|
throw new Error(`npm install failed: ${stderr}`);
|
|
171
236
|
}
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
237
|
+
// Resolve actual package name. npm specs encode the name (strip version);
|
|
238
|
+
// git specs do not, so diff plugins/package.json deps to find the new entry.
|
|
239
|
+
let actualName: string;
|
|
240
|
+
if (gitSource) {
|
|
241
|
+
const depsAfter = await this.#readDeps(pkgJsonPath);
|
|
242
|
+
let resolved: string | undefined;
|
|
243
|
+
for (const key of Object.keys(depsAfter)) {
|
|
244
|
+
if (!(key in depsBefore)) {
|
|
245
|
+
resolved = key;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Fallback: a force-reinstall of an already-present git plugin will not
|
|
250
|
+
// add a new key, just rewrite the existing one to the new spec value.
|
|
251
|
+
// Match by the install value for force-reinstalls where no new key is
|
|
252
|
+
// added (non-GitHub shorthands are normalized before bun sees them).
|
|
253
|
+
if (!resolved) {
|
|
254
|
+
const needle = packageInstallSpec.replace(/^git\+/i, "");
|
|
255
|
+
for (const [key, value] of Object.entries(depsAfter)) {
|
|
256
|
+
if (typeof value === "string" && value.includes(needle)) {
|
|
257
|
+
resolved = key;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!resolved) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Installed ${spec.packageName} but could not determine package name from plugins/package.json`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
actualName = resolved;
|
|
268
|
+
} else {
|
|
269
|
+
actualName = extractPackageName(spec.packageName);
|
|
270
|
+
}
|
|
175
271
|
const pkgPath = path.join(getPluginsNodeModules(), actualName, "package.json");
|
|
176
272
|
|
|
177
273
|
let pkg: { name: string; version: string; omp?: PluginManifest; pi?: PluginManifest };
|
|
@@ -204,13 +204,13 @@ export interface TurnEndEvent {
|
|
|
204
204
|
export interface AutoCompactionStartEvent {
|
|
205
205
|
type: "auto_compaction_start";
|
|
206
206
|
reason: "threshold" | "overflow" | "idle" | "incomplete";
|
|
207
|
-
action: "context-full" | "handoff";
|
|
207
|
+
action: "context-full" | "handoff" | "shake" | "shake-summary";
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/** Fired when auto-compaction ends */
|
|
211
211
|
export interface AutoCompactionEndEvent {
|
|
212
212
|
type: "auto_compaction_end";
|
|
213
|
-
action: "context-full" | "handoff";
|
|
213
|
+
action: "context-full" | "handoff" | "shake" | "shake-summary";
|
|
214
214
|
result: CompactionResult | undefined;
|
|
215
215
|
aborted: boolean;
|
|
216
216
|
willRetry: boolean;
|