s-app-scoder-agent 0.9.3 → 0.9.6

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 (112) hide show
  1. package/README.md +615 -638
  2. package/dist/{guardrails.js → agent/guardrails.js} +1 -1
  3. package/dist/agent/interaction.js +70 -0
  4. package/dist/agent/mcp-bridge.js +136 -0
  5. package/dist/agent/prompt-context.js +83 -0
  6. package/dist/agent/run-loop.js +318 -0
  7. package/dist/agent/session-persistence.js +61 -0
  8. package/dist/agent/tool-executor.js +158 -0
  9. package/dist/agent/types.js +88 -0
  10. package/dist/agent/verification.js +99 -0
  11. package/dist/agent.js +239 -1002
  12. package/dist/cli/args.js +230 -0
  13. package/dist/cli/init.js +79 -0
  14. package/dist/cli/main.js +211 -0
  15. package/dist/cli/mcp-command.js +185 -0
  16. package/dist/cli/repl.js +77 -0
  17. package/dist/cli/run-once.js +26 -0
  18. package/dist/{tui/commands.js → commands/definitions.js} +6 -16
  19. package/dist/commands/registry.js +260 -0
  20. package/dist/{config.js → config/config.js} +1 -1
  21. package/dist/{prompt-builder.js → context/prompt-builder.js} +12 -1
  22. package/dist/{prompt.js → context/prompt.js} +111 -9
  23. package/dist/{skills.js → context/skills.js} +41 -2
  24. package/dist/index.js +5 -798
  25. package/dist/io/console-reporter.js +24 -0
  26. package/dist/io/reporter.js +1 -0
  27. package/dist/mcp/connection.js +227 -0
  28. package/dist/mcp/env.js +8 -0
  29. package/dist/{mcp.js → mcp/manager.js} +3 -515
  30. package/dist/mcp/sse.js +28 -0
  31. package/dist/mcp/transport-http.js +140 -0
  32. package/dist/mcp/transport-stdio.js +98 -0
  33. package/dist/mcp/types.js +11 -0
  34. package/dist/media/image.js +149 -0
  35. package/dist/{server.js → server/server.js} +1 -1
  36. package/dist/{cost.js → session/cost.js} +1 -1
  37. package/dist/{session.js → session/session.js} +1 -1
  38. package/dist/{version.js → shared/version.js} +1 -1
  39. package/dist/tools/apply-patch.js +62 -0
  40. package/dist/tools/edit-file.js +29 -0
  41. package/dist/tools/file-edit-common.js +124 -0
  42. package/dist/tools/list-files.js +64 -0
  43. package/dist/tools/path-safety.js +75 -0
  44. package/dist/tools/read-file.js +64 -0
  45. package/dist/tools/registry.js +297 -0
  46. package/dist/tools/run-command.js +192 -0
  47. package/dist/tools/schemas.js +8 -0
  48. package/dist/tools/search.js +237 -0
  49. package/dist/tools/task-tool.js +48 -0
  50. package/dist/tools/types.js +1 -0
  51. package/dist/tools/write-file.js +34 -0
  52. package/dist/tui/app.js +59 -532
  53. package/dist/tui/ask-user-modal.js +2 -1
  54. package/dist/tui/at-complete.js +2 -93
  55. package/dist/tui/charset.js +41 -0
  56. package/dist/tui/chat-views.js +72 -16
  57. package/dist/tui/chrome.js +11 -5
  58. package/dist/tui/hooks/use-command-host.js +26 -0
  59. package/dist/tui/hooks/use-event-queue.js +69 -0
  60. package/dist/tui/hooks/use-keybinds.js +87 -0
  61. package/dist/tui/hooks/use-scroll.js +38 -0
  62. package/dist/tui/hooks/use-session-resume.js +47 -0
  63. package/dist/tui/hooks/use-submit.js +106 -0
  64. package/dist/tui/image.js +2 -149
  65. package/dist/tui/input.js +1 -1
  66. package/dist/tui/markdown/ast.js +5 -0
  67. package/dist/tui/markdown/diagram.js +239 -0
  68. package/dist/tui/markdown/inline-render.js +24 -0
  69. package/dist/tui/markdown/inline.js +93 -0
  70. package/dist/tui/markdown/parse.js +139 -0
  71. package/dist/tui/markdown/render.js +146 -0
  72. package/dist/tui/markdown/table.js +106 -0
  73. package/dist/tui/markdown.js +32 -379
  74. package/dist/tui/permission-modal.js +2 -1
  75. package/dist/tui/selectors.js +157 -0
  76. package/dist/tui/session-picker.js +4 -3
  77. package/dist/tui/sidebar.js +130 -15
  78. package/dist/tui/state.js +47 -118
  79. package/dist/tui/text-width.js +139 -0
  80. package/dist/tui/theme.js +7 -2
  81. package/dist/{build.js → workspace/build.js} +1 -1
  82. package/dist/workspace/file-index.js +93 -0
  83. package/package.json +5 -1
  84. package/dist/tools.js +0 -1223
  85. package/dist/tui/command-registry.js +0 -477
  86. package/dist/tui/review.js +0 -64
  87. package/dist/tui/transcript.js +0 -145
  88. /package/dist/{agents-guide.js → context/agents-guide.js} +0 -0
  89. /package/dist/{agents.js → context/agents.js} +0 -0
  90. /package/dist/{compaction.js → context/compaction.js} +0 -0
  91. /package/dist/{context.js → context/context.js} +0 -0
  92. /package/dist/{memory.js → context/memory.js} +0 -0
  93. /package/dist/{repo-map.js → context/repo-map.js} +0 -0
  94. /package/dist/{ui.js → io/terminal.js} +0 -0
  95. /package/dist/{llm.js → llm/llm.js} +0 -0
  96. /package/dist/{model-catalog.js → llm/model-catalog.js} +0 -0
  97. /package/dist/{provider-anthropic.js → llm/provider-anthropic.js} +0 -0
  98. /package/dist/{provider-registry.js → llm/provider-registry.js} +0 -0
  99. /package/dist/{retry.js → llm/retry.js} +0 -0
  100. /package/dist/{mcp-import.js → mcp/mcp-import.js} +0 -0
  101. /package/dist/{permissions.js → security/permissions.js} +0 -0
  102. /package/dist/{sandbox.js → security/sandbox.js} +0 -0
  103. /package/dist/{shell-scanner.js → security/shell-scanner.js} +0 -0
  104. /package/dist/{logger.js → session/logger.js} +0 -0
  105. /package/dist/{steering.js → session/steering.js} +0 -0
  106. /package/dist/{constants.js → shared/constants.js} +0 -0
  107. /package/dist/{json-schema.js → shared/json-schema.js} +0 -0
  108. /package/dist/{webfetch.js → tools/webfetch.js} +0 -0
  109. /package/dist/{diagnostics.js → workspace/diagnostics.js} +0 -0
  110. /package/dist/{diff.js → workspace/diff.js} +0 -0
  111. /package/dist/{snapshot.js → workspace/snapshot.js} +0 -0
  112. /package/dist/{watch.js → workspace/watch.js} +0 -0
