qlogicagent 2.17.8 → 2.17.10

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.
Files changed (47) hide show
  1. package/dist/agent.js +21 -21
  2. package/dist/cli.js +1 -1566
  3. package/dist/index.js +419 -403
  4. package/dist/orchestration.js +7 -7
  5. package/dist/protocol.js +1 -1
  6. package/dist/skills/builtin/desktop-screenshot/SKILL.md +62 -0
  7. package/dist/skills/builtin/office-doc-reading/SKILL.md +19 -0
  8. package/dist/skills/mcp/astraclaw-native-mcp-server.js +6 -6
  9. package/dist/types/agent/tool-loop/artifact-final-contract.d.ts +18 -0
  10. package/dist/types/agent/tool-loop/loop-helpers.d.ts +9 -0
  11. package/dist/types/agent/tool-loop.d.ts +8 -5
  12. package/dist/types/cli/acp-session-handlers.d.ts +8 -1
  13. package/dist/types/cli/acp-session-host.d.ts +5 -1
  14. package/dist/types/cli/default-project-bootstrap.d.ts +33 -0
  15. package/dist/types/cli/handlers/session-handler.d.ts +6 -0
  16. package/dist/types/cli/handlers/turn-handler.d.ts +65 -0
  17. package/dist/types/cli/media-understanding.d.ts +45 -0
  18. package/dist/types/cli/session-context.d.ts +5 -1
  19. package/dist/types/cli/stdio-acp-request-host.d.ts +1 -1
  20. package/dist/types/cli/stdio-server.d.ts +1 -0
  21. package/dist/types/cli/tool-bootstrap-core-registration.d.ts +2 -0
  22. package/dist/types/orchestration/workflow/capability-catalog.d.ts +1 -1
  23. package/dist/types/orchestration/workflow/workflow-controller.d.ts +1 -1
  24. package/dist/types/orchestration/workflow/workflow-runtime.d.ts +1 -1
  25. package/dist/types/protocol/notifications.d.ts +1 -1
  26. package/dist/types/protocol/wire/agent-events.d.ts +2 -2
  27. package/dist/types/protocol/wire/index.d.ts +1 -1
  28. package/dist/types/protocol/wire/notification-payloads.d.ts +26 -0
  29. package/dist/types/runtime/execution/turn-progress-notifier.d.ts +16 -0
  30. package/dist/types/runtime/infra/acp-detector.d.ts +5 -1
  31. package/dist/types/runtime/infra/agent-process.d.ts +1 -0
  32. package/dist/types/runtime/infra/astraclaw-capabilities.d.ts +2 -2
  33. package/dist/types/runtime/infra/builtin-skills-seed.d.ts +31 -0
  34. package/dist/types/runtime/infra/project-data-gc.d.ts +7 -1
  35. package/dist/types/runtime/infra/skill-resolver.d.ts +15 -0
  36. package/dist/types/runtime/prompt/environment-context.d.ts +24 -2
  37. package/dist/types/runtime/prompt/identity-section.d.ts +17 -0
  38. package/dist/types/runtime/session/inbound-upload-persistence.d.ts +20 -0
  39. package/dist/types/runtime/session/session-recovery.d.ts +58 -0
  40. package/dist/types/runtime/session/session-types.d.ts +22 -0
  41. package/dist/types/skills/tools/exec-tool.d.ts +1 -1
  42. package/dist/types/skills/tools/shell/command-classification.d.ts +1 -0
  43. package/dist/types/skills/tools/shell/index.d.ts +3 -3
  44. package/dist/types/skills/tools/shell/powershell-provider.d.ts +8 -3
  45. package/dist/types/skills/tools/shell/shell-exec.d.ts +18 -0
  46. package/dist/types/skills/tools/shell/shell-provider.d.ts +67 -5
  47. package/package.json +4 -1
@@ -284,6 +284,20 @@ export interface TurnExecProgressNotification {
284
284
  totalLines: number;
285
285
  totalBytes: number;
286
286
  }
287
+ /**
288
+ * Throttled long-turn progress (IM channel feedback). Emitted at most once per throttle
289
+ * interval while a turn keeps running tools; gateway/IM bridge renders it as a short
290
+ * progress line. Desktop UIs already show the tool stream and may ignore it.
291
+ */
292
+ export interface TurnProgressNotification {
293
+ turnId: string;
294
+ /** Milliseconds since the turn started. */
295
+ elapsedMs: number;
296
+ /** Tool results observed so far this turn. */
297
+ toolCalls: number;
298
+ /** Name of the most recently completed tool. */
299
+ lastTool: string;
300
+ }
287
301
  /** Generic progress heartbeat. */
