mocode-ai 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/agent/core.d.ts +112 -0
  2. package/dist/agent/core.js +51 -11
  3. package/dist/agent/index.d.ts +19 -0
  4. package/dist/agent/mode.d.ts +29 -0
  5. package/dist/agent/spawn.d.ts +36 -0
  6. package/dist/attachments/image.d.ts +22 -0
  7. package/dist/commands/config.d.ts +8 -0
  8. package/dist/config/file.d.ts +23 -0
  9. package/dist/config/index.d.ts +114 -0
  10. package/dist/config/index.js +48 -1
  11. package/dist/config/presets.d.ts +57 -0
  12. package/dist/context/budget.d.ts +79 -0
  13. package/dist/context/classifier.d.ts +14 -0
  14. package/dist/context/encoders/_util.d.ts +11 -0
  15. package/dist/context/encoders/code.d.ts +2 -0
  16. package/dist/context/encoders/doc.d.ts +14 -0
  17. package/dist/context/encoders/graph.d.ts +11 -0
  18. package/dist/context/encoders/index.d.ts +2 -0
  19. package/dist/context/encoders/log.d.ts +2 -0
  20. package/dist/context/encoders/memory.d.ts +2 -0
  21. package/dist/context/encoders/passthrough.d.ts +11 -0
  22. package/dist/context/encoders/search.d.ts +2 -0
  23. package/dist/context/encoders/summary.d.ts +12 -0
  24. package/dist/context/encoders/table.d.ts +2 -0
  25. package/dist/context/encoders/tree.d.ts +2 -0
  26. package/dist/context/index.d.ts +6 -0
  27. package/dist/context/lifecycle.d.ts +51 -0
  28. package/dist/context/pipeline.d.ts +9 -0
  29. package/dist/context/registry.d.ts +9 -0
  30. package/dist/context/relevance.d.ts +65 -0
  31. package/dist/context/types.d.ts +36 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/llm/capabilities.d.ts +19 -0
  34. package/dist/llm/index.d.ts +111 -0
  35. package/dist/memory/discover.d.ts +15 -0
  36. package/dist/memory/index.d.ts +13 -0
  37. package/dist/memory/reflect.d.ts +34 -0
  38. package/dist/memory/store.d.ts +124 -0
  39. package/dist/permissions/index.js +138 -0
  40. package/dist/pet/bridge.d.ts +73 -0
  41. package/dist/pet/protocol.d.ts +83 -0
  42. package/dist/pet/state.d.ts +28 -0
  43. package/dist/plan/active.d.ts +23 -0
  44. package/dist/plan/index.d.ts +13 -0
  45. package/dist/plan/state.d.ts +2 -0
  46. package/dist/plan/store.d.ts +78 -0
  47. package/dist/project-skill/index.js +129 -0
  48. package/dist/project-skill/initializer.js +179 -0
  49. package/dist/project-snapshot/index.js +177 -0
  50. package/dist/project-snapshot/static-files.js +87 -0
  51. package/dist/repl/index.d.ts +22 -0
  52. package/dist/repl/index.js +159 -1
  53. package/dist/rollback/index.d.ts +79 -0
  54. package/dist/sandbox/command.d.ts +3 -0
  55. package/dist/sandbox/index.d.ts +4 -0
  56. package/dist/sandbox/jail.d.ts +17 -0
  57. package/dist/sandbox/policy.d.ts +21 -0
  58. package/dist/sandbox/root.d.ts +5 -0
  59. package/dist/session/compact.d.ts +92 -0
  60. package/dist/session/drop.d.ts +19 -0
  61. package/dist/session/index.d.ts +13 -0
  62. package/dist/session/persist.d.ts +30 -0
  63. package/dist/session/scheduler.d.ts +53 -0
  64. package/dist/skills/discover.d.ts +33 -0
  65. package/dist/skills/index.d.ts +13 -0
  66. package/dist/tools/builtins/ask-human.d.ts +9 -0
  67. package/dist/tools/builtins/codegraph.d.ts +2 -0
  68. package/dist/tools/builtins/codegraph.js +17 -4
  69. package/dist/tools/builtins/drop-context.d.ts +18 -0
  70. package/dist/tools/builtins/edit-file.d.ts +2 -0
  71. package/dist/tools/builtins/edit-file.js +1 -0
  72. package/dist/tools/builtins/glob.d.ts +2 -0
  73. package/dist/tools/builtins/grep.d.ts +2 -0
  74. package/dist/tools/builtins/index.d.ts +2 -0
  75. package/dist/tools/builtins/index.js +7 -0
  76. package/dist/tools/builtins/memory-forget.d.ts +2 -0
  77. package/dist/tools/builtins/memory-forget.js +1 -0
  78. package/dist/tools/builtins/memory-list.d.ts +2 -0
  79. package/dist/tools/builtins/memory-save.d.ts +2 -0
  80. package/dist/tools/builtins/memory-save.js +1 -0
  81. package/dist/tools/builtins/memory-search.d.ts +2 -0
  82. package/dist/tools/builtins/memory-update.d.ts +2 -0
  83. package/dist/tools/builtins/memory-update.js +1 -0
  84. package/dist/tools/builtins/project-skill-update.js +73 -0
  85. package/dist/tools/builtins/read-file.d.ts +2 -0
  86. package/dist/tools/builtins/read-file.js +17 -1
  87. package/dist/tools/builtins/run-command.d.ts +2 -0
  88. package/dist/tools/builtins/run-command.js +1 -0
  89. package/dist/tools/builtins/switch-mode.d.ts +2 -0
  90. package/dist/tools/builtins/task.d.ts +2 -0
  91. package/dist/tools/builtins/task.js +1 -0
  92. package/dist/tools/builtins/todolist.d.ts +2 -0
  93. package/dist/tools/builtins/use-skill.d.ts +2 -0
  94. package/dist/tools/builtins/web-fetch.d.ts +2 -0
  95. package/dist/tools/builtins/web-search.d.ts +2 -0
  96. package/dist/tools/builtins/web-search.js +12 -1
  97. package/dist/tools/builtins/write-file.d.ts +2 -0
  98. package/dist/tools/builtins/write-file.js +1 -0
  99. package/dist/tools/constants.d.ts +52 -0
  100. package/dist/tools/registry.d.ts +18 -0
  101. package/dist/tools/types.d.ts +42 -0
  102. package/dist/ui/batch.d.ts +67 -0
  103. package/dist/ui/clipboard.d.ts +9 -0
  104. package/dist/ui/content.d.ts +55 -0
  105. package/dist/ui/content.js +14 -3
  106. package/dist/ui/diff.d.ts +34 -0
  107. package/dist/ui/diff.js +15 -24
  108. package/dist/ui/intervention.d.ts +46 -0
  109. package/dist/ui/layout.d.ts +225 -0
  110. package/dist/ui/layout.js +10 -0
  111. package/dist/ui/markdown.d.ts +46 -0
  112. package/dist/ui/mouse.d.ts +29 -0
  113. package/dist/ui/prompt.d.ts +61 -0
  114. package/dist/ui/render.d.ts +60 -0
  115. package/dist/ui/spinner.d.ts +26 -0
  116. package/dist/ui/theme.d.ts +47 -0
  117. package/dist/updater/index.d.ts +22 -0
  118. package/package.json +1 -1