@@ -0,0 +1,24 @@
1
+ import * as ui from "./terminal.js";
2
+ /** Default reporter preserving the existing classic terminal behavior. */
3
+ export const consoleReporter = {
4
+ info: ui.info,
5
+ step: ui.step,
6
+ success: ui.success,
7
+ warn: ui.warn,
8
+ error: ui.error,
9
+ llm: ui.llm,
10
+ toolCall: ui.toolCall,
11
+ toolResult: ui.toolResult,
12
+ buildStatus: ui.buildStatus,
13
+ header: ui.header,
14
+ banner: ui.banner,
15
+ llmStreamStart: ui.llmStreamStart,
16
+ llmStreamChunk: ui.llmStreamChunk,
17
+ llmStreamEnd: ui.llmStreamEnd,
18
+ toolStreamStart: ui.toolStreamStart,
19
+ toolStreamChunk: ui.toolStreamChunk,
20
+ toolStreamEnd: ui.toolStreamEnd,
21
+ isToolStreamOpen: ui.isToolStreamOpen,
22
+ printDiff: ui.printDiff,
23
+ diffSummary: ui.diffSummary,
24
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,227 @@
1
+ import { CLIENT_NAME, getVersion } from "../shared/version.js";
2
+ import { redactSecrets } from "../llm/llm.js";
3
+ import { HttpTransport } from "./transport-http.js";
4
+ import { StdioTransport } from "./transport-stdio.js";
5
+ // ── JSON-RPC connection ──────────────────────────────────────────────────────
6
+ const RECONNECT_MAX = 5;
7
+ const RECONNECT_BASE_MS = 500; // backoff: 0.5s, 1s, 2s, 4s, 8s
8
+ export class McpConnection {
9
+ name;
10
+ cfg;
11
+ transport;
12
+ nextId = 1;
13
+ pending = new Map();
14
+ /** M1: current state machine position. */
15
+ state = "connecting";
16
+ /** M1: status/state sink (AgentEvent mcp_status). */
17
+ onState = null;
18
+ /** MCP A: inbound server notifications (tools/list_changed, progress…). */
19
+ onNotification = null;
20
+ /** Server-reported metadata from `initialize` (surfaced on /mcp). */
21
+ serverInfo = {};
22
+ capabilities = {};
23
+ instructions = null;
24
+ /** Protocol version negotiated with the server. */
25
+ protocolVersion = "2024-11-05";
26
+ manuallyClosed = false;
27
+ reconnectAttempt = 0;
28
+ constructor(name, cfg) {
29
+ this.name = name;
30
+ this.cfg = cfg;
31
+ // M3: url → HTTP transport; ngược lại stdio (default).
32
+ const kind = cfg.type ?? (cfg.url ? "http" : "stdio");
33
+ if (kind === "http" && !cfg.url)
34
+ throw new Error(`MCP server "${name}": type "http" nhưng thiếu url`);
35
+ if (kind === "stdio" && !cfg.command)
36
+ throw new Error(`MCP server "${name}": thiếu command`);
37
+ this.transport = kind === "http" ? new HttpTransport(cfg) : new StdioTransport(cfg);
38
+ }
39
+ /** stdio process (null với HTTP) — dùng trong tests. */
40
+ get proc() {
41
+ return this.transport.kind === "stdio" ? this.transport.proc : null;
42
+ }
43
+ get stderrTail() {
44
+ return this.transport.kind === "stdio" ? this.transport.stderrTail : "";
45
+ }
46
+ get alive() {
47
+ return this.transport.alive;
48
+ }
49
+ setState(s, detail) {
50
+ this.state = s;
51
+ this.onState?.(s, detail);
52
+ }
53
+ async connect(timeoutMs = 15_000) {
54
+ this.manuallyClosed = false;
55
+ await this.startAndHandshake(timeoutMs);
56
+ }
57
+ async startAndHandshake(timeoutMs) {
58
+ await this.transport.start();
59
+ this.wireTransport();
60
+ // MCP handshake: initialize + notifications/initialized. Negotiate the
61
+ // highest protocol version we support that the server accepts.
62
+ const initRes = await this.rpc("initialize", {
63
+ protocolVersion: "2024-11-05",
64
+ capabilities: {},
65
+ clientInfo: { name: CLIENT_NAME, version: getVersion() },
66
+ }, timeoutMs);
67
+ this.protocolVersion = initRes.protocolVersion ?? "2024-11-05";
68
+ this.capabilities = initRes.capabilities ?? {};
69
+ this.serverInfo = initRes.serverInfo ?? {};
70
+ this.instructions = initRes.instructions ?? null;
71
+ void this.transport.send({ jsonrpc: "2.0", method: "notifications/initialized" }).catch(() => {
72
+ // notification — không có pending để reject; lỗi sẽ lộ ở call sau.
73
+ });
74
+ this.setState("ready", "connected");
75
+ }
76
+ /** Wire inbound messages + channel-death → reconnect (stdio only). */
77
+ wireTransport() {
78
+ this.transport.onMessage((msg) => {
79
+ if (msg.id === undefined) {
80
+ // Server notifications (tools/list_changed, progress, message) —
81
+ // route them to the manager so it can react (re-register tools etc.).
82
+ this.onNotification?.(msg);
83
+ return;
84
+ }
85
+ const pending = this.pending.get(msg.id);
86
+ if (!pending)
87
+ return;
88
+ this.pending.delete(msg.id);
89
+ const err = msg.error;
90
+ if (err)
91
+ pending.reject(new Error(`${err.code}: ${err.message}`));
92
+ else
93
+ pending.resolve((msg.result ?? {}));
94
+ });
95
+ if (this.transport.kind === "stdio") {
96
+ this.transport.onDown((err) => {
97
+ // M1: exit → drain pending (reject rõ ràng) → reconnect, trừ khi
98
+ // kill() chủ đích.
99
+ this.drain(new Error(`MCP server "${this.name}" ${err.message}`));
100
+ if (this.manuallyClosed)
101
+ return;
102
+ void this.scheduleReconnect();
103
+ });
104
+ }
105
+ }
106
+ /** M1: exponential backoff reconnect — tools/list refresh do Manager lo. */
107
+ async scheduleReconnect() {
108
+ if (this.reconnectAttempt >= RECONNECT_MAX) {
109
+ this.setState("dead", `reconnect failed after ${RECONNECT_MAX} attempts`);
110
+ return;
111
+ }
112
+ this.reconnectAttempt++;
113
+ const delay = RECONNECT_BASE_MS * 2 ** (this.reconnectAttempt - 1);
114
+ this.setState("reconnecting", `attempt ${this.reconnectAttempt}/${RECONNECT_MAX} in ${delay}ms`);
115
+ await new Promise((r) => setTimeout(r, delay));
116
+ if (this.manuallyClosed)
117
+ return;
118
+ try {
119
+ await this.startAndHandshake(15_000);
120
+ this.reconnectAttempt = 0;
121
+ this.setState("ready", "reconnected");
122
+ }
123
+ catch (e) {
124
+ this.drain(e);
125
+ await this.scheduleReconnect();
126
+ }
127
+ }
128
+ /** MCP A: liveness probe — prefer ping (official MCP spec), fallback tools/list. */
129
+ async healthy(timeoutMs = 5_000) {
130
+ if (!this.alive)
131
+ return false;
132
+ try {
133
+ // ping was added in MCP 2025-03; fall back to tools/list for older servers.
134
+ if (this.capabilities.ping === true) {
135
+ await this.rpc("ping", {}, timeoutMs);
136
+ }
137
+ else {
138
+ await this.rpc("tools/list", {}, timeoutMs);
139
+ }
140
+ return true;
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ }
146
+ /** MCP A: explicit ping. */
147
+ async ping(timeoutMs = 5_000) {
148
+ await this.rpc("ping", {}, timeoutMs);
149
+ }
150
+ async listTools(timeoutMs = 120_000) {
151
+ const res = await this.rpc("tools/list", {}, timeoutMs);
152
+ return (res.tools ?? []).filter((t) => typeof t.name === "string");
153
+ }
154
+ // ── Phase 4: resources ────────────────────────────────────────────────────
155
+ async listResources(timeoutMs = 30_000) {
156
+ const res = await this.rpc("resources/list", {}, timeoutMs);
157
+ return (res.resources ?? []).filter((r) => typeof r.uri === "string");
158
+ }
159
+ async listResourceTemplates(timeoutMs = 30_000) {
160
+ const res = await this.rpc("resources/templates/list", {}, timeoutMs);
161
+ return (res.resourceTemplates ?? []).filter((r) => typeof r.uriTemplate === "string");
162
+ }
163
+ async readResource(uri, timeoutMs = 30_000) {
164
+ const res = await this.rpc("resources/read", { uri }, timeoutMs);
165
+ return { contents: res.contents ?? [] };
166
+ }
167
+ async subscribeResource(uri, timeoutMs = 30_000) {
168
+ await this.rpc("resources/subscribe", { uri }, timeoutMs);
169
+ }
170
+ async unsubscribeResource(uri, timeoutMs = 30_000) {
171
+ await this.rpc("resources/unsubscribe", { uri }, timeoutMs);
172
+ }
173
+ // ── Phase 4: prompts ──────────────────────────────────────────────────────
174
+ async listPrompts(timeoutMs = 30_000) {
175
+ const res = await this.rpc("prompts/list", {}, timeoutMs);
176
+ return (res.prompts ?? []).filter((p) => typeof p.name === "string");
177
+ }
178
+ async getPrompt(name, args, timeoutMs = 30_000) {
179
+ const res = await this.rpc("prompts/get", { name, arguments: args ?? {} }, timeoutMs);
180
+ return { description: res.description, messages: res.messages ?? [] };
181
+ }
182
+ async callTool(name, args, timeoutMs = 120_000) {
183
+ const res = await this.rpc("tools/call", { name, arguments: args }, timeoutMs);
184
+ const isErr = res.isError === true;
185
+ const parts = (res.content ?? []).map((c) => c.text ?? "").filter(Boolean);
186
+ // H-13: MCP servers are third-party — redact secret shapes from output
187
+ // before it enters the conversation or logs.
188
+ const structured = res.structuredContent
189
+ ? JSON.parse(redactSecrets(JSON.stringify(res.structuredContent)))
190
+ : undefined;
191
+ return { ok: !isErr, output: redactSecrets(parts.join("\n")) || "(empty result)", structured };
192
+ }
193
+ kill() {
194
+ this.manuallyClosed = true;
195
+ this.setState("dead", "closed by client");
196
+ this.drain(new Error("disconnected"));
197
+ this.transport.close();
198
+ }
199
+ // ── internals ─────────────────────────────────────────────────────────────
200
+ async rpc(method, params, timeoutMs) {
201
+ if (!this.alive)
202
+ throw new Error(`MCP server "${this.name}" not connected`);
203
+ const id = this.nextId++;
204
+ return new Promise((resolve, reject) => {
205
+ const timer = setTimeout(() => {
206
+ this.pending.delete(id);
207
+ reject(new Error(`MCP "${method}" timed out after ${(timeoutMs / 1000).toFixed(0)}s`));
208
+ }, timeoutMs);
209
+ this.pending.set(id, {
210
+ resolve: (v) => { clearTimeout(timer); resolve(v); },
211
+ reject: (e) => { clearTimeout(timer); reject(e); },
212
+ });
213
+ void Promise.resolve(this.transport.send({ jsonrpc: "2.0", id, method, params })).catch((e) => {
214
+ const p = this.pending.get(id);
215
+ if (p) {
216
+ this.pending.delete(id);
217
+ p.reject(e);
218
+ }
219
+ });
220
+ });
221
+ }
222
+ drain(err) {
223
+ for (const [, p] of this.pending)
224
+ p.reject(err);
225
+ this.pending.clear();
226
+ }
227
+ }
@@ -0,0 +1,8 @@
1
+ /** Expand {env:VAR} references in config env values. */
2
+ export function expandEnv(env) {
3
+ const out = {};
4
+ for (const [k, v] of Object.entries(env ?? {})) {
5
+ out[k] = v.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, n) => process.env[n] ?? "");
6
+ }
7
+ return out;
8
+ }