288
302
  export interface TurnHeartbeatNotification {
289
303
  turnId: string;
@@ -466,6 +480,16 @@ export interface MemoryDecayCompletedNotification {
466
480
  archived: number;
467
481
  durationMs: number;
468
482
  }
483
+ /** L3 启动扫描发现的待恢复会话(崩溃残留的 in-flight turn,session-lifecycle-recovery)。 */
484
+ export interface SessionRecoveryPendingNotification {
485
+ sessions: Array<{
486
+ sessionId: string;
487
+ turnId: string;
488
+ startedAt: string;
489
+ lastUserPreview?: string;
490
+ hadSideEffectTool: boolean;
491
+ }>;
492
+ }
469
493
  /** Session metadata changed. */
470
494
  export interface SessionInfoNotification {
471
495
  sessionId: string;
@@ -1002,6 +1026,7 @@ export interface NotificationMethodMap {
1002
1026
  "turn.todos_updated": TurnTodosUpdatedNotification;
1003
1027
  "task.updated": TaskUpdatedNotification;
1004
1028
  "turn.exec_progress": TurnExecProgressNotification;
1029
+ "turn.progress": TurnProgressNotification;
1005
1030
  "turn.heartbeat": TurnHeartbeatNotification;
1006
1031
  "turn.tool_use_summary": TurnToolUseSummaryNotification;
1007
1032
  "turn.tool_selection_policy": TurnToolSelectionPolicyNotification;
@@ -1018,6 +1043,7 @@ export interface NotificationMethodMap {
1018
1043
  "memory.decay.completed": MemoryDecayCompletedNotification;
1019
1044
  "skills.updated": SkillsUpdatedNotification;
1020
1045
  "session.info": SessionInfoNotification;
1046
+ "session.recovery_pending": SessionRecoveryPendingNotification;
1021
1047
  "permission.rule_updated": PermissionRuleUpdatedNotification;
1022
1048
  "team.member.notification": TeamMemberNotification;
1023
1049
  "turn.usage_update": TurnUsageUpdateNotification;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 长任务进度通知(IM 渠道体验):IM 里看不到工具流,超过 minIntervalMs 的轮次
3
+ * 以节流方式发 turn.progress,由 gateway/IM 桥接渲染成一条简短进度文本。
4
+ * 桌面端 UI 已有工具流展示,gateway 可按渠道选择忽略此通知。
5
+ */
6
+ export interface TurnProgressNotifierOptions {
7
+ turnId: string;
8
+ send: (method: string, params: Record<string, unknown>) => void;
9
+ /** 两条进度通知的最小间隔;默认 10s。 */
10
+ minIntervalMs?: number;
11
+ now?: () => number;
12
+ }
13
+ export interface TurnProgressNotifier {
14
+ onToolResult(toolName: string): void;
15
+ }
16
+ export declare function createTurnProgressNotifier(options: TurnProgressNotifierOptions): TurnProgressNotifier;
@@ -165,7 +165,11 @@ export declare class AcpDetector {
165
165
  */
166
166
  scanAsync(force?: boolean): Promise<AgentDescriptor[]>;
167
167
  private runScanAsync;
168
- /** Return cached results without IO. */
168
+ /**
169
+ * Return cached results without IO. 冷缓存时后台预热(scanAsync)并返回 [],
170
+ * 绝不在 RPC 路径上同步 scan —— 同步探测(11-18s+)会饿死单线程引擎循环
171
+ * (x/product.confirm 60s 超时的根因)。调用方按"稍后再查"处理空列表。
172
+ */
169
173
  list(): AgentDescriptor[];
170
174
  /** Clear the detection cache. */
171
175
  clearCache(): void;
@@ -160,6 +160,7 @@ export declare function applyExternalDescriptorEnv(env: Record<string, string>,
160
160
  env?: Record<string, string>;
161
161
  suppressEnvKeys?: string[];
162
162
  }): Record<string, string>;
163
+ export declare function buildChildEnv(config: AgentProcessConfig): Record<string, string>;
163
164
  /**
164
165
  * Build LLM-related environment variables for an external ACP teammate.
165
166
  *
@@ -20,11 +20,11 @@ export declare function buildAstraClawCapabilitiesSystemPrompt(input: {
20
20
  mcpAvailable: boolean;
21
21
  availableToolNames?: Iterable<string>;
22
22
  currentEnvironment?: string;
23
- }): string | undefined;
23
+ }): Promise<string | undefined>;
24
24
  export declare function buildAstraClawCapabilitiesPromptPreamble(input: {
25
25
  projectRoot: string;
26
26
  mcpAvailable: boolean;
27
27
  availableToolNames?: Iterable<string>;
28
28
  currentEnvironment?: string;
29
- }): string | undefined;
29
+ }): Promise<string | undefined>;
30
30
  export declare function handleAstraClawCapabilityToolCall(input: AstraClawCapabilityToolInput): Promise<AstraClawCapabilityToolResult>;
@@ -0,0 +1,31 @@
1
+ export interface SeedBuiltinSkillsOptions {
2
+ /** Global skill store dir (getUserSkillsDir()). Each skill lands at <storeDir>/<name>/SKILL.md. */
3
+ storeDir: string;
4
+ /** Override the shipped builtin-skills source dir; defaults to the packaged skills/builtin. */
5
+ builtinDir?: string;
6
+ }
7
+ export interface SeedBuiltinSkillsResult {
8
+ /** Names of skills newly copied this run (empty when all already present or source missing). */
9
+ seeded: string[];
10
+ }
11
+ /**
12
+ * Locate the shipped skills/builtin directory from a module directory.
13
+ *
14
+ * The module dir differs by packaging mode — crucially, the production build is an esbuild
15
+ * BUNDLE: this module is inlined into dist/cli.js, so at runtime import.meta.url resolves to
16
+ * dist/cli.js itself, NOT dist/runtime/infra/... Probe candidates in order:
17
+ * 1. hereDir/skills/builtin — bundle: hereDir = dist/ → dist/skills/builtin
18
+ * (build.mjs copies skills/builtin there).
19
+ * 2. hereDir/../../skills/builtin — unbundled dist module at dist/runtime/infra/.
20
+ * 3. hereDir/../../../skills/builtin — dev/tsx/vitest: src/runtime/infra → repo-root skills/builtin.
21
+ * Returns the first candidate that exists, or undefined when none does (stripped-down build).
22
+ * Exported pure (hereDir in → path out) so tests can drive it against synthetic layouts.
23
+ */
24
+ export declare function resolveBuiltinDirFrom(hereDir: string): string | undefined;
25
+ /**
26
+ * Copy each builtin skill directory into the store only when its target does not yet exist.
27
+ * Idempotent: a second run with everything already present returns { seeded: [] }.
28
+ * Missing builtinDir is not an error — returns { seeded: [] } so a stripped-down build
29
+ * (or a local checkout without the assets) never blocks startup.
30
+ */
31
+ export declare function seedBuiltinSkills(options: SeedBuiltinSkillsOptions): SeedBuiltinSkillsResult;
@@ -5,5 +5,11 @@
5
5
  * re-indexes it). Only a dir with NEITHER (truly corrupt/orphaned — crashed mid-create) is reclaimed.
6
6
  * This makes the registry non-load-bearing for recovery: a lost projects.json never causes data loss.
7
7
  * Also prunes stale deletion tombstones. NEVER touches live projects' data or the user's workspace.
8
+ *
9
+ * TRULY ASYNC (not just deferred): the iteration count grows with every project dir ever created on
10
+ * this machine (unbounded on a long-lived box). Following the AcpDetector.scanAsync convention, this
11
+ * uses fs/promises and awaits between each directory — every await yields the event loop, so an RPC
12
+ * that arrives mid-GC is dispatched between directories instead of waiting for the whole sweep. The
13
+ * keep/reclaim judgment is unchanged; only the sync fs calls became awaited yield points.
8
14
  */
9
- export declare function gcOrphanProjectData(): void;
15
+ export declare function gcOrphanProjectData(): Promise<void>;
@@ -48,6 +48,17 @@ export interface SkillActivationContext {
48
48
  export declare function resolveSkills(projectRoot?: string, activation?: SkillActivationContext): ResolvedSkill[];
49
49
  /** Only the skills active in the given project (for the system-prompt skills catalog / resolution). */
50
50
  export declare function resolveActiveSkills(projectRoot?: string, activation?: SkillActivationContext): ResolvedSkill[];
51
+ /**
52
+ * Async, NON-BLOCKING, cached twin of {@link resolveSkills} for the first-token hot path
53
+ * ({@link createAvailableSkillsSection}). Returns ALL store skills annotated with mute/active state,
54
+ * exactly like {@link resolveSkills}, but reads off the event loop and reuses cached parse results when
55
+ * the store is unchanged. The sync {@link resolveSkills} is deliberately left intact for other callers.
56
+ */
57
+ export declare function resolveSkillsAsync(projectRoot?: string, activation?: SkillActivationContext): Promise<ResolvedSkill[]>;
58
+ /** Async twin of {@link resolveActiveSkills}: only the skills active in the given project context. */
59
+ export declare function resolveActiveSkillsAsync(projectRoot?: string, activation?: SkillActivationContext): Promise<ResolvedSkill[]>;
60
+ /** Test-only: drop the async raw-skill cache so each test observes a cold read. */
61
+ export declare function __clearSkillResolveCacheForTests(): void;
51
62
  /**
52
63
  * Resolve a single skill by name to its store paths, or null if not present.
53
64
  * Resolution honors only the global store (single source of truth) and ignores
@@ -57,3 +68,7 @@ export declare function resolveSkillPath(name: string): {
57
68
  baseDir: string;
58
69
  filePath: string;
59
70
  } | null;
71
+ export declare function skillConditionsMatch(meta: {
72
+ requiredTools?: string[];
73
+ environments?: string[];
74
+ }, activation: SkillActivationContext | undefined): boolean;
@@ -17,11 +17,33 @@
17
17
  import { type SystemPromptSection } from "./system-prompt-sections.js";
18
18
  import type { TaskDomain } from "./task-domain.js";
19
19
  export type ShellType = "bash" | "powershell" | "zsh" | "cmd" | "fish" | "unknown";
20
+ /**
21
+ * Structural snapshot of the shell provider that `exec()` actually drives.
22
+ *
23
+ * Defined LOCALLY (not imported from src/skills/tools/shell) on purpose: the
24
+ * runtime/prompt layer must not depend on the skills layer (architecture
25
+ * boundary). The CLI layer, which may import both, reads the real snapshot via
26
+ * `getActiveShellInfo()` and passes it in here; TS structural typing makes the
27
+ * shapes compatible without a cross-layer import.
28
+ */
29
+ export interface ShellInfo {
30
+ type: "bash" | "powershell";
31
+ shellPath?: string;
32
+ psEdition?: "5.1" | "7+";
33
+ isGitBashOnWindows?: boolean;
34
+ }
20
35
  /**
21
36
  * Create a system prompt section that provides environment context.
22
37
  * Memoized — computed once per session (environment doesn't change mid-session).
38
+ *
39
+ * @param cwdOverride Working directory to report (defaults to process.cwd()).
40
+ * @param shellInfo Real active-shell facts from the provider (via the CLI).
41
+ * When omitted, the `- Shell:` line and guidance fall back to
42
+ * `detectShellType()` (env-based guess). The shell identity is
43
+ * folded into the memoize key so a different shell doesn't
44
+ * serve a stale cached section for the same cwd.
23
45
  */
24
- export declare function createEnvironmentContextSection(cwdOverride?: string): SystemPromptSection;
46
+ export declare function createEnvironmentContextSection(cwdOverride?: string, shellInfo?: ShellInfo): SystemPromptSection;
25
47
  /**
26
48
  * Create the task guidance system prompt section.
27
49
  * Domain-aware: selects appropriate guidance based on detected or overridden task domain.
@@ -52,7 +74,7 @@ export declare function createLanguageSection(language?: string): SystemPromptSe
52
74
  * makes installed skills actually auto-activate; without it active skills only sit behind the
53
75
  * skill_view tool as a passive catalog and never fire on their own.
54
76
  */
55
- export declare function createAvailableSkillsSection(cwdOverride?: string): SystemPromptSection;
77
+ export declare function createAvailableSkillsSection(cwdOverride?: string, availableToolNames?: string[]): SystemPromptSection;
56
78
  /**
57
79
  * Detect the response language from the user's current-turn text so the strong
58
80
  * explicit-language branch of createLanguageSection actually fires.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Harness-owned identity section — 每轮(含轻量聊天轮)注入,使身份/型号回答与
3
+ * 入口渠道无关。审计结论:正常轮此前没有任何身份 section,桌面端答 "我是 Claude"
4
+ * (模型训练身份在真空中浮现)、微信端答 "qlogicagent, qlogic 团队"(硬编码人设
5
+ * 锚点 + .qlogicagent 路径 token 引发的编造)。此 section 是两者的唯一事实来源。
6
+ */
7
+ import { type SystemPromptSection } from "./system-prompt-sections.js";
8
+ export interface IdentityInfo {
9
+ /** 产品名(运营可配置),缺省 QLogicAgent */
10
+ productName?: string;
11
+ /** 可选的品牌/构建方说明行(运营可配置) */
12
+ vendorLine?: string;
13
+ provider?: string;
14
+ /** 本轮解析出的真实模型 id */
15
+ model?: string;
16
+ }
17
+ export declare function createIdentitySection(info: IdentityInfo): SystemPromptSection;
@@ -12,6 +12,12 @@
12
12
  * transport cache; only long-term persistence is skipped).
13
13
  */
14
14
  import type { ChatMessage } from "../../protocol/wire/index.js";
15
+ export interface PersistedUpload {
16
+ url: string;
17
+ localPath?: string;
18
+ }
19
+ /** 同 persistInboundUploadUrl,但返回持久化后的本地绝对路径(供模型直接 read/exec)。 */
20
+ export declare function persistInboundUploadUrlDetailed(url: string, projectId: string, sessionId: string): Promise<PersistedUpload>;
15
21
  /**
16
22
  * Persist a single inbound upload URL to projectData uploads and return the rewritten proxy URL.
17
23
  * Non-transport URLs (already-persisted, remote, file://) and any failure return the input unchanged.
@@ -22,3 +28,17 @@ export declare function persistInboundUploadUrl(url: string, projectId: string,
22
28
  * resumed history already carries persisted local URLs). Mutates and returns the message array.
23
29
  */
24
30
  export declare function persistInboundUploadsForTurn(messages: ChatMessage[], projectId: string | undefined, sessionId: string | undefined): Promise<ChatMessage[]>;
31
+ /**
32
+ * 在任意模型可见文本中查找文档类传输 URL,落盘并追加本地路径注记。
33
+ * 供两条真实投递路径复用:gateway 烤进 user text 的清单,以及
34
+ * turn-handler 的 attachmentModelContext(拼接到 LLM 视图的附件上下文)。
35
+ */
36
+ export declare function appendLocalDocumentNotesToText(text: string, projectId: string | undefined, sessionId: string | undefined): Promise<string>;
37
+ /**
38
+ * 把最后一条用户消息文本里的文档类传输 URL 抓下来落盘,并在消息末尾追加本地
39
+ * 路径注记 —— 模型从"短命 URL"起步变成从"可 read/exec 的本地文件"起步。
40
+ * image/video/audio 已由 persistInboundUploadsForTurn 处理,这里只管文档。
41
+ * 仅覆盖清单被烤进 user text 的 gateway 及 legacy 会话;新版 turn 的文档引用
42
+ * 走 attachmentModelContext,由 turn-handler 调 appendLocalDocumentNotesToText。
43
+ */
44
+ export declare function persistInboundDocumentRefsForTurn(messages: ChatMessage[], projectId: string | undefined, sessionId: string | undefined): Promise<ChatMessage[]>;
@@ -0,0 +1,58 @@
1
+ export declare function isSideEffectTool(name: string): boolean;
2
+ /** 取最后一条 user 消息的纯文本前若干字,供崩溃恢复 UI 提示(多模态/空则无预览)。 */
3
+ export declare function extractLastUserPreview(messages: ReadonlyArray<{
4
+ role: string;
5
+ content: unknown;
6
+ }>): string | undefined;
7
+ /**
8
+ * turn 开始:写 in-flight 标记。fire-and-forget 语义 —— 绝不阻塞或抛错打断 turn。
9
+ * projectRoot 未解析(无项目上下文)时静默跳过。
10
+ */
11
+ export declare function markTurnInflight(sessionId: string, projectRoot: string | undefined, turnId: string, messages: ReadonlyArray<{
12
+ role: string;
13
+ content: unknown;
14
+ }>): Promise<void>;
15
+ /**
16
+ * 首个副作用工具执行时置位 hadSideEffectTool(仅首次,避免每次工具调用写盘)。
17
+ * 安全档不据此自动续,仅用于 L4 的"继续可能重复"提示 + 为智能档铺路。
18
+ */
19
+ export declare function markInflightSideEffect(sessionId: string, projectRoot: string | undefined): Promise<void>;
20
+ /**
21
+ * turn 结束(finally 到达 = 有始有终,哪怕失败/用户中断):清标记 + 中断计数归零。
22
+ * finally 未到达(进程被硬杀/崩溃)→ 标记残留,由 L3 启动扫描接住。
23
+ */
24
+ export declare function clearTurnInflight(sessionId: string, projectRoot: string | undefined): Promise<void>;
25
+ interface SessionMetaLocation {
26
+ sessionId: string;
27
+ metadataPath: string;
28
+ }
29
+ /**
30
+ * 遍历当前 owner profile 下所有 project 的所有 session 的 metadata.json 绝对路径。
31
+ * 直接扫 projectData/<projectId>/sessions/<sessionId>/ —— 用绝对路径,绕开 projectRoot↔projectId
32
+ * 解析歧义(未注册 project 的残留 session 也覆盖到)。L2 清标记 + L3 启动扫描共用。
33
+ */
34
+ export declare function enumerateSessionMetadataFiles(): Promise<SessionMetaLocation[]>;
35
+ /**
36
+ * L2 优雅关闭:清所有 session 残留的 in-flight 标记 + 中断计数归零。
37
+ * 优雅关闭 == 主动清标记 == hermes 的 .clean_shutdown 语义(更简、单一事实源)。
38
+ * 直接原子改写 metadata.json(不经 updateSessionMetadata 的 projectRoot 解析)。返回清理数。
39
+ */
40
+ export declare function clearAllInflightMarkers(): Promise<number>;
41
+ export interface CrashedTurnInfo {
42
+ sessionId: string;
43
+ turnId: string;
44
+ startedAt: string;
45
+ lastUserPreview?: string;
46
+ hadSideEffectTool: boolean;
47
+ }
48
+ /**
49
+ * L3 启动扫描:找崩溃残留的 in-flight 标记,处理双校验/熔断,返回待恢复列表。
50
+ * agent 每次 initialize 调用 —— gateway 重启 or agent respawn 都覆盖(标记本身是事实源)。
51
+ *
52
+ * - 双校验(借鉴 codex #12382,spec §7):transcript 最后一条 assistant ts ≥ marker.startedAt →
53
+ * 该轮其实已完成、只是清标记那步没落盘 → 静默清、不误报。
54
+ * - 熔断:连续崩溃中断达 MAX_INTERRUPTED_RECOVERY → 放弃(清标记 + recoveryAbandonedAt + fail-loud)。
55
+ * - 否则:保留标记 + 递增中断计数,收进待恢复列表(交给 UI 让用户手动续)。
56
+ */
57
+ export declare function scanForCrashedTurns(): Promise<CrashedTurnInfo[]>;
58
+ export {};
@@ -2,6 +2,22 @@ import type { ChatMessage } from "../../protocol/wire/index.js";
2
2
  import type { TokenUsage } from "../../agent/types.js";
3
3
  import type { SessionUsageSnapshot } from "./session-state.js";
4
4
  export declare const SESSION_SCHEMA_VERSION = 1;
5
+ /** 熔断阈值:连续崩溃中断达此次数后放弃自动恢复(对齐 hermes stuck-loop 的 3)。 */
6
+ export declare const MAX_INTERRUPTED_RECOVERY = 3;
7
+ /**
8
+ * 在飞轮次标记。turn 开始写入 SessionMetadata.inflightTurn,正常完成/用户主动中断时清除。
9
+ * 崩溃/非优雅退出时它残留在磁盘上 —— 这就是"上次这轮没跑完"的显式信号
10
+ * (对照 codex CLI 靠时间戳隐式推断、坏了要手动裁 JSONL,见 docs/session-lifecycle-recovery-spec.md §7)。
11
+ */
12
+ export interface InflightTurnMarker {
13
+ turnId: string;
14
+ /** turn 开始时间(ISO)。启动扫描用它与 transcript 最后一条 assistant 消息的 ts 做双校验。 */
15
+ startedAt: string;
16
+ /** 最后一条 user 消息前若干字(UI 提示用,不含敏感全文)。 */
17
+ lastUserPreview?: string;
18
+ /** 该轮是否已执行过有副作用的工具(写文件/发消息/外部 API)。安全档不据此自动续,仅用于 UI"可能重复"提示 + 为智能档铺路。 */
19
+ hadSideEffectTool?: boolean;
20
+ }
5
21
  export interface SessionMetadata {
6
22
  schemaVersion?: number;
7
23
  sessionId: string;
@@ -30,6 +46,12 @@ export interface SessionMetadata {
30
46
  groupPlatform?: string;
31
47
  totalInputTokens?: number;
32
48
  totalOutputTokens?: number;
49
+ /** 在飞轮次标记:存在即"上次这轮没跑完"(崩溃残留)。turn 开始写、正常完成/中断清。 */
50
+ inflightTurn?: InflightTurnMarker;
51
+ /** 连续崩溃中断计数;turn 正常完成清零;达 MAX_INTERRUPTED_RECOVERY 熔断放弃恢复。 */
52
+ interruptedTurnCount?: number;
53
+ /** 达熔断阈值放弃恢复的时间戳(UI 显示"多次中断已停止自动恢复")。 */
54
+ recoveryAbandonedAt?: string;
33
55
  }
34
56
  export interface PersistedSession {
35
57
  metadata: SessionMetadata;
@@ -16,7 +16,7 @@ export declare const EXEC_TOOL_SCHEMA: {
16
16
  readonly properties: {
17
17
  readonly command: {
18
18
  readonly type: "string";
19
- readonly description: "Shell command to execute in PowerShell." | "Shell command to execute in bash. Use Unix-style commands (ls, cat, mkdir -p, etc.).";
19
+ readonly description: "Shell command to execute in PowerShell. See the # Environment section for the active edition and path/syntax rules." | "Shell command to execute in bash. Use Unix-style commands (ls, cat, mkdir -p, etc.).";
20
20
  };
21
21
  readonly description: {
22
22
  readonly type: "string";
@@ -50,3 +50,4 @@ export declare function requiresDedicatedReadSearchTool(classification: CommandC
50
50
  * called for reads/searches now (directory listing is allowed via exec — see above).
51
51
  */
52
52
  export declare function dedicatedToolRedirectMessage(classification: CommandClassification): string;
53
+ export declare function commandTargetsBinaryFile(command: string | null): boolean;
@@ -1,5 +1,5 @@
1
- export type { ShellType, BuildCommandOpts, BuiltCommand, ShellProvider, } from "./shell-provider.js";
2
- export { isPowerShellToolEnabled, getDefaultShellType, generateCommandId, } from "./shell-provider.js";
1
+ export type { ShellType, PowerShellEdition, ShellSelection, WindowsPowerShellResolution, WindowsPowerShellProbe, BuildCommandOpts, BuiltCommand, ShellProvider, } from "./shell-provider.js";
2
+ export { isPowerShellToolEnabled, resolveShellSelection, resolveWindowsPowerShell, getDefaultShellType, generateCommandId, } from "./shell-provider.js";
3
3
  export { createBashProvider } from "./bash-provider.js";
4
4
  export type { BashProviderOptions } from "./bash-provider.js";
5
5
  export { createPowerShellProvider, encodePowerShellCommand, } from "./powershell-provider.js";
@@ -14,5 +14,5 @@ export type { ProgressCallback } from "./task-output.js";
14
14
  export type { ExecResult, ShellCommand } from "./shell-command.js";
15
15
  export { wrapSpawn, createAbortedCommand, createFailedCommand, } from "./shell-command.js";
16
16
  export { subprocessEnv } from "./subprocess-env.js";
17
- export { exec, getCwd, setCwd, initCwd, getOriginalCwd, setShellProvider, getShellProvider, } from "./shell-exec.js";
17
+ export { exec, getCwd, setCwd, initCwd, getOriginalCwd, setShellProvider, getShellProvider, getActiveShellInfo, } from "./shell-exec.js";
18
18
  export type { ExecOptions } from "./shell-exec.js";
@@ -1,4 +1,4 @@
1
- import type { ShellProvider } from "./shell-provider.js";
1
+ import type { ShellProvider, PowerShellEdition } from "./shell-provider.js";
2
2
  /**
3
3
  * Encode a PowerShell command as UTF-16LE Base64 for -EncodedCommand.
4
4
  * This avoids all quoting/escaping issues when passing complex commands
@@ -7,9 +7,14 @@ import type { ShellProvider } from "./shell-provider.js";
7
7
  * Ported directly from cc's `encodePowerShellCommand()`.
8
8
  */
9
9
  export declare function encodePowerShellCommand(psCommand: string): string;
10
+ export interface PowerShellProviderOptions {
11
+ /** Which PowerShell edition `shellPath` is (5.1 vs 7+); surfaced as `psEdition`. */
12
+ edition?: PowerShellEdition;
13
+ }
10
14
  /**
11
15
  * Create a PowerShell ShellProvider.
12
16
  *
13
- * @param shellPath Absolute path to pwsh.exe or powershell.exe
17
+ * @param shellPath Absolute path (or PATH-resolvable name) to pwsh.exe / powershell.exe
18
+ * @param options Optional edition metadata (from `resolveWindowsPowerShell`)
14
19
  */
15
- export declare function createPowerShellProvider(shellPath: string): ShellProvider;
20
+ export declare function createPowerShellProvider(shellPath: string, options?: PowerShellProviderOptions): ShellProvider;
@@ -31,6 +31,24 @@ export declare function setShellProvider(provider: ShellProvider): void;
31
31
  * Get the current shell provider, or throw if not configured.
32
32
  */
33
33
  export declare function getShellProvider(): ShellProvider;
34
+ /**
35
+ * Read-only snapshot of the active shell provider's identity, as plain data.
36
+ *
37
+ * Consumed by the prompt layer (via the CLI, which passes it in — the prompt
38
+ * layer must not import shell modules) to tailor environment guidance to the
39
+ * shell that `exec()` actually drives, instead of re-guessing from env vars.
40
+ *
41
+ * Returns `undefined` when no provider has been configured yet. This MUST NOT
42
+ * throw: prompt assembly can run before/without `setShellProvider()` (tests,
43
+ * lightweight turns), and a missing provider is a normal "fall back to
44
+ * detectShellType()" signal, not an error.
45
+ */
46
+ export declare function getActiveShellInfo(): {
47
+ type: ShellType;
48
+ shellPath: string;
49
+ psEdition?: "5.1" | "7+";
50
+ isGitBashOnWindows?: boolean;
51
+ } | undefined;
34
52
  /**
35
53
  * Execute a shell command using the configured provider.
36
54
  * Creates a new child process for each invocation.
@@ -2,6 +2,13 @@
2
2
  * Discriminated shell type identifier.
3
3
  */
4
4
  export type ShellType = "bash" | "powershell";
5
+ /**
6
+ * Which PowerShell edition the active provider drives.
7
+ * "5.1" = Windows PowerShell (powershell.exe) — no `&&`/`||`, use `;`.
8
+ * "7+" = PowerShell 7 (pwsh) — `&&`/`||` supported.
9
+ * Consumed by prompt/guidance layers to tailor syntax advice.
10
+ */
11
+ export type PowerShellEdition = "5.1" | "7+";
5
12
  /**
6
13
  * Options passed to `buildExecCommand`.
7
14
  */
@@ -45,19 +52,31 @@ export interface ShellProvider {
45
52
  * PowerShell: false (no POSIX process groups on Windows).
46
53
  */
47
54
  readonly detached: boolean;
55
+ /**
56
+ * For PowerShell providers: which edition the binary is (5.1 vs 7+).
57
+ * Undefined for bash. Read by prompt guidance to decide `&&` vs `;`.
58
+ */
59
+ readonly psEdition?: PowerShellEdition;
60
+ /**
61
+ * True when this is a bash provider running on Windows (Git Bash / MSYS).
62
+ * Undefined/false otherwise. Read by prompt guidance for path conventions.
63
+ */
64
+ readonly isGitBashOnWindows?: boolean;
48
65
  /**
49
66
  * Wrap a raw user command into a full shell command string.
50
67
  * Handles:
51
68
  * - Environment snapshots / session env sourcing (bash)
52
- * - UTF-16LE EncodedCommand wrapping (PowerShell)
53
69
  * - CWD tracking (`pwd -P > file` / `Get-Location | Out-File`)
54
70
  * - stdin redirect for interactive commands (bash)
71
+ * Returns the UNENCODED script; PowerShell's UTF-16LE EncodedCommand wrapping
72
+ * happens in getSpawnArgs so the encode is applied exactly once.
55
73
  */
56
74
  buildExecCommand(command: string, opts: BuildCommandOpts): Promise<BuiltCommand>;
57
75
  /**
58
76
  * Return the argv array for `child_process.spawn()`.
59
77
  * E.g. bash → `['-c', commandString]`
60
- * PS → `['-NoProfile', '-NonInteractive', '-Command', cmd]`
78
+ * PS → `['-NoProfile', '-NonInteractive', '-EncodedCommand', <utf16le-base64(commandString)>]`
79
+ * (`commandString` is the UNENCODED script; PS encodes it here, exactly once.)
61
80
  */
62
81
  getSpawnArgs(commandString: string): string[];
63
82
  /**
@@ -66,18 +85,61 @@ export interface ShellProvider {
66
85
  */
67
86
  getEnvironmentOverrides(command: string): Promise<Record<string, string>>;
68
87
  }
88
+ /** High-level shell family selected for the current platform + env. */
89
+ export type ShellSelection = "powershell" | "bash";
90
+ /**
91
+ * Pure decision: given a platform + env, pick the shell family.
92
+ *
93
+ * Contract (the "flip default"):
94
+ * - non-win32 → bash (unchanged)
95
+ * - win32, QLOGICAGENT_SHELL=bash → bash (explicit opt-in Git Bash)
96
+ * - win32, QLOGICAGENT_SHELL=powershell|pwsh → powershell
97
+ * - win32, QLOGICAGENT_USE_POWERSHELL truthy (compat alias) → powershell
98
+ * - win32, nothing set → powershell ← flipped default
99
+ *
100
+ * `QLOGICAGENT_SHELL` is the single three-state switch; the legacy
101
+ * `QLOGICAGENT_USE_POWERSHELL` truthy flag is kept as a compat alias but is
102
+ * overridden by an explicit `QLOGICAGENT_SHELL=bash`.
103
+ */
104
+ export declare function resolveShellSelection(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): ShellSelection;
69
105
  /**
70
- * Check if PowerShell tool mode should be enabled.
106
+ * Check if PowerShell tool mode should be enabled for the live process.
71
107
  * Mirrors cc's `isPowerShellToolEnabled()`.
72
108
  *
73
- * On Windows: enabled when `QLOGICAGENT_USE_POWERSHELL` is truthy.
74
- * On non-Windows: always false.
109
+ * On Windows the default is now PowerShell (see `resolveShellSelection`);
110
+ * `QLOGICAGENT_SHELL=bash` opts back into Git Bash. On non-Windows: false.
75
111
  */
76
112
  export declare function isPowerShellToolEnabled(): boolean;
77
113
  /**
78
114
  * Default shell type for the current platform.
79
115
  */
80
116
  export declare function getDefaultShellType(): ShellType;
117
+ /**
118
+ * Result of Windows PowerShell resolution: the binary to spawn plus which
119
+ * edition it is (so guidance can decide `&&` vs `;`).
120
+ */
121
+ export interface WindowsPowerShellResolution {
122
+ shellPath: string;
123
+ psEdition: PowerShellEdition;
124
+ }
125
+ /**
126
+ * Injectable probes for `resolveWindowsPowerShell` (so it is unit-testable off
127
+ * a real Windows host). Defaults hit the real filesystem / PATH.
128
+ */
129
+ export interface WindowsPowerShellProbe {
130
+ /** Whether a path exists on disk. */
131
+ exists(path: string): boolean;
132
+ /** Absolute path to `pwsh` on PATH, or null if not found. */
133
+ pwshOnPath(): string | null;
134
+ }
135
+ /**
136
+ * Resolve which PowerShell binary to drive on Windows.
137
+ *
138
+ * Order: explicit `QLOGICAGENT_POWERSHELL_PATH` → `pwsh` on PATH (PS7) →
139
+ * well-known pwsh install path (PS7) → `powershell.exe` (5.1, always present).
140
+ * The returned `psEdition` records which one was chosen.
141
+ */
142
+ export declare function resolveWindowsPowerShell(env: NodeJS.ProcessEnv, probe: WindowsPowerShellProbe): WindowsPowerShellResolution;
81
143
  /**
82
144
  * Generate a short random hex ID for CWD temp file naming.
83
145
  * Matches cc's `Math.floor(Math.random() * 0x10000).toString(16)`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qlogicagent",
3
- "version": "2.17.8",
3
+ "version": "2.17.10",
4
4
  "description": "XiaozhiClaw Agent CLI — subprocess architecture (JSON-RPC over stdio)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -69,6 +69,7 @@
69
69
  "build:search-svc": "node scripts/build-search-svc.mjs",
70
70
  "start": "node dist/cli.js",
71
71
  "test": "vitest run",
72
+ "test:replay": "vitest run benchmarks/transcript-replay",
72
73
  "check": "pnpm run check:encoding-corruption && tsc --noEmit && pnpm test && pnpm run check:architecture-boundaries && pnpm run check:provider-core-boundary && pnpm run check:provider-core-release-sync && pnpm run check:pet-core-boundary && pnpm run check:workspace-hygiene && pnpm run check:package-artifact",
73
74
  "test:watch": "vitest",
74
75
  "benchmark:hermes-superiority": "tsx benchmarks/hermes-superiority/runner.ts",
@@ -81,6 +82,7 @@
81
82
  "check:workspace-hygiene": "node scripts/check-workspace-hygiene.mjs",
82
83
  "check:workspace-hygiene:strict": "node scripts/check-workspace-hygiene.mjs --strict",
83
84
  "check:package-artifact": "npm run build && node scripts/check-package-artifact.mjs",
85
+ "corpus:export-feedback": "tsx scripts/export-feedback-corpus.ts",
84
86
  "redteam:community-desensitization": "tsx src/runtime/community/community-desensitization-red-team-cli.ts",
85
87
  "clean:workspace-hygiene": "node scripts/clean-workspace-hygiene.mjs",
86
88
  "release": "node scripts/release.mjs"
@@ -106,6 +108,7 @@
106
108
  "@types/better-sqlite3": "^7.6.13",
107
109
  "@types/node": "^22.15.0",
108
110
  "esbuild": "^0.28.0",
111
+ "javascript-obfuscator": "^5.4.3",
109
112
  "oxlint": "1.67.0",
110
113
  "tsx": "^4.19.0",
111
114
  "typescript": "^5.9.0",