@oh-my-pi/pi-coding-agent 15.1.2 → 15.1.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 +42 -0
- package/dist/types/cli/auth-broker-cli.d.ts +25 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
- package/dist/types/cli/grievances-cli.d.ts +12 -0
- package/dist/types/commands/auth-broker.d.ts +54 -0
- package/dist/types/commands/auth-gateway.d.ts +32 -0
- package/dist/types/commands/grievances.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -1
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +46 -0
- package/dist/types/discovery/agents.d.ts +12 -1
- package/dist/types/edit/renderer.d.ts +3 -0
- package/dist/types/eval/index.d.ts +0 -2
- package/dist/types/goals/tools/goal-tool.d.ts +10 -2
- package/dist/types/index.d.ts +0 -1
- package/dist/types/internal-urls/index.d.ts +1 -1
- package/dist/types/internal-urls/{pi-protocol.d.ts → omp-protocol.d.ts} +3 -3
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/modes/acp/acp-agent.d.ts +1 -0
- package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/plan-mode/approved-plan.d.ts +4 -0
- package/dist/types/sdk.d.ts +10 -3
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/auth-broker-config.d.ts +13 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +41 -7
- package/dist/types/tools/irc.d.ts +8 -2
- package/dist/types/tools/report-tool-issue.d.ts +118 -1
- package/dist/types/tools/resolve.d.ts +8 -2
- package/examples/custom-tools/README.md +3 -12
- package/examples/extensions/README.md +2 -15
- package/examples/extensions/api-demo.ts +1 -7
- package/package.json +7 -7
- package/src/autoresearch/tools/init-experiment.ts +11 -33
- package/src/autoresearch/tools/log-experiment.ts +10 -24
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +2 -9
- package/src/cli/auth-broker-cli.ts +746 -0
- package/src/cli/auth-gateway-cli.ts +342 -0
- package/src/cli/grievances-cli.ts +109 -16
- package/src/cli.ts +4 -2
- package/src/commands/auth-broker.ts +96 -0
- package/src/commands/auth-gateway.ts +61 -0
- package/src/commands/grievances.ts +13 -8
- package/src/commands/launch.ts +1 -1
- package/src/commit/agentic/agent.ts +2 -0
- package/src/commit/agentic/tools/analyze-file.ts +2 -2
- package/src/commit/agentic/tools/git-file-diff.ts +2 -2
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +2 -2
- package/src/commit/agentic/tools/propose-changelog.ts +1 -3
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -9
- package/src/config/model-equivalence.ts +279 -174
- package/src/config/model-registry.ts +37 -6
- package/src/config/model-resolver.ts +13 -8
- package/src/config/models-config-schema.ts +8 -0
- package/src/config/settings-schema.ts +52 -0
- package/src/cursor.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/profiler.ts +4 -0
- package/src/debug/raw-sse-buffer.ts +100 -59
- package/src/debug/raw-sse.ts +1 -1
- package/src/discovery/agents.ts +15 -4
- package/src/edit/modes/apply-patch.ts +1 -5
- package/src/edit/modes/patch.ts +5 -5
- package/src/edit/modes/replace.ts +5 -5
- package/src/edit/renderer.ts +2 -1
- package/src/edit/streaming.ts +1 -1
- package/src/eval/index.ts +0 -2
- package/src/eval/js/shared/runtime.ts +25 -0
- package/src/eval/py/kernel.ts +1 -1
- package/src/exa/researcher.ts +4 -4
- package/src/exa/search.ts +10 -22
- package/src/exa/websets.ts +33 -33
- package/src/goals/tools/goal-tool.ts +3 -3
- package/src/index.ts +0 -3
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/internal-urls/index.ts +1 -1
- package/src/internal-urls/{pi-protocol.ts → omp-protocol.ts} +10 -10
- package/src/internal-urls/router.ts +3 -3
- package/src/internal-urls/types.ts +1 -1
- package/src/lsp/types.ts +8 -11
- package/src/main.ts +3 -0
- package/src/mcp/tool-bridge.ts +3 -3
- package/src/modes/acp/acp-agent.ts +88 -25
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/diff.ts +1 -2
- package/src/modes/components/eval-execution.ts +1 -1
- package/src/modes/components/oauth-selector.ts +38 -2
- package/src/modes/components/tool-execution.ts +1 -2
- package/src/modes/controllers/command-controller.ts +95 -34
- package/src/modes/controllers/input-controller.ts +4 -3
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/interactive-mode.ts +92 -19
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/prompt-action-autocomplete.ts +14 -0
- package/src/plan-mode/approved-plan.ts +9 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/tools/eval.md +25 -26
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/resolve.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/web-search.md +1 -1
- package/src/sdk.ts +78 -7
- package/src/session/agent-session.ts +176 -77
- package/src/session/agent-storage.ts +7 -2
- package/src/session/auth-broker-config.ts +102 -0
- package/src/session/auth-storage.ts +7 -1
- package/src/session/streaming-output.ts +1 -1
- package/src/task/types.ts +10 -35
- package/src/tools/bash-interactive.ts +4 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser.ts +12 -20
- package/src/tools/eval.ts +77 -100
- package/src/tools/gh.ts +21 -45
- package/src/tools/hindsight-recall.ts +1 -1
- package/src/tools/hindsight-reflect.ts +2 -2
- package/src/tools/hindsight-retain.ts +3 -7
- package/src/tools/index.ts +8 -1
- package/src/tools/inspect-image.ts +4 -1
- package/src/tools/irc.ts +4 -12
- package/src/tools/job.ts +3 -11
- package/src/tools/report-tool-issue.ts +462 -17
- package/src/tools/resolve.ts +2 -7
- package/src/tools/todo-write.ts +8 -15
- package/src/utils/title-generator.ts +3 -0
- package/src/web/search/index.ts +6 -6
- package/dist/types/eval/parse.d.ts +0 -28
- package/dist/types/eval/sniff.d.ts +0 -11
- package/src/eval/eval.lark +0 -36
- package/src/eval/parse.ts +0 -407
- package/src/eval/sniff.ts +0 -28
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Searches the web for up-to-date information beyond
|
|
1
|
+
Searches the web for up-to-date information beyond knowledge cutoff.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- You SHOULD prefer primary sources (papers, official docs) and corroborate key claims with multiple sources
|
package/src/sdk.ts
CHANGED
|
@@ -7,7 +7,13 @@ import {
|
|
|
7
7
|
INTENT_FIELD,
|
|
8
8
|
type ThinkingLevel,
|
|
9
9
|
} from "@oh-my-pi/pi-agent-core";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
type CredentialDisabledEvent,
|
|
12
|
+
type Message,
|
|
13
|
+
type Model,
|
|
14
|
+
type SimpleStreamOptions,
|
|
15
|
+
streamSimple,
|
|
16
|
+
} from "@oh-my-pi/pi-ai";
|
|
11
17
|
import {
|
|
12
18
|
getOpenAICodexTransportDetails,
|
|
13
19
|
prewarmOpenAICodexResponses,
|
|
@@ -93,7 +99,8 @@ import {
|
|
|
93
99
|
SecretObfuscator,
|
|
94
100
|
} from "./secrets";
|
|
95
101
|
import { AgentSession } from "./session/agent-session";
|
|
96
|
-
import {
|
|
102
|
+
import { resolveAuthBrokerConfig } from "./session/auth-broker-config";
|
|
103
|
+
import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "./session/auth-storage";
|
|
97
104
|
import { convertToLlm } from "./session/messages";
|
|
98
105
|
import { SessionManager } from "./session/session-manager";
|
|
99
106
|
import { closeAllConnections } from "./ssh/connection-manager";
|
|
@@ -317,13 +324,37 @@ function getDefaultAgentDir(): string {
|
|
|
317
324
|
// Discovery Functions
|
|
318
325
|
|
|
319
326
|
/**
|
|
320
|
-
* Create an AuthStorage instance
|
|
321
|
-
*
|
|
327
|
+
* Create an AuthStorage instance.
|
|
328
|
+
*
|
|
329
|
+
* Default: local SQLite store at `<agentDir>/agent.db`.
|
|
330
|
+
*
|
|
331
|
+
* Broker mode: when `OMP_AUTH_BROKER_URL` is set, credentials are pulled from
|
|
332
|
+
* a remote auth-broker over the wire. Refresh tokens never leave the broker;
|
|
333
|
+
* the client receives access tokens with `refresh = "__remote__"` and calls
|
|
334
|
+
* back into the broker through the {@link AuthStorageOptions.refreshOAuthCredential}
|
|
335
|
+
* override to re-mint access tokens when needed.
|
|
322
336
|
*/
|
|
323
337
|
export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
|
|
338
|
+
const brokerConfig = await resolveAuthBrokerConfig();
|
|
339
|
+
if (brokerConfig) {
|
|
340
|
+
const client = new AuthBrokerClient({ url: brokerConfig.url, token: brokerConfig.token });
|
|
341
|
+
const initialResult = await client.fetchSnapshot();
|
|
342
|
+
if (initialResult.status !== 200) throw new Error("Auth broker returned no initial snapshot");
|
|
343
|
+
const store = new RemoteAuthCredentialStore({ client, initialSnapshot: initialResult.snapshot });
|
|
344
|
+
// Refresh + usage hooks live on RemoteAuthCredentialStore; AuthStorage
|
|
345
|
+
// discovers them automatically when no explicit option overrides them.
|
|
346
|
+
const storage = new AuthStorage(store, {
|
|
347
|
+
configValueResolver: resolveConfigValue,
|
|
348
|
+
sourceLabel: `broker ${brokerConfig.url}`,
|
|
349
|
+
});
|
|
350
|
+
await storage.reload();
|
|
351
|
+
return storage;
|
|
352
|
+
}
|
|
324
353
|
const dbPath = getAgentDbPath(agentDir);
|
|
325
|
-
|
|
326
|
-
|
|
354
|
+
const storage = await AuthStorage.create(dbPath, {
|
|
355
|
+
configValueResolver: resolveConfigValue,
|
|
356
|
+
sourceLabel: `local ${dbPath}`,
|
|
357
|
+
});
|
|
327
358
|
await storage.reload();
|
|
328
359
|
return storage;
|
|
329
360
|
}
|
|
@@ -885,6 +916,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
885
916
|
thinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
886
917
|
resolveThinkingLevelForModel(resolvedModel, thinkingLevel),
|
|
887
918
|
);
|
|
919
|
+
// Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
|
|
920
|
+
// with the rest of session setup (extension/skill load, tool registry,
|
|
921
|
+
// system prompt build). Without this, the first `fetch(...)` pays the
|
|
922
|
+
// full handshake serially — 100–300 ms transcontinental for
|
|
923
|
+
// api.anthropic.com from a residential IP. Every mode benefits
|
|
924
|
+
// (interactive, print, rpc, acp).
|
|
925
|
+
preconnectModelHost(model.baseUrl);
|
|
888
926
|
}
|
|
889
927
|
|
|
890
928
|
let skills: Skill[];
|
|
@@ -1763,6 +1801,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1763
1801
|
}
|
|
1764
1802
|
return key;
|
|
1765
1803
|
},
|
|
1804
|
+
streamFn: (streamModel, context, streamOptions) =>
|
|
1805
|
+
streamSimple(streamModel, context, {
|
|
1806
|
+
...streamOptions,
|
|
1807
|
+
onAuthError: async (provider, oldKey, error) => {
|
|
1808
|
+
await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, streamOptions?.signal);
|
|
1809
|
+
logger.debug("Retrying provider request after credential invalidation", {
|
|
1810
|
+
provider,
|
|
1811
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1812
|
+
});
|
|
1813
|
+
return modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
|
|
1814
|
+
},
|
|
1815
|
+
}),
|
|
1766
1816
|
cursorExecHandlers,
|
|
1767
1817
|
transformToolCallArguments: (args, _toolName) => {
|
|
1768
1818
|
let result = args;
|
|
@@ -1899,8 +1949,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1899
1949
|
}
|
|
1900
1950
|
|
|
1901
1951
|
// Start LSP warmup in the background so startup does not block on language server initialization.
|
|
1952
|
+
// Print/script invocations (`hasUI=false`) don't render the warmup status indicator AND typically
|
|
1953
|
+
// finish before LSP servers would have stabilized — warming them just spends CPU parsing big
|
|
1954
|
+
// `initialize` responses concurrently with the LLM stream consumer, jittering perceived latency.
|
|
1955
|
+
// Tools that need an LSP server still spin one up on demand through `getOrCreateClient`.
|
|
1902
1956
|
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
1903
|
-
if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
|
|
1957
|
+
if (enableLsp && options.hasUI && settings.get("lsp.diagnosticsOnWrite")) {
|
|
1904
1958
|
lspServers = discoverStartupLspServers(cwd);
|
|
1905
1959
|
if (lspServers.length > 0) {
|
|
1906
1960
|
void (async () => {
|
|
@@ -2017,3 +2071,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2017
2071
|
throw error;
|
|
2018
2072
|
}
|
|
2019
2073
|
}
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* Best-effort preconnect to the model's API host. Bun's `fetch.preconnect`
|
|
2077
|
+
* primes DNS + TCP + TLS + H2 so the first real request reuses the warm
|
|
2078
|
+
* connection. Errors are swallowed: preconnect is an optimization, never a
|
|
2079
|
+
* hard dependency.
|
|
2080
|
+
*/
|
|
2081
|
+
function preconnectModelHost(baseUrl: string | undefined): void {
|
|
2082
|
+
if (!baseUrl) return;
|
|
2083
|
+
const preconnect = (globalThis.fetch as typeof fetch & { preconnect?: (url: string) => void }).preconnect;
|
|
2084
|
+
if (typeof preconnect !== "function") return;
|
|
2085
|
+
try {
|
|
2086
|
+
preconnect(baseUrl);
|
|
2087
|
+
} catch {
|
|
2088
|
+
// Best effort.
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
@@ -18,12 +18,15 @@ import * as fs from "node:fs";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { scheduler } from "node:timers/promises";
|
|
20
20
|
import {
|
|
21
|
+
type AfterToolCallContext,
|
|
22
|
+
type AfterToolCallResult,
|
|
21
23
|
type Agent,
|
|
22
24
|
AgentBusyError,
|
|
23
25
|
type AgentEvent,
|
|
24
26
|
type AgentMessage,
|
|
25
27
|
type AgentState,
|
|
26
28
|
type AgentTool,
|
|
29
|
+
resolveTelemetry,
|
|
27
30
|
ThinkingLevel,
|
|
28
31
|
} from "@oh-my-pi/pi-agent-core";
|
|
29
32
|
import {
|
|
@@ -153,6 +156,7 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool
|
|
|
153
156
|
type: "text",
|
|
154
157
|
};
|
|
155
158
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
159
|
+
import ttsrToolReminderTemplate from "../prompts/system/ttsr-tool-reminder.md" with { type: "text" };
|
|
156
160
|
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
157
161
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
158
162
|
import { invalidateHostMetadata } from "../ssh/connection-manager";
|
|
@@ -767,6 +771,10 @@ export class AgentSession {
|
|
|
767
771
|
// TTSR manager for time-traveling stream rules
|
|
768
772
|
#ttsrManager: TtsrManager | undefined = undefined;
|
|
769
773
|
#pendingTtsrInjections: Rule[] = [];
|
|
774
|
+
/** Per-tool TTSR rules whose `interruptMode` opted out of aborting the stream.
|
|
775
|
+
* These are folded into the matched tool call's `toolResult` content as an
|
|
776
|
+
* in-band system reminder, instead of spawning a separate follow-up turn. */
|
|
777
|
+
#perToolTtsrInjections = new Map<string, Rule[]>();
|
|
770
778
|
#ttsrAbortPending = false;
|
|
771
779
|
#ttsrRetryToken = 0;
|
|
772
780
|
#ttsrResumePromise: Promise<void> | undefined = undefined;
|
|
@@ -880,16 +888,28 @@ export class AgentSession {
|
|
|
880
888
|
this.#transformContext = config.transformContext ?? (messages => messages);
|
|
881
889
|
this.#onPayload = config.onPayload;
|
|
882
890
|
this.rawSseDebugBuffer = config.rawSseDebugBuffer ?? new RawSseDebugBuffer();
|
|
891
|
+
// Avoid wrapping in an `async` closure when no user callback is configured: the
|
|
892
|
+
// outer await on `#onResponse` (provider-response.ts) tolerates a sync void return,
|
|
893
|
+
// and skipping the wrapper drops a per-event `newPromiseCapability` allocation that
|
|
894
|
+
// shows up as ~3.5% self time in streaming profiles.
|
|
883
895
|
const configuredOnResponse = config.onResponse;
|
|
884
|
-
this.#onResponse =
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
896
|
+
this.#onResponse = configuredOnResponse
|
|
897
|
+
? async (response, model) => {
|
|
898
|
+
this.rawSseDebugBuffer.recordResponse(response, model);
|
|
899
|
+
await configuredOnResponse(response, model);
|
|
900
|
+
}
|
|
901
|
+
: (response, model) => {
|
|
902
|
+
this.rawSseDebugBuffer.recordResponse(response, model);
|
|
903
|
+
};
|
|
888
904
|
const configuredOnSseEvent = config.onSseEvent;
|
|
889
|
-
this.#onSseEvent =
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
905
|
+
this.#onSseEvent = configuredOnSseEvent
|
|
906
|
+
? (event, model) => {
|
|
907
|
+
this.rawSseDebugBuffer.recordEvent(event, model);
|
|
908
|
+
configuredOnSseEvent(event, model);
|
|
909
|
+
}
|
|
910
|
+
: (event, model) => {
|
|
911
|
+
this.rawSseDebugBuffer.recordEvent(event, model);
|
|
912
|
+
};
|
|
893
913
|
this.agent.setProviderResponseInterceptor(this.#onResponse);
|
|
894
914
|
this.agent.setRawSseEventInterceptor(this.#onSseEvent);
|
|
895
915
|
this.#convertToLlm = config.convertToLlm ?? convertToLlm;
|
|
@@ -932,6 +952,8 @@ export class AgentSession {
|
|
|
932
952
|
this.#preCacheStreamingEditFile(event);
|
|
933
953
|
this.#maybeAbortStreamingEdit(event);
|
|
934
954
|
});
|
|
955
|
+
// Per-tool TTSR reminders are folded into the matched tool's result via this hook.
|
|
956
|
+
this.agent.afterToolCall = ctx => this.#ttsrAfterToolCall(ctx);
|
|
935
957
|
this.agent.providerSessionState = this.#providerSessionState;
|
|
936
958
|
this.#syncAgentSessionId();
|
|
937
959
|
this.#syncTodoPhasesFromBranch();
|
|
@@ -1325,77 +1347,87 @@ export class AgentSession {
|
|
|
1325
1347
|
if (matchContext && "delta" in assistantEvent) {
|
|
1326
1348
|
const matches = this.#ttsrManager.checkDelta(assistantEvent.delta, matchContext);
|
|
1327
1349
|
if (matches.length > 0) {
|
|
1328
|
-
//
|
|
1329
|
-
|
|
1330
|
-
this.#
|
|
1331
|
-
|
|
1332
|
-
if (
|
|
1333
|
-
|
|
1334
|
-
this.#ttsrAbortPending = true;
|
|
1335
|
-
this.#ensureTtsrResumePromise();
|
|
1336
|
-
this.agent.abort();
|
|
1337
|
-
// Notify extensions (fire-and-forget, does not block abort)
|
|
1350
|
+
// Decide first: a non-interrupting tool-source match attaches to the
|
|
1351
|
+
// specific tool call's result instead of driving a loop-wide follow-up.
|
|
1352
|
+
const shouldInterrupt = this.#shouldInterruptForTtsrMatch(matches, matchContext);
|
|
1353
|
+
const perToolId = shouldInterrupt ? undefined : this.#extractTtsrToolCallId(matchContext);
|
|
1354
|
+
if (perToolId) {
|
|
1355
|
+
this.#addPerToolTtsrInjections(perToolId, matches);
|
|
1338
1356
|
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1357
|
+
} else {
|
|
1358
|
+
// Queue rules for injection; mark as injected only after successful enqueue.
|
|
1359
|
+
this.#addPendingTtsrInjections(matches);
|
|
1360
|
+
|
|
1361
|
+
if (shouldInterrupt) {
|
|
1362
|
+
// Abort the stream immediately — do not gate on extension callbacks
|
|
1363
|
+
this.#ttsrAbortPending = true;
|
|
1364
|
+
this.#ensureTtsrResumePromise();
|
|
1365
|
+
this.agent.abort();
|
|
1366
|
+
// Notify extensions (fire-and-forget, does not block abort)
|
|
1367
|
+
this.#emitSessionEvent({ type: "ttsr_triggered", rules: matches }).catch(() => {});
|
|
1368
|
+
// Schedule retry after a short delay
|
|
1369
|
+
const retryToken = ++this.#ttsrRetryToken;
|
|
1370
|
+
const generation = this.#promptGeneration;
|
|
1371
|
+
const targetMessageTimestamp =
|
|
1372
|
+
event.message.role === "assistant" ? event.message.timestamp : undefined;
|
|
1373
|
+
this.#schedulePostPromptTask(
|
|
1374
|
+
async () => {
|
|
1375
|
+
if (this.#ttsrRetryToken !== retryToken) {
|
|
1376
|
+
this.#resolveTtsrResume();
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1350
1379
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1380
|
+
const targetAssistantIndex = this.#findTtsrAssistantIndex(targetMessageTimestamp);
|
|
1381
|
+
if (
|
|
1382
|
+
!this.#ttsrAbortPending ||
|
|
1383
|
+
this.#promptGeneration !== generation ||
|
|
1384
|
+
targetAssistantIndex === -1
|
|
1385
|
+
) {
|
|
1386
|
+
this.#ttsrAbortPending = false;
|
|
1387
|
+
this.#pendingTtsrInjections = [];
|
|
1388
|
+
this.#perToolTtsrInjections.clear();
|
|
1389
|
+
this.#resolveTtsrResume();
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1357
1392
|
this.#ttsrAbortPending = false;
|
|
1358
|
-
this.#
|
|
1359
|
-
this.#
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
{ delayMs: 50 },
|
|
1397
|
-
);
|
|
1398
|
-
return;
|
|
1393
|
+
this.#perToolTtsrInjections.clear();
|
|
1394
|
+
const ttsrSettings = this.#ttsrManager?.getSettings();
|
|
1395
|
+
if (ttsrSettings?.contextMode === "discard") {
|
|
1396
|
+
// Remove the partial/aborted assistant turn from agent state
|
|
1397
|
+
this.agent.replaceMessages(this.agent.state.messages.slice(0, targetAssistantIndex));
|
|
1398
|
+
}
|
|
1399
|
+
// Inject TTSR rules as system reminder before retry
|
|
1400
|
+
const injection = this.#getTtsrInjectionContent();
|
|
1401
|
+
if (injection) {
|
|
1402
|
+
const details = { rules: injection.rules.map(rule => rule.name) };
|
|
1403
|
+
this.agent.appendMessage({
|
|
1404
|
+
role: "custom",
|
|
1405
|
+
customType: "ttsr-injection",
|
|
1406
|
+
content: injection.content,
|
|
1407
|
+
display: false,
|
|
1408
|
+
details,
|
|
1409
|
+
attribution: "agent",
|
|
1410
|
+
timestamp: Date.now(),
|
|
1411
|
+
});
|
|
1412
|
+
this.sessionManager.appendCustomMessageEntry(
|
|
1413
|
+
"ttsr-injection",
|
|
1414
|
+
injection.content,
|
|
1415
|
+
false,
|
|
1416
|
+
details,
|
|
1417
|
+
"agent",
|
|
1418
|
+
);
|
|
1419
|
+
this.#markTtsrInjected(details.rules);
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
await this.agent.continue();
|
|
1423
|
+
} catch {
|
|
1424
|
+
this.#resolveTtsrResume();
|
|
1425
|
+
}
|
|
1426
|
+
},
|
|
1427
|
+
{ delayMs: 50 },
|
|
1428
|
+
);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1399
1431
|
}
|
|
1400
1432
|
}
|
|
1401
1433
|
}
|
|
@@ -1804,6 +1836,61 @@ export class AgentSession {
|
|
|
1804
1836
|
}
|
|
1805
1837
|
}
|
|
1806
1838
|
|
|
1839
|
+
/** Tool-call id whose argument deltas triggered a TTSR match, when known. */
|
|
1840
|
+
#extractTtsrToolCallId(matchContext: TtsrMatchContext): string | undefined {
|
|
1841
|
+
if (matchContext.source !== "tool") return undefined;
|
|
1842
|
+
const key = matchContext.streamKey;
|
|
1843
|
+
if (typeof key !== "string" || !key.startsWith("toolcall:")) return undefined;
|
|
1844
|
+
const id = key.slice("toolcall:".length);
|
|
1845
|
+
return id.length > 0 ? id : undefined;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
#addPerToolTtsrInjections(toolCallId: string, rules: Rule[]): void {
|
|
1849
|
+
const bucket = this.#perToolTtsrInjections.get(toolCallId) ?? [];
|
|
1850
|
+
const seen = new Set(bucket.map(rule => rule.name));
|
|
1851
|
+
// Dedupe against rules already bucketed for other tool calls in this
|
|
1852
|
+
// same assistant message so one rule attaches to exactly one tool call.
|
|
1853
|
+
const claimedElsewhere = new Set<string>();
|
|
1854
|
+
for (const [otherId, otherBucket] of this.#perToolTtsrInjections) {
|
|
1855
|
+
if (otherId === toolCallId) continue;
|
|
1856
|
+
for (const rule of otherBucket) claimedElsewhere.add(rule.name);
|
|
1857
|
+
}
|
|
1858
|
+
const newlyAdded: string[] = [];
|
|
1859
|
+
for (const rule of rules) {
|
|
1860
|
+
if (seen.has(rule.name) || claimedElsewhere.has(rule.name)) continue;
|
|
1861
|
+
bucket.push(rule);
|
|
1862
|
+
seen.add(rule.name);
|
|
1863
|
+
newlyAdded.push(rule.name);
|
|
1864
|
+
}
|
|
1865
|
+
if (bucket.length === 0) return;
|
|
1866
|
+
this.#perToolTtsrInjections.set(toolCallId, bucket);
|
|
1867
|
+
// Claim the rules in the TTSR manager so subsequent deltas in this same
|
|
1868
|
+
// turn (e.g. a sibling tool call's argument stream) don't re-match them.
|
|
1869
|
+
// Persistence still happens in #ttsrAfterToolCall when the tool actually
|
|
1870
|
+
// produces a result we can fold the reminder into.
|
|
1871
|
+
if (newlyAdded.length > 0) {
|
|
1872
|
+
this.#ttsrManager?.markInjectedByNames(newlyAdded);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/** `afterToolCall` hook: fold any per-tool TTSR reminders into the result. */
|
|
1877
|
+
#ttsrAfterToolCall(ctx: AfterToolCallContext): AfterToolCallResult | undefined {
|
|
1878
|
+
const rules = this.#perToolTtsrInjections.get(ctx.toolCall.id);
|
|
1879
|
+
if (!rules || rules.length === 0) return undefined;
|
|
1880
|
+
this.#perToolTtsrInjections.delete(ctx.toolCall.id);
|
|
1881
|
+
const reminder = rules
|
|
1882
|
+
.map(r => prompt.render(ttsrToolReminderTemplate, { name: r.name, path: r.path, content: r.content }))
|
|
1883
|
+
.join("\n\n");
|
|
1884
|
+
// The TTSR manager was already claimed at bucket time; only persistence remains.
|
|
1885
|
+
const ruleNames = rules.map(r => r.name.trim()).filter(n => n.length > 0);
|
|
1886
|
+
if (ruleNames.length > 0) {
|
|
1887
|
+
this.sessionManager.appendTtsrInjection(ruleNames);
|
|
1888
|
+
}
|
|
1889
|
+
return {
|
|
1890
|
+
content: [{ type: "text", text: reminder }, ...ctx.result.content],
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1807
1894
|
#extractTtsrRuleNames(details: unknown): string[] {
|
|
1808
1895
|
if (!details || typeof details !== "object" || Array.isArray(details)) {
|
|
1809
1896
|
return [];
|
|
@@ -1854,6 +1941,11 @@ export class AgentSession {
|
|
|
1854
1941
|
}
|
|
1855
1942
|
|
|
1856
1943
|
#queueDeferredTtsrInjectionIfNeeded(assistantMsg: AssistantMessage): void {
|
|
1944
|
+
if (assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error") {
|
|
1945
|
+
// Tools that hadn't started by abort/error will never produce results to
|
|
1946
|
+
// fold injections into — drop their stale per-tool entries.
|
|
1947
|
+
this.#perToolTtsrInjections.clear();
|
|
1948
|
+
}
|
|
1857
1949
|
if (this.#ttsrAbortPending || this.#pendingTtsrInjections.length === 0) {
|
|
1858
1950
|
return;
|
|
1859
1951
|
}
|
|
@@ -5249,6 +5341,7 @@ export class AgentSession {
|
|
|
5249
5341
|
convertToLlm,
|
|
5250
5342
|
initiatorOverride: "agent",
|
|
5251
5343
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
5344
|
+
telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
|
|
5252
5345
|
},
|
|
5253
5346
|
handoffSignal,
|
|
5254
5347
|
);
|
|
@@ -5961,6 +6054,7 @@ export class AgentSession {
|
|
|
5961
6054
|
options?: SummaryOptions,
|
|
5962
6055
|
): Promise<CompactionResult> {
|
|
5963
6056
|
const candidates = this.#getCompactionModelCandidates(this.#modelRegistry.getAvailable());
|
|
6057
|
+
const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
|
|
5964
6058
|
|
|
5965
6059
|
for (const candidate of candidates) {
|
|
5966
6060
|
const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
|
|
@@ -5971,6 +6065,7 @@ export class AgentSession {
|
|
|
5971
6065
|
...options,
|
|
5972
6066
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
5973
6067
|
convertToLlm,
|
|
6068
|
+
telemetry,
|
|
5974
6069
|
});
|
|
5975
6070
|
} catch (error) {
|
|
5976
6071
|
if (!this.#isCompactionAuthFailure(error)) {
|
|
@@ -6207,6 +6302,7 @@ export class AgentSession {
|
|
|
6207
6302
|
} else {
|
|
6208
6303
|
const candidates = this.#getCompactionModelCandidates(availableModels);
|
|
6209
6304
|
const retrySettings = this.settings.getGroup("retry");
|
|
6305
|
+
const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
|
|
6210
6306
|
let compactResult: CompactionResult | undefined;
|
|
6211
6307
|
let lastError: unknown;
|
|
6212
6308
|
|
|
@@ -6224,6 +6320,7 @@ export class AgentSession {
|
|
|
6224
6320
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
6225
6321
|
initiatorOverride: "agent",
|
|
6226
6322
|
convertToLlm,
|
|
6323
|
+
telemetry,
|
|
6227
6324
|
});
|
|
6228
6325
|
break;
|
|
6229
6326
|
} catch (error) {
|
|
@@ -7828,6 +7925,7 @@ export class AgentSession {
|
|
|
7828
7925
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
7829
7926
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
7830
7927
|
convertToLlm,
|
|
7928
|
+
telemetry: resolveTelemetry(this.agent.telemetry, this.sessionId),
|
|
7831
7929
|
});
|
|
7832
7930
|
this.#branchSummaryAbortController = undefined;
|
|
7833
7931
|
if (result.aborted) {
|
|
@@ -8063,11 +8161,12 @@ export class AgentSession {
|
|
|
8063
8161
|
};
|
|
8064
8162
|
}
|
|
8065
8163
|
|
|
8066
|
-
async fetchUsageReports(): Promise<UsageReport[] | null> {
|
|
8164
|
+
async fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null> {
|
|
8067
8165
|
const authStorage = this.#modelRegistry.authStorage;
|
|
8068
8166
|
if (!authStorage.fetchUsageReports) return null;
|
|
8069
8167
|
return authStorage.fetchUsageReports({
|
|
8070
8168
|
baseUrlResolver: provider => this.#modelRegistry.getProviderBaseUrl?.(provider),
|
|
8169
|
+
signal,
|
|
8071
8170
|
});
|
|
8072
8171
|
}
|
|
8073
8172
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Database, type Statement } from "bun:sqlite";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type AuthCredential,
|
|
6
|
+
type AuthCredentialStore,
|
|
7
|
+
SqliteAuthCredentialStore,
|
|
8
|
+
type StoredAuthCredential,
|
|
9
|
+
} from "@oh-my-pi/pi-ai";
|
|
5
10
|
import { getAgentDbPath, isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
6
11
|
import type { RawSettings as Settings } from "../config/settings";
|
|
7
12
|
|
|
@@ -57,7 +62,7 @@ export class AgentStorage {
|
|
|
57
62
|
this.#hardenPermissions(dbPath);
|
|
58
63
|
|
|
59
64
|
// Create AuthCredentialStore with our open database
|
|
60
|
-
this.#authStore = new
|
|
65
|
+
this.#authStore = new SqliteAuthCredentialStore(this.#db);
|
|
61
66
|
|
|
62
67
|
this.#listSettingsStmt = this.#db.prepare("SELECT key, value FROM settings");
|
|
63
68
|
this.#upsertModelUsageStmt = this.#db.prepare(
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve auth-broker connection configuration for the local omp client.
|
|
3
|
+
*
|
|
4
|
+
* Precedence (highest first):
|
|
5
|
+
* 1. `OMP_AUTH_BROKER_URL` / `OMP_AUTH_BROKER_TOKEN` env vars.
|
|
6
|
+
* 2. `auth.broker.url` / `auth.broker.token` in `~/.omp/agent/config.yml`
|
|
7
|
+
* (hidden from the settings UI; `!command` resolution supported).
|
|
8
|
+
* 3. Token file `~/.omp/auth-broker.token` (paired with URL from env or config).
|
|
9
|
+
*
|
|
10
|
+
* Returns null when no broker URL is configured — caller falls back to the
|
|
11
|
+
* local SQLite store.
|
|
12
|
+
*
|
|
13
|
+
* Reads config.yml directly (instead of going through `Settings.init`) because
|
|
14
|
+
* `discoverAuthStorage` runs before the settings singleton is initialized in
|
|
15
|
+
* `runRootCommand`, and we want hand-edited config entries to be honoured at
|
|
16
|
+
* boot without forcing a startup reorder.
|
|
17
|
+
*/
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { getAgentDir, getConfigRootDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
20
|
+
import { YAML } from "bun";
|
|
21
|
+
import { resolveConfigValue } from "../config/resolve-config-value";
|
|
22
|
+
|
|
23
|
+
export interface AuthBrokerClientConfig {
|
|
24
|
+
url: string;
|
|
25
|
+
token: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Path to the local bearer token file. Created on the broker host by `omp auth-broker token`. */
|
|
29
|
+
export function getAuthBrokerTokenFilePath(): string {
|
|
30
|
+
return path.join(getConfigRootDir(), "auth-broker.token");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readTokenFile(): Promise<string | null> {
|
|
34
|
+
try {
|
|
35
|
+
const raw = await Bun.file(getAuthBrokerTokenFilePath()).text();
|
|
36
|
+
const trimmed = raw.trim();
|
|
37
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (isEnoent(err)) return null;
|
|
40
|
+
logger.warn("auth-broker token file unreadable", { error: String(err) });
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ConfigSnapshot {
|
|
46
|
+
url?: string;
|
|
47
|
+
token?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readConfigYaml(): Promise<ConfigSnapshot> {
|
|
51
|
+
const configPath = path.join(getAgentDir(), "config.yml");
|
|
52
|
+
try {
|
|
53
|
+
const raw = await Bun.file(configPath).text();
|
|
54
|
+
const parsed = YAML.parse(raw);
|
|
55
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
56
|
+
const record = parsed as Record<string, unknown>;
|
|
57
|
+
const url = typeof record["auth.broker.url"] === "string" ? (record["auth.broker.url"] as string) : undefined;
|
|
58
|
+
const token =
|
|
59
|
+
typeof record["auth.broker.token"] === "string" ? (record["auth.broker.token"] as string) : undefined;
|
|
60
|
+
return { url, token };
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (isEnoent(err)) return {};
|
|
63
|
+
logger.warn("auth-broker config.yml unreadable", { error: String(err) });
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read broker configuration. Returns null when the URL is missing
|
|
70
|
+
* (broker disabled — local store is used). Throws when URL is set but no
|
|
71
|
+
* token is available — the caller cannot fall back silently because the
|
|
72
|
+
* user explicitly asked to use the broker.
|
|
73
|
+
*/
|
|
74
|
+
export async function resolveAuthBrokerConfig(): Promise<AuthBrokerClientConfig | null> {
|
|
75
|
+
const envUrl = process.env.OMP_AUTH_BROKER_URL;
|
|
76
|
+
const envToken = process.env.OMP_AUTH_BROKER_TOKEN;
|
|
77
|
+
|
|
78
|
+
let url = envUrl && envUrl.length > 0 ? envUrl : undefined;
|
|
79
|
+
let configToken: string | undefined;
|
|
80
|
+
if (!url || !envToken) {
|
|
81
|
+
const fromConfig = await readConfigYaml();
|
|
82
|
+
if (!url && fromConfig.url) {
|
|
83
|
+
const resolved = await resolveConfigValue(fromConfig.url);
|
|
84
|
+
if (resolved && resolved.length > 0) url = resolved;
|
|
85
|
+
}
|
|
86
|
+
if (fromConfig.token) {
|
|
87
|
+
const resolved = await resolveConfigValue(fromConfig.token);
|
|
88
|
+
if (resolved && resolved.length > 0) configToken = resolved;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!url) return null;
|
|
92
|
+
|
|
93
|
+
const token =
|
|
94
|
+
(envToken && envToken.length > 0 ? envToken : undefined) ?? configToken ?? (await readTokenFile()) ?? undefined;
|
|
95
|
+
if (!token) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`OMP_AUTH_BROKER_URL is set (${url}) but no bearer token is available. ` +
|
|
98
|
+
`Set OMP_AUTH_BROKER_TOKEN, the \`auth.broker.token\` config entry, or place one at ${getAuthBrokerTokenFilePath()}.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return { url, token };
|
|
102
|
+
}
|
|
@@ -14,4 +14,10 @@ export type {
|
|
|
14
14
|
SerializedAuthStorage,
|
|
15
15
|
StoredAuthCredential,
|
|
16
16
|
} from "@oh-my-pi/pi-ai";
|
|
17
|
-
export {
|
|
17
|
+
export {
|
|
18
|
+
AuthBrokerClient,
|
|
19
|
+
AuthStorage,
|
|
20
|
+
REMOTE_REFRESH_SENTINEL,
|
|
21
|
+
RemoteAuthCredentialStore,
|
|
22
|
+
SqliteAuthCredentialStore,
|
|
23
|
+
} from "@oh-my-pi/pi-ai";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import { sanitizeText } from "@oh-my-pi/pi-
|
|
2
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { formatBytes } from "../tools/render-utils";
|
|
4
4
|
import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
|
|
5
5
|
|