@@ -0,0 +1,124 @@
1
+ export type MemoryType = 'decision' | 'fact' | 'pitfall' | 'reference' | 'feedback';
2
+ export type MemoryStatus = 'active' | 'superseded' | 'archived';
3
+ export type MemoryScope = 'project' | 'global';
4
+ export interface MemoryEntry {
5
+ id: string;
6
+ type: MemoryType;
7
+ name: string;
8
+ summary: string;
9
+ body: string;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ lastRecalledAt: string | null;
13
+ recallCount: number;
14
+ status: MemoryStatus;
15
+ supersededBy: string | null;
16
+ pinned: boolean;
17
+ scope: MemoryScope;
18
+ source: {
19
+ session?: string;
20
+ turn?: number;
21
+ } | null;
22
+ lastUpdateReason?: string;
23
+ }
24
+ /** 索引项(无 body,不进 history 也不占 token)。 */
25
+ export interface MemoryIndexItem {
26
+ id: string;
27
+ name: string;
28
+ summary: string;
29
+ type: MemoryType;
30
+ status: MemoryStatus;
31
+ }
32
+ /** 加载全部(global + project),按文件归一化 scope(防旧条目缺字段 / 字段错)。 */
33
+ export declare function loadAll(): MemoryEntry[];
34
+ export interface SaveInput {
35
+ name: string;
36
+ summary: string;
37
+ body: string;
38
+ type?: MemoryType;
39
+ pinned?: boolean;
40
+ scope?: MemoryScope;
41
+ source?: {
42
+ session?: string;
43
+ turn?: number;
44
+ } | null;
45
+ }
46
+ export type SaveResult = {
47
+ ok: true;
48
+ id: string;
49
+ } | {
50
+ ok: false;
51
+ exists: string;
52
+ };
53
+ /** 新建:id 全局唯一(跨两文件),撞库返 exists 让工具层提示用 memory_update。 */
54
+ export declare function saveEntry(input: SaveInput): SaveResult;
55
+ export interface SearchOpts {
56
+ type?: MemoryType;
57
+ status?: MemoryStatus | 'any';
58
+ limit?: number;
59
+ }
60
+ /** 关键词搜索:多词子串匹配(name 权重最高)。命中即 bump recallCount/lastRecalledAt 写回(遗忘衰减依据)。 */
61
+ export declare function searchEntries(query: string, opts?: SearchOpts): MemoryEntry[];
62
+ /** 索引(无 body、不 bump recall)。 */
63
+ export declare function listEntries(opts?: {
64
+ type?: MemoryType;
65
+ status?: MemoryStatus | 'any';
66
+ }): MemoryIndexItem[];
67
+ export interface UpdatePatch {
68
+ name?: string;
69
+ summary?: string;
70
+ body?: string;
71
+ reason?: string;
72
+ pinned?: boolean;
73
+ }
74
+ export type UpdateResult = {
75
+ ok: true;
76
+ } | {
77
+ ok: false;
78
+ notFound: true;
79
+ };
80
+ /** 原地改:id 不变(name 可改 → 但 id 仍是旧 slug,故 name 改不会触发重 slug);记 lastUpdateReason;pinned 可切换。 */
81
+ export declare function updateEntry(id: string, patch: UpdatePatch): UpdateResult;
82
+ export type ForgetResult = {
83
+ ok: true;
84
+ mode: string;
85
+ } | {
86
+ ok: false;
87
+ notFound: true;
88
+ } | {
89
+ ok: false;
90
+ pinned: true;
91
+ };
92
+ /** 归档(默认,可复活)/ 硬删;pinned 拒删。 */
93
+ export declare function forgetEntry(id: string, mode?: 'archive' | 'delete'): ForgetResult;
94
+ export interface GcResult {
95
+ decayed: number;
96
+ capped: number;
97
+ gced: number;
98
+ }
99
+ /**
100
+ * 遗忘策略(全同步,后台调):
101
+ * ① archived 超 GC_DAYS → 硬删;
102
+ * ② active + !pinned + (lastRecalledAt|createdAt) 早于 DECAY_DAYS → archived;
103
+ * ③ active 数 > MAX_ACTIVE → 按 recallCount 低 × 最近未召回久 淘汰到 archived。
104
+ * pinned 豁免一切自动衰减。写回两文件(均按 scope 过滤)。
105
+ */
106
+ export declare function gcMemories(): GcResult;
107
+ /**
108
+ * active 条目按 updatedAt 降序,封顶 MAX_INDEX_ENTRIES,只注 id/name/summary/type。
109
+ * 无 active 返空串(零行为变化)。body 不注入——按需 memory_search 取。
110
+ *
111
+ * 索引策略(省 token):不全量塞进每轮 systemPrompt。
112
+ * - pinned 永远包含(pinned = 用户明确想长期保留)
113
+ * - recallCount ≥ 1 包含(被引用过,价值已验证)
114
+ * - 否则仅当 (lastRecalledAt|createdAt) 近 RECENT_MS(=DECAY_DAYS×2) 内
115
+ * 排序:pinned 先 → recallCount 降 → updatedAt 降。
116
+ * 封顶 MAX_INDEX_ENTRIES;尾部标 hidden 数量,引导用 memory_list/memory_search 兜底。
117
+ * 真正「陈旧」被滤掉时也明示(让 LLM 知道有内容存在但被策略隐藏,而不是误以为空)。
118
+ *
119
+ * memoryEnabled=false 时(记忆子系统总开关关闭)直接返空串:Memory Index 段
120
+ * 不进系统提示,LLM 看不到工具使用提示;配合 tools/builtins 屏蔽 memory_* 工具,
121
+ * 实现「关闭时零侵入」(默认行为)。传参由 repl 的 buildSystemMessage 在拼装前调
122
+ * isMemoryEnabled() 注入(本文件是叶子,避免直接引 config 起环)。
123
+ */
124
+ export declare function buildMemoryIndexSection(memoryEnabled?: boolean): string;
@@ -0,0 +1,138 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { promptIntervention } from '../ui/intervention.js';
5
+ import { config } from '../config/index.js';
6
+ /**
7
+ * 工具权限系统:基于 risk 字段在执行前拦截确认。
8
+ *
9
+ * 设计:
10
+ * - safe → 直接放行(只读工具,零交互)
11
+ * - confirm → 弹面板确认,同工具同会话缓存(避免重复打断)
12
+ * - dangerous → 每次都弹(高风险,命令内容不可预测)
13
+ *
14
+ * 三层允许(优先级从高到低):
15
+ * 1. 永久允许(permanentAllow,跨会话持久化到 ~/.mocode/permissions.json)
16
+ * 2. 会话允许(approvedTools,本次进程内缓存)
17
+ * 3. 面板询问(promptIntervention,复用 ask_human UI)
18
+ *
19
+ * 复用 promptIntervention:统一 UI 面板,非 TTY 自动降级(第一项 = 允许)。
20
+ */
21
+ /** 跨会话持久化允许列表路径 */
22
+ const PERMISSIONS_PATH = path.join(os.homedir(), '.mocode', 'permissions.json');
23
+ /** 永久允许:跨会话持久化(用户选了"以后不再询问此工具") */
24
+ let permanentAllow = new Set();
25
+ let permanentLoaded = false;
26
+ /** 会话级缓存:confirm 级工具批准后,后续调用不再弹 */
27
+ const approvedTools = new Set();
28
+ /** 从磁盘加载永久允许列表(首调时触发,之后用内存缓存;文件不存在/解析失败返空集合) */
29
+ function loadPermanent() {
30
+ if (permanentLoaded)
31
+ return permanentAllow;
32
+ permanentLoaded = true;
33
+ try {
34
+ const raw = fs.readFileSync(PERMISSIONS_PATH, 'utf8');
35
+ const parsed = JSON.parse(raw);
36
+ if (parsed && Array.isArray(parsed.allowForever)) {
37
+ permanentAllow = new Set(parsed.allowForever.filter((x) => typeof x === 'string'));
38
+ }
39
+ }
40
+ catch {
41
+ // 文件不存在 / 解析失败 → 空集合(不阻断,用户首次使用或配置损坏均安全降级)
42
+ }
43
+ return permanentAllow;
44
+ }
45
+ /** 写入永久允许列表(覆盖写;失败静默,下次启动丢失但不阻断当前会话) */
46
+ function savePermanent() {
47
+ try {
48
+ fs.mkdirSync(path.dirname(PERMISSIONS_PATH), { recursive: true });
49
+ fs.writeFileSync(PERMISSIONS_PATH, JSON.stringify({ allowForever: Array.from(permanentAllow) }, null, 2) + '\n', 'utf8');
50
+ }
51
+ catch {
52
+ // 写失败静默(与 updateConfigKey 一致:UI 偏好路径,不阻断 REPL)
53
+ }
54
+ }
55
+ /** 从 Tool 解析 risk,缺省返 'safe'(只读工具无需标注)。 */
56
+ export function getToolRisk(tool) {
57
+ return tool.risk ?? 'safe';
58
+ }
59
+ /** 参数摘要:提取关键参数供确认面板展示(path / command 等)。 */
60
+ function summarizeArgs(tool, args) {
61
+ const lines = [];
62
+ if (typeof args.path === 'string')
63
+ lines.push(`路径: ${args.path}`);
64
+ if (typeof args.command === 'string')
65
+ lines.push(`命令: ${args.command}`);
66
+ if (typeof args.prompt === 'string') {
67
+ const preview = String(args.prompt).slice(0, 100);
68
+ lines.push(`任务: ${preview}${String(args.prompt).length > 100 ? '…' : ''}`);
69
+ }
70
+ return lines.length > 0 ? lines.join('\n') : '(无参数)';
71
+ }
72
+ /**
73
+ * 检查权限:safe 直接放行;confirm/dangerous 弹面板让用户确认。
74
+ *
75
+ * 返回 'allow' = 放行, 'deny' = 拒绝(不执行,也不记回滚快照)。
76
+ * 非 TTY 环境(promptIntervention 内部处理):自动选第一项(允许)并打 stderr 日志,不阻塞。
77
+ */
78
+ export async function checkPermission(tool, args, signal) {
79
+ // 总开关关闭 → 全部放行(零行为变化,向后兼容)
80
+ if (!config.permissionEnabled)
81
+ return 'allow';
82
+ const risk = getToolRisk(tool);
83
+ if (risk === 'safe')
84
+ return 'allow';
85
+ // 永久允许(跨会话):命中则放行,不弹面板
86
+ if (loadPermanent().has(tool.name))
87
+ return 'allow';
88
+ // 会话允许(confirm 级):同进程内缓存,命中则放行
89
+ if (risk === 'confirm' && approvedTools.has(tool.name))
90
+ return 'allow';
91
+ // 构建确认面板
92
+ const isDangerous = risk === 'dangerous';
93
+ const title = isDangerous
94
+ ? `⚠ 高风险操作: ${tool.name}`
95
+ : `确认执行: ${tool.name}`;
96
+ const detail = summarizeArgs(tool, args) + (isDangerous ? '\n\n⚠ 此操作可能产生不可逆副作用,请谨慎确认。' : '');
97
+ // 选项统一结构:dangerous 也提供"以后不再询问"(用户明确授权即尊重,即使 run_command)
98
+ const options = isDangerous
99
+ ? ['确认执行', '以后不再询问此工具', '拒绝']
100
+ : ['允许', '本次会话始终允许此工具', '以后不再询问此工具', '拒绝'];
101
+ // 弹面板(阻塞直到用户选择;signal 中断时 promptIntervention 内部处理)
102
+ const result = await promptIntervention({
103
+ type: 'choice',
104
+ title,
105
+ detail,
106
+ options,
107
+ allowCustom: false,
108
+ });
109
+ // 用户取消(Esc / Ctrl+C)→ 拒绝
110
+ if (result.action === 'cancelled')
111
+ return 'deny';
112
+ // 解析选择
113
+ const value = result.value ?? '';
114
+ if (value === '拒绝')
115
+ return 'deny';
116
+ // 永久允许:写入磁盘 + 加入内存集合(跨会话生效)
117
+ if (value === '以后不再询问此工具') {
118
+ permanentAllow.add(tool.name);
119
+ savePermanent();
120
+ return 'allow';
121
+ }
122
+ // 会话允许(confirm 级):加入内存缓存,本次进程内不再弹
123
+ if (!isDangerous && value === '本次会话始终允许此工具') {
124
+ approvedTools.add(tool.name);
125
+ }
126
+ return 'allow';
127
+ }
128
+ /** 移除工具的永久允许(撤销"以后不再询问"授权)。供未来 `/permissions` 管理命令使用。 */
129
+ export function revokePermanentAllow(toolName) {
130
+ if (permanentAllow.has(toolName)) {
131
+ permanentAllow.delete(toolName);
132
+ savePermanent();
133
+ }
134
+ }
135
+ /** 列出所有永久允许的工具名(供未来 `/permissions` 管理命令使用)。 */
136
+ export function listPermanentAllow() {
137
+ return Array.from(loadPermanent());
138
+ }
@@ -0,0 +1,73 @@
1
+ import WebSocket from 'ws';
2
+ import type { PetState, PetStateMeta, SkinListMessage } from './protocol.js';
3
+ /** 默认端口;MOCODE_PET_PORT 环境变量覆盖(design.md 默认假设)。 */
4
+ export declare const DEFAULT_PET_PORT = 47821;
5
+ /**
6
+ * 探测本地端口是否已有 WS server 监听并可完成一次 WS 握手。
7
+ * 前置条件:port 为合法端口号。
8
+ * 后置条件:返回 true 表示 <timeoutMs> 内握手成功(桌宠已在跑);false 表示超时/拒绝连接。
9
+ * 无副作用(探测用的临时连接在返回前关闭)。
10
+ */
11
+ export declare function probePort(port: number, timeoutMs?: number): Promise<boolean>;
12
+ /**
13
+ * 拉起独立 Electron 桌宠进程(detached,不随当前 mocode 进程退出而杀死)。
14
+ * 前置条件:调用方已确认端口未被占用(避免重复 spawn)。
15
+ * 后置条件:
16
+ * - resolve() 表示 spawn 系统调用成功发出(不代表桌宠已可连接,调用方需配合 connectWithBackoff)。
17
+ * - reject(err) 表示可执行文件不可解析(mocode-pet-app 未安装/安装失败)——降级路径。
18
+ */
19
+ export declare function spawnPetProcess(): Promise<void>;
20
+ /**
21
+ * 按退避序列重试连接,直到成功或耗尽重试次数。
22
+ * 前置条件:retryDelaysMs 非空、单调(本设计取 [200,400,800,1600,3200])。
23
+ * 后置条件:
24
+ * - resolve(ws) 表示某次尝试内 probePort/connect 成功。
25
+ * - reject(err) 表示所有尝试均失败,err 汇总最后一次失败原因。
26
+ * 循环不变量:每次尝试前 attempts < retryDelaysMs.length;每次失败后 attempts 严格 +1。
27
+ */
28
+ export declare function connectWithBackoff(port: number, retryDelaysMs?: number[]): Promise<WebSocket>;
29
+ /**
30
+ * 发送一次状态消息(经节流:与上次发送状态相同则跳过)。
31
+ * 前置条件:无(未连接时静默 no-op,不抛错、不阻塞 agent 主循环)。
32
+ * 后置条件:若 state !== lastSentState,构造并发送一条合法 StateMessage;否则无副作用。
33
+ */
34
+ export declare function sendState(state: PetState, meta?: PetStateMeta): void;
35
+ /** 当前是否已建立活跃连接。 */
36
+ export declare function isConnected(): boolean;
37
+ /** 主动断开连接(best-effort 发 bye 后 close code=1000)。 */
38
+ export declare function disconnect(): void;
39
+ /**
40
+ * 请求桌宠进程整体退出(方案C:CLI 侧退出入口;托盘图标是桌面侧的另一入口,见 packages/pet-app/src/main.ts)。
41
+ * 不要求本连接是活跃连接——任何已连接的 mocode 进程都可以关闭桌宠。
42
+ * 前置条件:当前进程已建立连接(未连接则先尝试探测端口直连,再发 shutdown)。
43
+ * 后置条件:发送 shutdown 消息后主动断开本地连接;不等待桌宠进程确认退出(best-effort,不阻塞 REPL)。
44
+ */
45
+ export declare function killPetProcess(): Promise<{
46
+ ok: boolean;
47
+ reason?: string;
48
+ }>;
49
+ /**
50
+ * 请求当前可用皮肤列表(供 /pet skin 菜单展示)。
51
+ * 前置条件:当前进程已建立连接(未连接则先尝试探测端口直连;若桌宠未运行则失败)。
52
+ * 后置条件:resolve 桌宠回复的 SkinListMessage;超时(2s 内无回复)reject。
53
+ */
54
+ export declare function listSkins(): Promise<SkinListMessage>;
55
+ /**
56
+ * 请求桌宠切换皮肤(选宠物)。
57
+ * 前置条件:当前进程已建立连接(未连接则静默 no-op,与 sendState 的降级策略一致)。
58
+ * 后置条件:已连接时发送 set_skin 消息;未连接时不抛错、无副作用。
59
+ */
60
+ export declare function setSkin(skinId: string): void;
61
+ /**
62
+ * /pet 命令入口。
63
+ * 前置条件:REPL 主循环已初始化(不要求 agent 正在运行)。
64
+ * 后置条件:
65
+ * - 若调用前无活跃连接:调用后 either 已建立连接(connected=true)
66
+ * 或已尝试 spawn+重试全部失败(connected=false,附错误原因)。
67
+ * - 若调用前有活跃连接:调用后连接已关闭(connected=false)。
68
+ * 不抛异常(所有失败路径转为返回值,供 REPL 渲染提示行)。
69
+ */
70
+ export declare function togglePet(): Promise<{
71
+ connected: boolean;
72
+ reason?: string;
73
+ }>;
@@ -0,0 +1,83 @@
1
+ /** 桌宠状态集合。子 agent(task 工具派生)永不产生这些状态——只在主 agent hooks 组装处广播。 */
2
+ export type PetState = 'idle' | 'thinking' | 'speaking' | 'tool_call' | 'done' | 'aborted' | 'error' | 'waiting_human';
3
+ /** 全部合法状态值(供运行时校验,如 Set 成员判断)。 */
4
+ export declare const PET_STATES: readonly PetState[];
5
+ /** 状态负载:随 state 消息携带的可选元数据。 */
6
+ export interface PetStateMeta {
7
+ /** 仅 tool_call 状态携带:当前调用的工具名(如 "write_file")。 */
8
+ toolName?: string;
9
+ /** 仅 error 状态携带:简短错误摘要(截断,不含敏感路径/密钥)。 */
10
+ errorMessage?: string;
11
+ }
12
+ /** 每个 mocode 进程生成一次,进程存活期间稳定。 */
13
+ export type ClientId = string;
14
+ export interface HelloMessage {
15
+ type: 'hello';
16
+ clientId: ClientId;
17
+ pid: number;
18
+ cwd: string;
19
+ ts: number;
20
+ }
21
+ export interface StateMessage {
22
+ type: 'state';
23
+ clientId: ClientId;
24
+ state: PetState;
25
+ meta?: PetStateMeta;
26
+ ts: number;
27
+ }
28
+ export interface PingMessage {
29
+ type: 'ping';
30
+ ts: number;
31
+ }
32
+ export interface ByeMessage {
33
+ type: 'bye';
34
+ clientId: ClientId;
35
+ ts: number;
36
+ }
37
+ /** 主动请求桌宠进程整体退出(与 disconnect 不同:disconnect 只断开本连接,shutdown 让桌宠 app.quit())。
38
+ * 不要求发送方是当前活跃连接——任何已连接的 mocode 进程都可以关闭桌宠(见 Requirement 补充:关闭桌宠)。 */
39
+ export interface ShutdownMessage {
40
+ type: 'shutdown';
41
+ clientId: ClientId;
42
+ ts: number;
43
+ }
44
+ /** 请求切换桌宠皮肤(选宠物)。skinId 对应 assets/pets/manifest.json 里的 id,空字符串/'default' 表示恢复默认 mascot.svg。 */
45
+ export interface SetSkinMessage {
46
+ type: 'set_skin';
47
+ clientId: ClientId;
48
+ skinId: string;
49
+ ts: number;
50
+ }
51
+ /** 请求当前可用皮肤列表(触发 server 回复 SkinListMessage)。 */
52
+ export interface ListSkinsMessage {
53
+ type: 'list_skins';
54
+ ts: number;
55
+ }
56
+ export type ClientMessage = HelloMessage | StateMessage | PingMessage | ByeMessage | ShutdownMessage | SetSkinMessage | ListSkinsMessage;
57
+ export interface WelcomeMessage {
58
+ type: 'welcome';
59
+ isActive: boolean;
60
+ ts: number;
61
+ }
62
+ export interface PongMessage {
63
+ type: 'pong';
64
+ ts: number;
65
+ }
66
+ /** 皮肤列表(供 /pet skin 菜单展示)。桌宠进程回复,ids 对应 assets/pets/ 下的候选素材。 */
67
+ export interface SkinListMessage {
68
+ type: 'skin_list';
69
+ skins: {
70
+ id: string;
71
+ name: string;
72
+ }[];
73
+ currentSkinId: string;
74
+ ts: number;
75
+ }
76
+ export type ServerMessage = WelcomeMessage | PongMessage | SkinListMessage;
77
+ /** 判断值是否为合法 PetState(供消息校验,非法值丢弃不崩)。 */
78
+ export declare function isValidPetState(v: unknown): v is PetState;
79
+ /** 校验并解析一条原始 JSON 字符串为 ClientMessage;失败(JSON 非法/缺字段/type 未知)返回 null。
80
+ * 永不抛错——调用方(bridge/server)据此静默丢弃畸形消息,不断开连接。 */
81
+ export declare function parseClientMessage(raw: string): ClientMessage | null;
82
+ /** 校验并解析一条原始 JSON 字符串为 ServerMessage;失败返回 null(同上,永不抛错)。 */
83
+ export declare function parseServerMessage(raw: string): ServerMessage | null;
@@ -0,0 +1,28 @@
1
+ import type { AgentHooks } from '../agent/core.js';
2
+ import type { PetState } from './protocol.js';
3
+ import * as bridge from './bridge.js';
4
+ /** AgentHooks 方法名(供 deriveState 的 event 参数类型与穷举测试)。 */
5
+ export type AgentHookEventName = 'onStepStart' | 'onText' | 'onToolCall' | 'onToolStart' | 'onToolResult' | 'onDone' | 'onAbort' | 'onMaxSteps' | 'onChatDone' | 'onToolBatchEnd' | 'onNoReply';
6
+ /** deriveState 的可选参数(按事件类型而异)。 */
7
+ export interface DeriveStateArgs {
8
+ /** onToolCall / onToolStart 的工具名。 */
9
+ toolName?: string;
10
+ /** onToolResult 的输出(用于判断是否报错:以"错误"开头,参考 agent/index.ts writeToolResult)。 */
11
+ toolOutput?: string;
12
+ }
13
+ /**
14
+ * 纯函数:给定当前 hook 事件与其参数,推导下一个 PetState。
15
+ * 前置条件:event 是 AgentHooks 定义的方法名之一。
16
+ * 后置条件:返回值 ∈ PetState 枚举;对同一 (event, args) 输入,任意调用时刻返回值相同(确定性,可测)。
17
+ * 不依赖调用历史之外的隐藏状态——纯函数式转移表。
18
+ */
19
+ export declare function deriveState(event: AgentHookEventName, args?: DeriveStateArgs): PetState;
20
+ /**
21
+ * 把 AgentHooks 事件流映射为 PetState 变化并调用 bridge.sendState。
22
+ * 前置条件:传入的 sender 已存在(可能尚未连接,sendState 内部自行处理未连接情形——no-op)。
23
+ * 后置条件:返回的 AgentHooks 对象的每个方法都是纯粹的"状态推导 + 转发",
24
+ * 不修改 core.ts 的任何行为、不影响现有 TUI hooks 的调用结果。
25
+ */
26
+ export declare function createPetHooks(sender?: {
27
+ sendState: typeof bridge.sendState;
28
+ }): AgentHooks;
@@ -0,0 +1,23 @@
1
+ import type { Plan } from './store.js';
2
+ export interface ActivePlanSnapshot {
3
+ id: string;
4
+ title: string;
5
+ status: Plan['status'];
6
+ done: number;
7
+ total: number;
8
+ }
9
+ type Listener = (snap: ActivePlanSnapshot | null) => void;
10
+ /** 取当前活跃 plan(完整对象,工具用)。无 → null。 */
11
+ export declare function getActivePlan(): Plan | null;
12
+ /** 设活跃 plan。同一 id 重复设也走 listener(repl 借此刷 status 行——即使内容未变,显式刷新有助)。 */
13
+ export declare function setActivePlan(plan: Plan | null): void;
14
+ /** 清活跃 plan(plan finish/abandon 后由 repl/todolist 调)。 */
15
+ export declare function clearActivePlan(): void;
16
+ /** 注册活跃 plan 变更监听器。返注销函数(不常用,模式 listener 一次性常驻)。 */
17
+ export declare function onActivePlanChange(cb: Listener): () => void;
18
+ /** 是否有活跃 plan(in_progress 状态)。finished/abandoned 不算「活跃」。 */
19
+ export declare function hasActivePlan(): boolean;
20
+ /** 给 status 行用的极简摘要(避免调用方读 Plan 全字段)。无 → 空串。 */
21
+ /** 状态行 chip 用的短摘要(无 ANSI 颜色,由 layout 上色)。maxWidth 默认 56,留 room 给右段。 */
22
+ export declare function getActivePlanSummary(maxWidth?: number): string;
23
+ export {};
@@ -0,0 +1,13 @@
1
+ export { type Plan, type PlanStep, type PlanStatus, type StepStatus, plansDir, archiveDir, planPath, ensurePlansDir, ensureArchiveDir, newPlanId, parsePlan, serializePlan, readPlan, writePlan, deletePlan, deletePlanAnywhere, updatePlan, listPlans, archivePlan, unarchivePlan, renderPlanForLLM, renderPlanChip, } from './store.js';
2
+ export { type ActivePlanSnapshot, getActivePlan, setActivePlan, clearActivePlan, onActivePlanChange, hasActivePlan, getActivePlanSummary, } from './active.js';
3
+ /**
4
+ * 拼给 systemPrompt 注入的活跃 plan 摘要段。
5
+ * - 无活跃 plan → 空串(repl 直接跳过拼接,systemPrompt 长度不变)。
6
+ * - 有 → 紧凑 markdown(目标 + 步骤 checkbox + 进度日志末 5 条),header + 内容。
7
+ *
8
+ * 触发:setActivePlan 后由 repl listener 调;也供 buildSystemMessage 直接同步取(repl 入口)。
9
+ * 上限:超 MAX_ACTIVE_PLAN_CHARS 截到尾部(罕见,plan 文件本身就小)。
10
+ */
11
+ export declare function buildActivePlanSection(): string;
12
+ /** 注入 systemPrompt 的活跃 plan 摘要上限(字符)。 */
13
+ export declare const MAX_ACTIVE_PLAN_CHARS = 3000;
@@ -0,0 +1,2 @@
1
+ export type { ActivePlanSnapshot } from './active.js';
2
+ export { getActivePlan, setActivePlan, clearActivePlan, onActivePlanChange, hasActivePlan, getActivePlanSummary, } from './active.js';
@@ -0,0 +1,78 @@
1
+ export type StepStatus = 'pending' | 'in_progress' | 'done' | 'skipped' | 'failed';
2
+ export interface PlanStep {
3
+ /** 1-based 序号(在当前 plan 内稳定,即便中间 add_step 也不重用旧号——见 addStep)。 */
4
+ id: number;
5
+ title: string;
6
+ status: StepStatus;
7
+ }
8
+ export type PlanStatus = 'in_progress' | 'finished' | 'abandoned';
9
+ export interface Plan {
10
+ id: string;
11
+ title: string;
12
+ status: PlanStatus;
13
+ created: string;
14
+ updated: string;
15
+ goal: string;
16
+ steps: PlanStep[];
17
+ /** 进度日志条目(时间戳 + 一行说明),append-only。 */
18
+ log: {
19
+ at: string;
20
+ text: string;
21
+ }[];
22
+ }
23
+ /** plans 目录:<sandboxRoot>/.mocode/plans/。无 sandboxRoot 退到 cwd(防御)。 */
24
+ export declare function plansDir(): string;
25
+ /** 归档目录:<root>/.mocode/plans/archive/。finish 自动归档、用户手动 unarchive 都走这里。 */
26
+ export declare function archiveDir(): string;
27
+ /** 单 plan 文件路径:<root>/.mocode/plans/<id>.md。可选 baseDir:默认 plans,也可指 archive。 */
28
+ export declare function planPath(id: string, baseDir?: string): string;
29
+ /** 确保 plans 目录存在(惰性,工具每次写前调)。 */
30
+ export declare function ensurePlansDir(): void;
31
+ /** 确保归档目录存在(惰性)。 */
32
+ export declare function ensureArchiveDir(): void;
33
+ /** 本地时间戳(无时区后缀),与 filename 的 YYYY-MM-DDTHH-mm-ss 风格一致。
34
+ * 例:2026-07-04T22:54:51.729(本地时,便于人对读 frontmatter / progress log,
35
+ * 且与同 plan id 文件名"2026-07-04T22-54-51"对应)。
36
+ * 不带 Z 后缀 → JS Date 解析会按本地时读(一致 round-trip);
37
+ * 反例:toISOString() 返 2026-07-04T14:54:51.729Z(UTC),跟用户当地差 8 小时,易误解。 */
38
+ export declare function localIsoTimestamp(d?: Date): string;
39
+ /** plan id:YYYY-MM-DDTHH-mm-ss-xxxxxx(本地时间,文件名安全)。xxxxxx = 4 字节随机 hex(防同秒并发撞)。 */
40
+ export declare function newPlanId(now?: Date): string;
41
+ /**
42
+ * 解析 plan 文件为 Plan 对象。失败(无 frontmatter / 段缺失 / 坏 checkbox)→ 退化:
43
+ * - 无 frontmatter:把整文当 title 段
44
+ * - 缺字段:空串 / 空数组
45
+ * - 坏 checkbox:跳过该行,不污染好行
46
+ * 解析成功但 step id 缺序 / 重号:按出现顺序重排为 1..N(保稳定)。
47
+ */
48
+ export declare function parsePlan(raw: string, fallbackId: string): Plan;
49
+ /** 序列化为 markdown 文本。固定段序:frontmatter → 目标 → 步骤 → 进度日志。 */
50
+ export declare function serializePlan(p: Plan): string;
51
+ /** 读 plan 文件;不存在 → null,坏文件 → fallbackId 最小可用 Plan(不抛,工具契约)。
52
+ * 可选 baseDir:默认 plans,传 archiveDir() 可读归档。 */
53
+ export declare function readPlan(id: string, baseDir?: string): Plan | null;
54
+ /** 写 plan 文件(atomic:写 .tmp 再 rename)。返回是否成功。 */
55
+ export declare function writePlan(p: Plan): boolean;
56
+ /** 删 plan 文件;不存在静默 ok。可选 baseDir:默认 plans,传 archiveDir() 删归档。 */
57
+ export declare function deletePlan(id: string, baseDir?: string): boolean;
58
+ /**
59
+ * 原子读-改-写:读 → mutator 改 → 写。mutator 返回 false 视为"无改动",跳过写。
60
+ * mutator 抛错 → 写失败 → 返 null(不抛,工具契约)。
61
+ *
62
+ * mutator 同步:本 store 走同步 fs,mutator 必须同步(返 Promise 视为"无信号"——返原对象);
63
+ * 设计如此以避免把 fs API 改成 async(同步 fs 在 node 单 tick 原子,无需锁)。
64
+ */
65
+ export declare function updatePlan(id: string, mutator: (p: Plan) => Plan | false): Plan | null;
66
+ /** 列 plans 目录下所有 plan(按 updated 倒序,最新在前)。解析失败的跳过。
67
+ * scope: 'active'(默认)=仅 plans/;'archived'=仅 archive/;'all'=合并(去重按 id)。 */
68
+ export declare function listPlans(scope?: 'active' | 'archived' | 'all'): Plan[];
69
+ /** 把 plans/<id>.md mv 到 plans/archive/<id>.md(atomic rename)。
70
+ * 源不存在 → false(已归档 / 删了 / 从未存在都算)。 */
71
+ export declare function archivePlan(id: string): boolean;
72
+ /** 把 plans/archive/<id>.md 还原到 plans/<id>.md。源不存在 → false。 */
73
+ export declare function unarchivePlan(id: string): boolean;
74
+ /** 任一处存在就都删(活跃 + 归档)。用于 todolist delete 不关心来源。 */
75
+ export declare function deletePlanAnywhere(id: string): boolean;
76
+ /** 把 Plan 渲成给 LLM 看的紧凑摘要(多行文本)。 */
77
+ export declare function renderPlanForLLM(p: Plan): string;
78
+ export declare function renderPlanChip(p: Plan | null, maxWidth?: number): string;