qlogicagent 2.17.10 → 2.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +16 -16
- package/dist/cli.js +1 -1
- package/dist/index.js +431 -423
- package/dist/skills/mcp/astraclaw-native-mcp-server.js +6 -5
- package/dist/types/agent/tool-loop.d.ts +6 -0
- package/dist/types/agent/types.d.ts +5 -0
- package/dist/types/cli/handlers/agents-handler.d.ts +9 -0
- package/dist/types/cli/handlers/config-handler.d.ts +5 -0
- package/dist/types/cli/handlers/files-handler.d.ts +7 -0
- package/dist/types/cli/handlers/session-handler.d.ts +43 -0
- package/dist/types/cli/handlers/turn-handler.d.ts +12 -0
- package/dist/types/cli/permission-rule-persistence.d.ts +16 -0
- package/dist/types/cli/stdio-rpc-handler-hosts.d.ts +4 -0
- package/dist/types/cli/stdio-server.d.ts +16 -1
- package/dist/types/cli/turn-core.d.ts +2 -0
- package/dist/types/orchestration/agent-instance.d.ts +4 -0
- package/dist/types/orchestration/delegation-coordinator.d.ts +10 -0
- package/dist/types/orchestration/product-persistence.d.ts +4 -0
- package/dist/types/protocol/wire/acp-agent-management.d.ts +16 -0
- package/dist/types/protocol/wire/agent-methods.d.ts +3 -0
- package/dist/types/runtime/infra/acp-detector.d.ts +35 -5
- package/dist/types/runtime/infra/acp-protocol-adapter.d.ts +14 -0
- package/dist/types/runtime/infra/agent-process.d.ts +15 -0
- package/dist/types/runtime/infra/llmrouter-catalog.d.ts +26 -6
- package/dist/types/runtime/infra/model-registry.d.ts +43 -2
- package/dist/types/runtime/infra/user-dirs.d.ts +21 -0
- package/dist/types/runtime/permission-model.d.ts +1 -1
- package/dist/types/runtime/ports/permission-contracts.d.ts +5 -2
- package/dist/types/runtime/prompt/fresh-workspace-evidence.d.ts +1 -2
- package/dist/types/runtime/prompt/identity-section.d.ts +1 -1
- package/dist/types/runtime/session/native/claude-session-source.d.ts +23 -0
- package/dist/types/runtime/session/native/codex-index.d.ts +21 -0
- package/dist/types/runtime/session/native/codex-session-source.d.ts +31 -0
- package/dist/types/runtime/session/native/cwd-normalize.d.ts +53 -0
- package/dist/types/runtime/session/native/ephemeral-rollout-sweep.d.ts +13 -0
- package/dist/types/runtime/session/native/native-cwd-agents.d.ts +17 -0
- package/dist/types/runtime/session/native/native-session-source.d.ts +89 -0
- package/dist/types/runtime/session/native/native-title.d.ts +1 -0
- package/dist/types/runtime/session/native/register-builtin-sources.d.ts +1 -0
- package/dist/types/runtime/session/session-types.d.ts +8 -0
- package/dist/types/skills/permissions/hook-runner.d.ts +9 -0
- package/dist/types/skills/permissions/settings-watcher.d.ts +7 -0
- package/dist/types/transport/acp-server.d.ts +6 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ModelCapabilityProfile, ModelEntry, ModelRegistry, ReasoningMode } from "./model-registry.js";
|
|
1
|
+
import type { ModelCapabilityProfile, ModelEntry, ModelPurpose, ModelRegistry, ReasoningMode } from "./model-registry.js";
|
|
2
2
|
import type { ConfigPort } from "../ports/index.js";
|
|
3
3
|
export declare const LLMROUTER_CATALOG_UNAVAILABLE_MESSAGE = "\u65E0\u6CD5\u8FDE\u63A5 llmrouter \u6A21\u578B\u76EE\u5F55";
|
|
4
4
|
export declare const LLMROUTER_PROVIDER_ID = "llmrouter";
|
|
@@ -97,14 +97,34 @@ export declare function refreshRegistryFromLlmrouterApiKey(registry: ModelRegist
|
|
|
97
97
|
forceCatalog?: boolean;
|
|
98
98
|
}): Promise<ModelEntry[]>;
|
|
99
99
|
export declare function resetLlmrouterCatalogClientCache(): void;
|
|
100
|
+
export interface DefaultBindingsResult {
|
|
101
|
+
/** 本次真正写入的绑定(用途 → modelId)。 */
|
|
102
|
+
applied: Array<{
|
|
103
|
+
purpose: ModelPurpose;
|
|
104
|
+
modelId: string;
|
|
105
|
+
}>;
|
|
106
|
+
/** 跳过的用途 + 原因(已有配置不覆盖 / 该用途 llmrouter 无可用模型)。 */
|
|
107
|
+
skipped: Array<{
|
|
108
|
+
purpose: ModelPurpose;
|
|
109
|
+
reason: "already-bound" | "no-model";
|
|
110
|
+
}>;
|
|
111
|
+
}
|
|
100
112
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
113
|
+
* 零配置默认绑定:给一组「安全」用途补上 llmrouter 默认模型 —— **只补空白**,已有 binding 的用途一律
|
|
114
|
+
* 不动(尊重用户/老配置)。每用途三级挑选:①服务端推荐(模型 capability_profile.default_for 声明本
|
|
115
|
+
* 用途)优先 → ②回退硬编码偏好子串(DEFAULT_BINDING_PREFERENCES,向后兼容) → ③再回退该用途第一个
|
|
116
|
+
* 可用模型;都没有则不绑。**「哪些用途该默认绑」是客户端产品边界(安全白名单),「每用途选哪个模型」
|
|
117
|
+
* 交服务端** —— 两层职责分离,llmrouter 换模型客户端零发版跟随。
|
|
104
118
|
*
|
|
105
|
-
*
|
|
119
|
+
* 触发场景:①agent 启动(设备账号推理 key 注入后,stdio-server) ②刷新目录后(settings refresh)
|
|
120
|
+
* ③手动添加 llmrouter key。"有没有 llmrouter 可用"由 listModels 结果隐含——refresh 成功(key 有效)
|
|
121
|
+
* 才有 llmrouter 模型条目;无则每个用途 no-model 跳过、不绑。key 不在此注册(ModelRegistry 取 llmrouter
|
|
122
|
+
* key 时回退到 QLOGIC_LLMROUTER_INFERENCE_KEY env 或用户 key)。调用方需先刷新过目录。
|
|
106
123
|
*/
|
|
107
|
-
export declare function
|
|
124
|
+
export declare function ensureLlmrouterDefaultBindings(registry: ModelRegistry, opts?: {
|
|
125
|
+
requireInferenceKey?: boolean;
|
|
126
|
+
configPort?: ConfigPort;
|
|
127
|
+
}): DefaultBindingsResult;
|
|
108
128
|
export declare function toModelEntry(model: LlmrouterCatalogModel): ModelEntry | null;
|
|
109
129
|
export declare function toModelEntries(model: LlmrouterCatalogModel): ModelEntry[];
|
|
110
130
|
export {};
|
|
@@ -29,6 +29,13 @@ export interface ModelEntry {
|
|
|
29
29
|
routeIntents: ModelPurpose[];
|
|
30
30
|
baseUrl?: string;
|
|
31
31
|
enabled: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Purposes this model has been individually turned OFF for. Absent/empty = on for all of its
|
|
34
|
+
* routeIntents (still subject to the master `enabled`). Lets one model serve several purposes
|
|
35
|
+
* with independent on/off switches, so disabling it under one purpose group never strands the
|
|
36
|
+
* bindings that use it for another.
|
|
37
|
+
*/
|
|
38
|
+
disabledPurposes?: ModelPurpose[];
|
|
32
39
|
nativeModelId?: string;
|
|
33
40
|
transport?: string;
|
|
34
41
|
contextWindow?: number;
|
|
@@ -40,6 +47,11 @@ export interface ModelEntry {
|
|
|
40
47
|
pricing?: Record<string, unknown>;
|
|
41
48
|
/** Thinking-strength control class for the UI (see ReasoningMode). */
|
|
42
49
|
reasoningMode?: ReasoningMode;
|
|
50
|
+
/**
|
|
51
|
+
* 归一后的「服务端推荐默认用途」(来自 capabilityProfile.default_for)。零配置默认绑定优先选
|
|
52
|
+
* defaultForIntents 命中本用途的模型,读不到则回退硬编码偏好。空/缺 → 无服务端推荐。
|
|
53
|
+
*/
|
|
54
|
+
defaultForIntents?: ModelPurpose[];
|
|
43
55
|
}
|
|
44
56
|
export interface ModelCapabilityProfile {
|
|
45
57
|
tasks?: string[];
|
|
@@ -55,6 +67,12 @@ export interface ModelCapabilityProfile {
|
|
|
55
67
|
requires_confirmation?: boolean;
|
|
56
68
|
default_selectable?: boolean;
|
|
57
69
|
};
|
|
70
|
+
/**
|
|
71
|
+
* 服务端声明:该模型是这些 route_intent 的「推荐默认」(零配置绑定优先读它)。原始 intent 字符串
|
|
72
|
+
* (由 llmrouter 归一为规范名);客户端过滤到已知 ModelPurpose 得 defaultForIntents。空/缺 → 无服务端
|
|
73
|
+
* 推荐 → 客户端回退硬编码偏好(向后兼容:llmrouter 未部署 default_for 时行为与现状完全一致)。
|
|
74
|
+
*/
|
|
75
|
+
default_for?: string[];
|
|
58
76
|
}
|
|
59
77
|
export type PurposeBindings = Partial<Record<ModelPurpose, string>>;
|
|
60
78
|
export interface ModelRegistryConfig {
|
|
@@ -62,6 +80,7 @@ export interface ModelRegistryConfig {
|
|
|
62
80
|
models: Array<{
|
|
63
81
|
id: string;
|
|
64
82
|
enabled?: boolean;
|
|
83
|
+
disabledPurposes?: ModelPurpose[];
|
|
65
84
|
}>;
|
|
66
85
|
bindings: PurposeBindings;
|
|
67
86
|
tunables?: Record<string, unknown>;
|
|
@@ -84,6 +103,7 @@ export declare class ModelRegistry {
|
|
|
84
103
|
private catalogProviders;
|
|
85
104
|
private models;
|
|
86
105
|
private modelEnabledOverrides;
|
|
106
|
+
private modelPurposeDisabled;
|
|
87
107
|
private bindings;
|
|
88
108
|
private injectedKeyStates;
|
|
89
109
|
private settingsPath;
|
|
@@ -106,6 +126,12 @@ export declare class ModelRegistry {
|
|
|
106
126
|
*/
|
|
107
127
|
acceptsInputModality(modality: string, purpose?: ModelPurpose): boolean;
|
|
108
128
|
isAvailable(purpose: ModelPurpose): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Whether `id` is enabled for `purpose`. The master `enabled` flag is the global gate; on top of it
|
|
131
|
+
* a single purpose can be switched off (modelPurposeDisabled) without touching the model's other
|
|
132
|
+
* purposes. This is what keeps "disable in 文本生成" from stranding the 图像理解 binding.
|
|
133
|
+
*/
|
|
134
|
+
isEnabledForPurpose(id: string, purpose: ModelPurpose): boolean;
|
|
109
135
|
resolveModelForPurpose(purpose: ModelPurpose): string | null;
|
|
110
136
|
addProvider(providerId: string, opts?: {
|
|
111
137
|
baseUrl?: string;
|
|
@@ -132,8 +158,23 @@ export declare class ModelRegistry {
|
|
|
132
158
|
replaceCatalogModels(entries: ModelEntry[]): void;
|
|
133
159
|
replaceProviderModels(providerId: string, entries: ModelEntry[]): void;
|
|
134
160
|
removeModel(id: string): void;
|
|
135
|
-
|
|
136
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Enable a model. With `purpose`, enables it for just that purpose (clearing its per-purpose OFF
|
|
163
|
+
* switch); if the whole model was off, it flips on but keeps its OTHER purposes off so a single
|
|
164
|
+
* "turn on imageUnderstanding" never silently re-enables textGeneration too. Without `purpose`,
|
|
165
|
+
* enables the model globally (on for all of its purposes).
|
|
166
|
+
*/
|
|
167
|
+
enableModel(id: string, purpose?: ModelPurpose): void;
|
|
168
|
+
/**
|
|
169
|
+
* Disable a model. With `purpose`, turns it off for just that purpose; the master flag only flips
|
|
170
|
+
* off once EVERY one of its routeIntents is off, so purpose-agnostic consumers (default-model
|
|
171
|
+
* pickers) still see the model while any purpose stays live. Without `purpose`, disables globally.
|
|
172
|
+
*/
|
|
173
|
+
disableModel(id: string, purpose?: ModelPurpose): void;
|
|
174
|
+
private isMasterEnabled;
|
|
175
|
+
private setMasterEnabled;
|
|
176
|
+
private setPurposeDisabled;
|
|
177
|
+
private purposeDisabledArray;
|
|
137
178
|
listModels(filter?: {
|
|
138
179
|
purpose?: ModelPurpose;
|
|
139
180
|
provider?: string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 跨平台获取用户常见目录(桌面/下载/文档)的真实绝对路径,供 environment-context 注入给 agent——
|
|
3
|
+
* 让「读桌面/下载的文件」这类桌面端最常见请求有一个确定的绝对路径锚点,而不是相对会话工作区瞎找。
|
|
4
|
+
*
|
|
5
|
+
* 各平台权威来源(都不能简单 homedir + "/Desktop" 硬拼——OneDrive/自定义重定向会漏):
|
|
6
|
+
* - Windows: 注册表 User Shell Folders(含 OneDrive/自定义重定向);值是 REG_EXPAND_SZ,含 %VAR% 需展开。
|
|
7
|
+
* 展开用 node 的 process.env(编码正确),所以即便用户名是中文,%USERPROFILE% 模板展开后仍准;
|
|
8
|
+
* 只有用户把桌面手动重定向到硬编码中文路径时,reg 的 OEM 输出可能乱码 → existsSync 兜底回退。
|
|
9
|
+
* - macOS: ~/Desktop 等固定英文路径(Finder 里的本地化名只是显示,真实路径是英文)。
|
|
10
|
+
* - Linux: XDG user-dirs(~/.config/user-dirs.dirs 的 XDG_DESKTOP_DIR 等),回退 ~/Desktop。
|
|
11
|
+
* 一次性探测 + 缓存(目录在进程生命周期内不变)。任何一项失败都回退到 homedir 拼接,绝不抛。
|
|
12
|
+
*/
|
|
13
|
+
export interface UserDirs {
|
|
14
|
+
desktop: string;
|
|
15
|
+
downloads: string;
|
|
16
|
+
documents: string;
|
|
17
|
+
}
|
|
18
|
+
/** 用户桌面/下载/文档的真实绝对路径(缓存)。失败项回退 homedir 拼接,绝不抛。 */
|
|
19
|
+
export declare function getUserDirs(): UserDirs;
|
|
20
|
+
/** 仅测试用:清缓存,让不同平台/环境的断言各自重探。 */
|
|
21
|
+
export declare function resetUserDirsCache(): void;
|
|
@@ -3,7 +3,7 @@ export interface PermissionPromptOption {
|
|
|
3
3
|
optionId: string;
|
|
4
4
|
name: string;
|
|
5
5
|
kind: "allow_once" | "allow_always" | "reject_once" | "reject_always";
|
|
6
|
-
scope?: "once" | "directory";
|
|
6
|
+
scope?: "once" | "directory" | "directory_persistent";
|
|
7
7
|
}
|
|
8
8
|
export interface PermissionModeDescriptor {
|
|
9
9
|
id: PermissionMode;
|
|
@@ -105,8 +105,11 @@ export interface ApprovalResponse {
|
|
|
105
105
|
approvalId: string;
|
|
106
106
|
decision: "allow" | "deny";
|
|
107
107
|
updatedInput?: Record<string, unknown>;
|
|
108
|
-
/**
|
|
109
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Approval scope chosen by the user: just this call, the whole directory for
|
|
110
|
+
* this session, or the whole directory persisted across restarts.
|
|
111
|
+
*/
|
|
112
|
+
scope?: "once" | "directory" | "directory_persistent";
|
|
110
113
|
}
|
|
111
114
|
export interface PermissionRuleEnginePort {
|
|
112
115
|
getMode(): PermissionMode;
|
|
@@ -11,9 +11,8 @@ export interface ToolNameLike {
|
|
|
11
11
|
}
|
|
12
12
|
export interface FreshWorkspaceEvidencePolicy {
|
|
13
13
|
requiresFreshTool: boolean;
|
|
14
|
-
reason?: "file-read" | "
|
|
14
|
+
reason?: "file-read" | "memory-mutation" | "media-generation" | "directory-list" | "content-search" | "shell" | "web";
|
|
15
15
|
mediaKind?: "image" | "tts" | "music";
|
|
16
|
-
workflowKind?: "artifact-index";
|
|
17
16
|
allowedToolNames: string[];
|
|
18
17
|
}
|
|
19
18
|
export declare function getFreshWorkspaceEvidencePolicy(messages: readonly PromptMessageLike[] | string): FreshWorkspaceEvidencePolicy;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { NativeSessionEntry, NativeSessionRef, NativeSessionSource, TranscriptMessage } from "./native-session-source.js";
|
|
2
|
+
/**
|
|
3
|
+
* Native session source for Claude Code (claude CLI). Reads directly from
|
|
4
|
+
* `~/.claude/projects/<claudeProjectDirName(cwd)>/<sessionUuid>.jsonl` — the same on-disk format
|
|
5
|
+
* Claude Code itself writes, so this is read-only and never touched by qlogicagent.
|
|
6
|
+
*
|
|
7
|
+
* `isSidechain: true` events are subagent side-chains, not part of the user-visible conversation,
|
|
8
|
+
* and are filtered out of both list() (message counting / title / lastActiveAt) and
|
|
9
|
+
* loadTranscript() (transcript body).
|
|
10
|
+
*/
|
|
11
|
+
export declare class ClaudeSessionSource implements NativeSessionSource {
|
|
12
|
+
private readonly home;
|
|
13
|
+
readonly agentId = "claude";
|
|
14
|
+
readonly kind: "claude";
|
|
15
|
+
/** `home` is injectable so tests can point at a temp directory instead of the real user home. */
|
|
16
|
+
constructor(home?: string);
|
|
17
|
+
list(cwd: string, opts?: {
|
|
18
|
+
limit?: number;
|
|
19
|
+
}): Promise<NativeSessionEntry[]>;
|
|
20
|
+
/** A claude native session is exactly the file `<encoded-cwd>/<sessionUuid>.jsonl` — a single stat. */
|
|
21
|
+
has(sessionId: string, cwd: string): Promise<boolean>;
|
|
22
|
+
loadTranscript(ref: NativeSessionRef): Promise<TranscriptMessage[]>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NativeSessionEntry } from "./native-session-source.js";
|
|
2
|
+
/**
|
|
3
|
+
* Lists codex-native sessions belonging to `normalizedTargetCwd` (already run through
|
|
4
|
+
* {@link normalizeCwd} by the caller), sorted by `lastActiveAt` descending.
|
|
5
|
+
*
|
|
6
|
+
* Synchronous by design — built entirely on sync fs calls, since it's a Cut 3 building block; a
|
|
7
|
+
* future CodexSessionSource adapter (mirroring claude-session-source.ts's shape) would wrap this in
|
|
8
|
+
* the async `NativeSessionSource.list()` contract.
|
|
9
|
+
*
|
|
10
|
+
* `messageCount` is always 0: unlike claude's one-line-per-message transcript format, a codex
|
|
11
|
+
* rollout interleaves many event types per turn, so computing an exact count means fully parsing
|
|
12
|
+
* every candidate rollout's entire event stream — deliberately out of scope here. Revisit if/when a
|
|
13
|
+
* caller actually needs an exact count.
|
|
14
|
+
*
|
|
15
|
+
* Never throws: a missing/unreadable codexHome, sessions dir, session_index.jsonl, or cache file all
|
|
16
|
+
* degrade to "as if empty" rather than propagating an error.
|
|
17
|
+
*/
|
|
18
|
+
export declare function listCodexSessionsByCwd(normalizedTargetCwd: string, opts?: {
|
|
19
|
+
codexHome?: string;
|
|
20
|
+
cacheDir?: string;
|
|
21
|
+
}): NativeSessionEntry[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NativeSessionEntry, NativeSessionRef, NativeSessionSource, TranscriptMessage } from "./native-session-source.js";
|
|
2
|
+
/**
|
|
3
|
+
* Native session source for Codex (codex CLI / codex_cli_rs) — Cut 3 Task 3.2.
|
|
4
|
+
*
|
|
5
|
+
* `list()` delegates entirely to {@link listCodexSessionsByCwd} (Task 3.1), which already handles
|
|
6
|
+
* cwd filtering, title/lastActiveAt joining against session_index.jsonl, and mtime-keyed caching;
|
|
7
|
+
* this adapter only adds the async `NativeSessionSource.list()` contract and cwd normalization on
|
|
8
|
+
* top of that synchronous building block (mirroring ClaudeSessionSource's shape).
|
|
9
|
+
*
|
|
10
|
+
* `loadTranscript()` reads a single rollout file and keeps only `event_msg` lines whose payload is
|
|
11
|
+
* `user_message` or `agent_message` with `phase: "final_answer"` — see {@link CodexEventMsgLine}'s
|
|
12
|
+
* doc for why every other line/event/payload type in a rollout is intentionally skipped.
|
|
13
|
+
*/
|
|
14
|
+
export declare class CodexSessionSource implements NativeSessionSource {
|
|
15
|
+
private readonly opts;
|
|
16
|
+
readonly agentId = "codex";
|
|
17
|
+
readonly kind: "codex";
|
|
18
|
+
/** `opts` is injectable (codexHome/cacheDir) so tests can point at temp directories instead of
|
|
19
|
+
* the real user home — mirrors listCodexSessionsByCwd's own opts shape. */
|
|
20
|
+
constructor(opts?: {
|
|
21
|
+
codexHome?: string;
|
|
22
|
+
cacheDir?: string;
|
|
23
|
+
});
|
|
24
|
+
list(cwd: string, opts?: {
|
|
25
|
+
limit?: number;
|
|
26
|
+
}): Promise<NativeSessionEntry[]>;
|
|
27
|
+
/** Codex has no id->file mapping without the index (id lives in each rollout's session_meta), so
|
|
28
|
+
* membership goes through the mtime-cached listCodexSessionsByCwd (no full re-scan when warm). */
|
|
29
|
+
has(sessionId: string, cwd: string): Promise<boolean>;
|
|
30
|
+
loadTranscript(ref: NativeSessionRef): Promise<TranscriptMessage[]>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a cwd for CODEX session-cwd equality comparisons (matching a codex rollout's
|
|
3
|
+
* `session_meta.cwd` against our own workspace cwd). Same rule in spirit as projectId derivation
|
|
4
|
+
* (see agent-paths.ts `deriveProjectId`: resolve + lowercase) — resolve to an absolute path, then
|
|
5
|
+
* lowercase and force forward-slash separators so both `E:\Foo\Bar` and `e:/foo/bar` compare equal.
|
|
6
|
+
* (path.resolve() already normalizes `/` to `\` on win32, so the explicit slash replace below is
|
|
7
|
+
* belt-and-suspenders rather than load-bearing — verified via `path.resolve("e:/xiaozhiclaw/openclaw")`
|
|
8
|
+
* === `"e:\\xiaozhiclaw\\openclaw"` on this machine — but kept explicit so the output format doesn't
|
|
9
|
+
* silently depend on that internal, unstated Node behavior.)
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeCwd(cwd: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Encode a cwd into the directory name Claude Code uses under `~/.claude/projects/<encoded>`.
|
|
14
|
+
*
|
|
15
|
+
* This is Claude Code's OWN encoding (not ours) — we must replicate it byte-for-byte to locate
|
|
16
|
+
* existing native claude session directories on disk. It has no public spec, so this was reverse
|
|
17
|
+
* engineered two ways and cross-checked against each other:
|
|
18
|
+
*
|
|
19
|
+
* 1. Real directory names from `ls ~/.claude/projects` on this dev machine (see
|
|
20
|
+
* cwd-normalize.test.ts for the samples, notably a workspace with a literal space in its name
|
|
21
|
+
* — "QA Agent Mention Pool 20260701-1325" — which is what falsified the initially-assumed
|
|
22
|
+
* narrower rule).
|
|
23
|
+
* 2. Decompiling the bundled VS Code extension (anthropic.claude-code-2.1.168,
|
|
24
|
+
* extension/extension.js). The relevant minified functions (renamed here for readability;
|
|
25
|
+
* originals were Hv/yv/pOe/cOe):
|
|
26
|
+
*
|
|
27
|
+
* const MAX_LEN = 200;
|
|
28
|
+
* function hashCode(e) { // Java String.hashCode()-style,
|
|
29
|
+
* let t = 0; // 32-bit signed, wraps via `|0`
|
|
30
|
+
* for (let r = 0; r < e.length; r++) t = (t << 5) - t + e.charCodeAt(r) | 0;
|
|
31
|
+
* return t;
|
|
32
|
+
* }
|
|
33
|
+
* function encodeProjectDirName(e) {
|
|
34
|
+
* const t = e.replace(/[^a-zA-Z0-9]/g, "-");
|
|
35
|
+
* if (t.length <= MAX_LEN) return t;
|
|
36
|
+
* return `${t.slice(0, MAX_LEN)}-${Math.abs(hashCode(e)).toString(36)}`;
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* Two things this rule does that a naive "just swap \\/:. for -" guess would get wrong:
|
|
40
|
+
* - The replacement set is "everything that isn't [a-zA-Z0-9]", not a fixed 4-character set —
|
|
41
|
+
* spaces, underscores, parens, plus signs, and non-ASCII (e.g. Chinese) characters are all
|
|
42
|
+
* replaced too. Original casing is preserved (no lowercasing, unlike normalizeCwd above).
|
|
43
|
+
* - Encoded names longer than 200 characters are truncated to 200 chars plus a
|
|
44
|
+
* "-<base36 32-bit hash>" suffix, where the hash is computed over the ORIGINAL
|
|
45
|
+
* (pre-replacement) string, not the truncated one.
|
|
46
|
+
*
|
|
47
|
+
* This encoding is lossy/one-way by construction (e.g. `...\\qlogicagent-hub` and
|
|
48
|
+
* `...\\qlogicagent\\hub` both encode to the same name) — it can only be used to look up a
|
|
49
|
+
* directory for a known cwd, never to recover a cwd from a directory name. If Claude Code changes
|
|
50
|
+
* this encoding in a future version, update this function and the fixtures in
|
|
51
|
+
* cwd-normalize.test.ts together.
|
|
52
|
+
*/
|
|
53
|
+
export declare function claudeProjectDirName(cwd: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sweep both native stores. Best-effort: never throws (all I/O is guarded); returns how many rollouts were
|
|
3
|
+
* reclaimed. `home`/`codexHome`/`ttlMs`/`now` are injectable for tests.
|
|
4
|
+
*/
|
|
5
|
+
export declare function sweepEphemeralWorktreeRollouts(opts?: {
|
|
6
|
+
home?: string;
|
|
7
|
+
codexHome?: string;
|
|
8
|
+
ttlMs?: number;
|
|
9
|
+
now?: number;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
claudeDeleted: number;
|
|
12
|
+
codexDeleted: number;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for WHICH third-party ACP backend ids have local, cwd-discoverable
|
|
3
|
+
* native session history ("native-cwd" adapters registered in register-builtin-sources.ts).
|
|
4
|
+
*
|
|
5
|
+
* Consumed by both:
|
|
6
|
+
* - acp-detector.ts (assembleBackendDescriptor) — descriptor's capabilities.sessionHistory
|
|
7
|
+
* - session-handler.ts (resolveAgentSessionHistory) — session.list routing
|
|
8
|
+
*
|
|
9
|
+
* so the two can never drift on which ids are native-adapted (the DRY debt Cut 1 intentionally
|
|
10
|
+
* left behind — see Cut 3 Task 3.0). To add or remove a native-cwd agent: change this set AND
|
|
11
|
+
* register/unregister the matching NativeSessionSource adapter in register-builtin-sources.ts.
|
|
12
|
+
*
|
|
13
|
+
* Zero imports by design — this file must stay a dependency-free leaf so both acp-detector.ts and
|
|
14
|
+
* session-handler.ts (which itself imports ACP_BACKENDS from acp-detector.ts) can import it without
|
|
15
|
+
* risking an import cycle.
|
|
16
|
+
*/
|
|
17
|
+
export declare const NATIVE_CWD_BACKEND_IDS: ReadonlySet<string>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native session source registry.
|
|
3
|
+
*
|
|
4
|
+
* A "native" session source lets a third-party ACP agent (codex/claude) expose ITS OWN
|
|
5
|
+
* CWD-scoped session history — recorded by the agent's own CLI, not by qlogicagent — through
|
|
6
|
+
* the same session.list / session.get surface as our own sessions.
|
|
7
|
+
*
|
|
8
|
+
* Cut 1 shipped only the registry contract with a loose shape; no adapter registered itself, so
|
|
9
|
+
* {@link getNativeSessionSource} always returned undefined and callers (session-handler's
|
|
10
|
+
* resolveSessionSource) fell back to an empty list for native-cwd agents. Cut 2 refines the
|
|
11
|
+
* contract below and adds the claude adapter (see claude-session-source.ts) that calls
|
|
12
|
+
* {@link registerNativeSessionSource}.
|
|
13
|
+
*/
|
|
14
|
+
/** Opaque locator for a single native session's transcript, round-tripped by the frontend without
|
|
15
|
+
* interpretation (session.list returns it, session.loadNativeTranscript consumes it). */
|
|
16
|
+
export interface NativeSessionRef {
|
|
17
|
+
agentId: string;
|
|
18
|
+
kind: "codex" | "claude";
|
|
19
|
+
/** Absolute path to the on-disk transcript file (adapter-specific format). */
|
|
20
|
+
file: string;
|
|
21
|
+
}
|
|
22
|
+
/** One native session list entry — the `source: "native"` variant of session.list's
|
|
23
|
+
* SessionListEntry (see session-types.ts SessionListEntry for the "own" counterpart). */
|
|
24
|
+
export interface NativeSessionEntry {
|
|
25
|
+
sessionId: string;
|
|
26
|
+
source: "native";
|
|
27
|
+
agentId: string;
|
|
28
|
+
title?: string;
|
|
29
|
+
lastActiveAt: string;
|
|
30
|
+
messageCount: number;
|
|
31
|
+
cwd: string;
|
|
32
|
+
nativeRef: NativeSessionRef;
|
|
33
|
+
/** Stamped by handleSessionList from the requested project so the frontend's per-project grouping
|
|
34
|
+
* (project-tree filters by projectId) shows native entries under it. The source discovers by cwd
|
|
35
|
+
* and cannot know the project, so it leaves this unset. */
|
|
36
|
+
projectId?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Minimal transcript-line shape for native session.loadTranscript. This intentionally does NOT
|
|
40
|
+
* import the frontend's `InboundMessagePayload` (ui-next/src/types/gateway.ts) — that type lives
|
|
41
|
+
* in a separate package/repo from this engine and carries many fields irrelevant to a bare
|
|
42
|
+
* transcript line (sessionKey, blocks, attachments, mediaUrl, chatId, ...). Instead this is the
|
|
43
|
+
* minimal subset every native adapter can populate, documented field-for-field against the
|
|
44
|
+
* frontend contract so a caller can widen a TranscriptMessage into an InboundMessagePayload by
|
|
45
|
+
* filling in the rest as optional/absent:
|
|
46
|
+
* - messageId -> InboundMessagePayload.messageId (required)
|
|
47
|
+
* - content -> InboundMessagePayload.content (required)
|
|
48
|
+
* - timestamp -> InboundMessagePayload.timestamp (required)
|
|
49
|
+
* - source -> InboundMessagePayload.source (required, narrowed here to "user"|"assistant"
|
|
50
|
+
* since that's all a transcript line can be)
|
|
51
|
+
* - platform -> InboundMessagePayload.platform (required there; optional here since every
|
|
52
|
+
* current adapter always sets it to "desktop" — kept optional so future adapters
|
|
53
|
+
* aren't forced to fabricate a value)
|
|
54
|
+
*/
|
|
55
|
+
export interface TranscriptMessage {
|
|
56
|
+
messageId: string;
|
|
57
|
+
content: string;
|
|
58
|
+
timestamp: string;
|
|
59
|
+
source: "user" | "assistant";
|
|
60
|
+
platform?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface NativeSessionSource {
|
|
63
|
+
readonly agentId: string;
|
|
64
|
+
readonly kind: "codex" | "claude";
|
|
65
|
+
/** `cwd` is the RAW (un-normalized) project cwd — callers (session-handler's
|
|
66
|
+
* resolveSessionSource, via handleSessionList) pass the REQUESTED project's workspaceDir
|
|
67
|
+
* (params.projectId's, when present) straight through, falling back to getActiveProjectRoot()
|
|
68
|
+
* only when no projectId was given. Each adapter is responsible for whatever
|
|
69
|
+
* normalization/encoding its own on-disk storage requires (e.g. claude's
|
|
70
|
+
* claudeProjectDirName preserves original casing). */
|
|
71
|
+
list(cwd: string, opts?: {
|
|
72
|
+
limit?: number;
|
|
73
|
+
}): Promise<NativeSessionEntry[]>;
|
|
74
|
+
/** Cheap existence check: is `sessionId` an existing native session under `cwd` for this agent?
|
|
75
|
+
* Used on a COLD spawn to decide whether to resume it (ACP session/load) instead of creating a
|
|
76
|
+
* fresh session — so a native conversation regains its authoritative history after pool eviction/
|
|
77
|
+
* restart, not only on the one-shot "继续". Optional so test fakes need not implement it. */
|
|
78
|
+
has?(sessionId: string, cwd: string): Promise<boolean>;
|
|
79
|
+
loadTranscript(ref: NativeSessionRef): Promise<TranscriptMessage[]>;
|
|
80
|
+
}
|
|
81
|
+
/** Register a native session source for an agentId. Last registration for a given id wins. */
|
|
82
|
+
export declare function registerNativeSessionSource(source: NativeSessionSource): void;
|
|
83
|
+
/** Look up a registered native session source by agentId. Undefined when none is registered for
|
|
84
|
+
* that id (no adapter registered itself yet, or the agentId has no native adapter at all). */
|
|
85
|
+
export declare function getNativeSessionSource(agentId?: string): NativeSessionSource | undefined;
|
|
86
|
+
/** Remove a registered native session source by agentId. Symmetric with {@link registerNativeSessionSource};
|
|
87
|
+
* exists primarily so tests can deregister a fake adapter afterward without leaking it into other
|
|
88
|
+
* test cases that share this module-level registry. No-op if nothing is registered for that id. */
|
|
89
|
+
export declare function unregisterNativeSessionSource(agentId: string): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cleanNativeTitle(message: string, maxLen?: number): string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerBuiltinNativeSources(): void;
|
|
@@ -65,6 +65,14 @@ export interface MessageDisplayMetadata {
|
|
|
65
65
|
chatId?: string;
|
|
66
66
|
userId?: string;
|
|
67
67
|
userName?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Presentation kind for a persisted message whose ROLE alone doesn't capture how
|
|
70
|
+
* the UI should render it. Currently: "mid_turn_guidance" — a user-authored message
|
|
71
|
+
* injected into a running turn (运行中引导). It stays role:"user" (the model must see
|
|
72
|
+
* it as the user's words), but the desktop renders it as an agent-side "收到引导"
|
|
73
|
+
* receipt line rather than a standalone user bubble.
|
|
74
|
+
*/
|
|
75
|
+
kind?: string;
|
|
68
76
|
}
|
|
69
77
|
export interface PersistedChatMessage extends ChatMessage {
|
|
70
78
|
timestamp?: string;
|
|
@@ -43,6 +43,14 @@ export interface PermissionCheckerDeps {
|
|
|
43
43
|
sessionId?: string;
|
|
44
44
|
/** Getter for current turn ID (changes per turn). */
|
|
45
45
|
getTurnId?: () => string;
|
|
46
|
+
/**
|
|
47
|
+
* Persist a directory allow-rule across restarts (user picked "始终允许此文件夹").
|
|
48
|
+
* Wired by the CLI layer to append into the user-level settings.json; optional so
|
|
49
|
+
* headless/test checkers degrade to session-only memory.
|
|
50
|
+
*/
|
|
51
|
+
persistPermissionRule?: (rule: {
|
|
52
|
+
pathPrefix: string;
|
|
53
|
+
}) => void | Promise<void>;
|
|
46
54
|
}
|
|
47
55
|
export declare class PermissionChecker {
|
|
48
56
|
private readonly ruleEngine;
|
|
@@ -57,6 +65,7 @@ export declare class PermissionChecker {
|
|
|
57
65
|
private readonly auditLogger;
|
|
58
66
|
private readonly sessionId;
|
|
59
67
|
private readonly getTurnId;
|
|
68
|
+
private readonly persistPermissionRule;
|
|
60
69
|
private unregisterHook;
|
|
61
70
|
/** Tool meta cache — populated from ToolDefinition[] at agent creation */
|
|
62
71
|
private toolMetaCache;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings watcher for canonical qlogicagent permission settings.
|
|
3
3
|
*
|
|
4
|
+
* Two layers (CC settings semantics):
|
|
5
|
+
* - user level ~/.qlogicagent/profiles/<owner>/settings.json — where config.update persists
|
|
6
|
+
* - project level <projectRoot>/.qlogicagent/settings.json — repo-local overrides
|
|
7
|
+
* The project file's `permissions` object, when present, overrides the user file's.
|
|
8
|
+
* Both layers are watched; either changing re-resolves the effective permissions.
|
|
9
|
+
* (写读同源:此前 config.update 写用户级、watcher 只读项目级,重启后持久模式永远丢失。)
|
|
10
|
+
*
|
|
4
11
|
* Only the `permissions` object is accepted. Removed top-level aliases such as
|
|
5
12
|
* `permissionMode`, `permissionRules`, and `defaultBehavior` must fail loudly.
|
|
6
13
|
*/
|
|
@@ -154,6 +154,12 @@ export declare class AcpServer {
|
|
|
154
154
|
* Returns a promise that resolves with the host's chosen option.
|
|
155
155
|
*/
|
|
156
156
|
requestPermission(params: AcpPermissionRequestParams): Promise<AcpPermissionRequestResult>;
|
|
157
|
+
/**
|
|
158
|
+
* Cancel every in-flight permission request (turn abort / session cancel). Each
|
|
159
|
+
* awaiter resolves to "deny" and its maps are cleared, so a cancelled turn's
|
|
160
|
+
* pending approval can't survive to poison the next turn.
|
|
161
|
+
*/
|
|
162
|
+
cancelAllPendingPermissions(): void;
|
|
157
163
|
/** Whether the host supports extended events */
|
|
158
164
|
get supportsExtendedEvents(): boolean;
|
|
159
165
|
/** Whether the host supports extended methods */
|