@inharness-ai/claude4spec 1.0.4 → 1.0.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.
- package/.claude/skills/c4s-brief-implementer/SKILL.md +37 -3
- package/.claude/skills/c4s-spec-reader/SKILL.md +11 -0
- package/CHANGELOG.md +26 -0
- package/dist/bin/c4s/commands/ask.d.ts +15 -0
- package/dist/bin/c4s/commands/ask.js +73 -0
- package/dist/bin/c4s/commands/ask.js.map +1 -0
- package/dist/bin/c4s/errors.d.ts +1 -1
- package/dist/bin/c4s/errors.js.map +1 -1
- package/dist/bin/c4s/project.d.ts +6 -0
- package/dist/bin/c4s/project.js +25 -0
- package/dist/bin/c4s/project.js.map +1 -1
- package/dist/bin/c4s.js +12 -6
- package/dist/bin/c4s.js.map +1 -1
- package/dist/client/assets/{arc-grMo2vzL.js → arc-CqFoQdai.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-Fp_VHONq.js → architectureDiagram-3BPJPVTR-CIEQ1CV_.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-CIwVG0sx.js → blockDiagram-GPEHLZMM-DwCCDYZs.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-tysuJyz0.js → c4Diagram-AAUBKEIU-DB988zZs.js} +1 -1
- package/dist/client/assets/channel-mXCQ_joU.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DZFNNwID.js → chunk-2J33WTMH-Bwc9B4Id.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-CsnJG7Pv.js → chunk-4BX2VUAB-ClOw-zt3.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-MhREYXzo.js → chunk-55IACEB6-B513D_Oz.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-BjvyGtK3.js → chunk-727SXJPM-CvT-ppRD.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-zfcfKNbO.js → chunk-AQP2D5EJ-BDZ-uKjF.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Ckx-Hc8U.js → chunk-FMBD7UC4-DlnsCvyP.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-DSIVVmYM.js → chunk-ND2GUHAM-Cv4kIfqi.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-EcIM6ZdS.js → chunk-QZHKN3VN-Boyq-O-A.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CvLk0Sg1.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CvLk0Sg1.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DC8DVe0z.js → cose-bilkent-S5V4N54A-B4fXXqZV.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-BQjcdXnw.js → dagre-BM42HDAG-D7JvdAaj.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BUPeBMwt.js → diagram-2AECGRRQ-TqVC9_06.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-I_Q-Pnp6.js → diagram-5GNKFQAL-BJE7Sfvp.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-B8WbbWJu.js → diagram-KO2AKTUF-dDI3Q4Xy.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CsACxgW7.js → diagram-LMA3HP47-zrNXdbAH.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-lPuJxQED.js → diagram-OG6HWLK6-C9RoGREB.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-cU5ZGREz.js → erDiagram-TEJ5UH35-BIOg0Mgx.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-Dr-vZTBV.js → flowDiagram-I6XJVG4X-C5olRPEY.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CXiWZ0R8.js → ganttDiagram-6RSMTGT7-Daqt3_2M.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-PDvLBbvz.js → gitGraphDiagram-PVQCEYII-E09LH7cR.js} +1 -1
- package/dist/client/assets/{index-RMlMPKY7.js → index-BwbjYrNH.js} +1 -1
- package/dist/client/assets/{index-BQltseJ-.js → index-CMsZQS3t.js} +1 -1
- package/dist/client/assets/{index-YFPhJxE5.js → index-CUhhC6SY.js} +151 -146
- package/dist/client/assets/{infoDiagram-5YYISTIA-Re9fOUPS.js → infoDiagram-5YYISTIA-Cy_D0LmA.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BTazB_7p.js → ishikawaDiagram-YF4QCWOH-B3cvnpaf.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-pGUoqxHl.js → journeyDiagram-JHISSGLW-IR8sEnrO.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-C2RzCJrM.js → kanban-definition-UN3LZRKU-DHaqk7jz.js} +1 -1
- package/dist/client/assets/{linear-Be3WPYKf.js → linear-BJO3kbAY.js} +1 -1
- package/dist/client/assets/{mermaid.core-GdPXct3q.js → mermaid.core-Dwygsmt4.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DvktVTUU.js → mindmap-definition-RKZ34NQL-C9XVPJV6.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DvC_m4m5.js → pieDiagram-4H26LBE5-CZldRk6l.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-SWtidcZL.js → quadrantDiagram-W4KKPZXB-CY053ghg.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CQEHDx1W.js → requirementDiagram-4Y6WPE33-DQbMS7j1.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-C0PTA3gP.js → sankeyDiagram-5OEKKPKP-C5w3huvN.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-D-x7SXoo.js → sequenceDiagram-3UESZ5HK-nF1GikPI.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-BhcGVfs1.js → stateDiagram-AJRCARHV-CxjKXGSz.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CLYCEwSk.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-JYTlKPB-.js → timeline-definition-PNZ67QCA-q0ZvTC5H.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-KjNL1Qpz.js → vennDiagram-CIIHVFJN-CO7sztp2.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-DcljmmEY.js → wardley-L42UT6IY-JeTZFzC9.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Pbq6ALgg.js → wardleyDiagram-YWT4CUSO-COpCQvmO.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-G_r_2Cvk.js → xychartDiagram-2RQKCTM6-C78ptl5b.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/core/ask/run-ask.d.ts +37 -0
- package/dist/core/ask/run-ask.js +171 -0
- package/dist/core/ask/run-ask.js.map +1 -0
- package/dist/server/core/plugin-host/host.js +8 -8
- package/dist/server/core/plugin-host/host.js.map +1 -1
- package/dist/server/core/plugin-host/types.d.ts +22 -7
- package/dist/server/entities/ac/plugin.js +1 -1
- package/dist/server/entities/ac/plugin.js.map +1 -1
- package/dist/server/entities/database-table/plugin.js +1 -1
- package/dist/server/entities/database-table/plugin.js.map +1 -1
- package/dist/server/entities/dto/plugin.js +1 -1
- package/dist/server/entities/dto/plugin.js.map +1 -1
- package/dist/server/entities/endpoint/plugin.js +1 -1
- package/dist/server/entities/endpoint/plugin.js.map +1 -1
- package/dist/server/entities/ui-view/plugin.js +1 -1
- package/dist/server/entities/ui-view/plugin.js.map +1 -1
- package/dist/server/external-skills/brief-implementer-template.d.ts +1 -1
- package/dist/server/external-skills/brief-implementer-template.js +19 -1
- package/dist/server/external-skills/brief-implementer-template.js.map +1 -1
- package/dist/server/external-skills/spec-reader-template.d.ts +1 -1
- package/dist/server/external-skills/spec-reader-template.js +12 -0
- package/dist/server/external-skills/spec-reader-template.js.map +1 -1
- package/dist/server/index.js +44 -39
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/brief-tools.d.ts +2 -4
- package/dist/server/mcp/brief-tools.js +4 -25
- package/dist/server/mcp/brief-tools.js.map +1 -1
- package/dist/server/mcp/c4s-tools.d.ts +15 -0
- package/dist/server/mcp/c4s-tools.js +67 -0
- package/dist/server/mcp/c4s-tools.js.map +1 -0
- package/dist/server/routes/agent-turn.d.ts +92 -0
- package/dist/server/routes/agent-turn.js +386 -0
- package/dist/server/routes/agent-turn.js.map +1 -0
- package/dist/server/routes/chat.d.ts +2 -30
- package/dist/server/routes/chat.js +33 -396
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/threads.d.ts +2 -2
- package/dist/server/routes/threads.js +51 -1
- package/dist/server/routes/threads.js.map +1 -1
- package/dist/server/services/chat-context.d.ts +4 -0
- package/dist/server/services/chat-context.js +24 -6
- package/dist/server/services/chat-context.js.map +1 -1
- package/dist/server/skills/brief-author/SKILL.md +0 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-B30S19wt.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-Bm4SZLsb.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Bm4SZLsb.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-2vcyLY1V.js +0 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* creation time. Mounted by `routes/chat.ts` ONLY for threads with
|
|
5
5
|
* `context_type='brief'`.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* UI/user surface, not agent loop).
|
|
7
|
+
* Two tools (get_brief, update_brief) — no `create_brief`/`list_briefs`/
|
|
8
|
+
* `brief_generate` (UI/user surface, not agent loop).
|
|
9
9
|
*/
|
|
10
10
|
import { createMcpServer, mcpTool } from '@inharness-ai/agent-adapters';
|
|
11
11
|
import { z } from 'zod';
|
|
@@ -16,7 +16,7 @@ const AGENT_ACTIONS = z.enum(['replace', 'append', 'insert_after_section']);
|
|
|
16
16
|
const ANCHOR_RE = new RegExp(ANCHOR_PATTERN_SOURCE);
|
|
17
17
|
const HEADING_RE = /^(#{2,6})\s+(.+?)\s*$/;
|
|
18
18
|
export function buildBriefToolsServer(ctx) {
|
|
19
|
-
const { briefService,
|
|
19
|
+
const { briefService, briefPath } = ctx;
|
|
20
20
|
const ok = (payload) => ({
|
|
21
21
|
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
22
22
|
});
|
|
@@ -88,30 +88,9 @@ export function buildBriefToolsServer(ctx) {
|
|
|
88
88
|
return fail(err);
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
|
-
const listBriefVersions = mcpTool('list_brief_versions', 'List all versions of this thread\'s brief (metadata only). Use to inspect history before calling get_brief_version.', {}, async () => {
|
|
92
|
-
try {
|
|
93
|
-
return ok({ versions: pageVersions.listVersions(briefPath) });
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
return fail(err);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
const getBriefVersion = mcpTool('get_brief_version', 'Get a specific version snapshot of this brief (full content + metadata).', {
|
|
100
|
-
version: z.number().int().positive(),
|
|
101
|
-
}, async (args) => {
|
|
102
|
-
try {
|
|
103
|
-
const v = pageVersions.getVersion(briefPath, Number(args.version));
|
|
104
|
-
if (!v)
|
|
105
|
-
return fail(new DomainError('VERSION_NOT_FOUND', `version ${args.version} not found`));
|
|
106
|
-
return ok(v);
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
return fail(err);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
91
|
return createMcpServer({
|
|
113
92
|
name: 'brief-tools',
|
|
114
|
-
tools: [getBrief, updateBrief
|
|
93
|
+
tools: [getBrief, updateBrief],
|
|
115
94
|
});
|
|
116
95
|
}
|
|
117
96
|
function composeBody(prior, action, fragment, anchor, heading) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brief-tools.js","sourceRoot":"","sources":["../../../src/server/mcp/brief-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,OAAO,EAA0B,MAAM,8BAA8B,CAAC;AAChG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"brief-tools.js","sourceRoot":"","sources":["../../../src/server/mcp/brief-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type McpServerInstance } from '@inharness-ai/agent-adapters';
|
|
2
|
+
/**
|
|
3
|
+
* `c4s-tools` — cross-cutting in-process MCP server expozujacy `c4s ask`
|
|
4
|
+
* jako narzedzie MCP. Jeden tool `ask`, mountowany per request (jak `plan-tools`).
|
|
5
|
+
*
|
|
6
|
+
* Motywacja: w `plan_mode=true` agent ma `disallowedTools = MUTATING_BUILTINS`
|
|
7
|
+
* (Bash zakazany), wiec nie moglby zawolac binarki `c4s ask`. MCP nie podlega
|
|
8
|
+
* temu filtrowi, wiec `mcp__c4s-tools__ask` dziala w plan_mode i poza nim,
|
|
9
|
+
* bez zdejmowania bana na Bash.
|
|
10
|
+
*
|
|
11
|
+
* Stateless — w odroznieniu od `plan-tools` (gdzie `threadId` jest ambient
|
|
12
|
+
* z closure), `c4s-tools.ask` celuje w **innego** peera, wszystkie parametry
|
|
13
|
+
* przychodza jako input. Brak `ctx` w fabryce.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildC4sToolsServer(): McpServerInstance;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createMcpServer, mcpTool } from '@inharness-ai/agent-adapters';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { runAsk, AskError } from '../../core/ask/run-ask.js';
|
|
4
|
+
/**
|
|
5
|
+
* `c4s-tools` — cross-cutting in-process MCP server expozujacy `c4s ask`
|
|
6
|
+
* jako narzedzie MCP. Jeden tool `ask`, mountowany per request (jak `plan-tools`).
|
|
7
|
+
*
|
|
8
|
+
* Motywacja: w `plan_mode=true` agent ma `disallowedTools = MUTATING_BUILTINS`
|
|
9
|
+
* (Bash zakazany), wiec nie moglby zawolac binarki `c4s ask`. MCP nie podlega
|
|
10
|
+
* temu filtrowi, wiec `mcp__c4s-tools__ask` dziala w plan_mode i poza nim,
|
|
11
|
+
* bez zdejmowania bana na Bash.
|
|
12
|
+
*
|
|
13
|
+
* Stateless — w odroznieniu od `plan-tools` (gdzie `threadId` jest ambient
|
|
14
|
+
* z closure), `c4s-tools.ask` celuje w **innego** peera, wszystkie parametry
|
|
15
|
+
* przychodza jako input. Brak `ctx` w fabryce.
|
|
16
|
+
*/
|
|
17
|
+
export function buildC4sToolsServer() {
|
|
18
|
+
const ask = mcpTool('ask', [
|
|
19
|
+
'Consult another claude4spec specification synchronously. Returns { threadId, answer }.',
|
|
20
|
+
'Use `project` (local path to peer .claude4spec/) OR `server` (URL override); if both, `server` wins.',
|
|
21
|
+
'Continue an existing peer thread by passing its `threadId` (omit `contextType` then).',
|
|
22
|
+
'Works in plan_mode — MCP is not filtered by READONLY_BUILTINS, so this works where Bash-shelled `c4s ask` does not.',
|
|
23
|
+
'Same contract as the `c4s` CLI: same discovery, same errors.',
|
|
24
|
+
].join('\n'), {
|
|
25
|
+
message: z.string(),
|
|
26
|
+
project: z.string().optional(),
|
|
27
|
+
server: z.string().optional(),
|
|
28
|
+
contextType: z.enum(['chat', 'brief', 'patch']).optional(),
|
|
29
|
+
threadId: z.string().optional(),
|
|
30
|
+
brief: z.string().optional(),
|
|
31
|
+
}, async (input) => {
|
|
32
|
+
try {
|
|
33
|
+
const result = await runAsk({
|
|
34
|
+
message: String(input.message ?? ''),
|
|
35
|
+
project: typeof input.project === 'string' ? input.project : undefined,
|
|
36
|
+
server: typeof input.server === 'string' ? input.server : undefined,
|
|
37
|
+
contextType: typeof input.contextType === 'string'
|
|
38
|
+
? input.contextType
|
|
39
|
+
: undefined,
|
|
40
|
+
threadId: typeof input.threadId === 'string' ? input.threadId : undefined,
|
|
41
|
+
briefPath: typeof input.brief === 'string' ? input.brief : undefined,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const code = err instanceof AskError ? err.code : 'AGENT_ERROR';
|
|
49
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50
|
+
const hint = err instanceof AskError ? err.hint : undefined;
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: 'text',
|
|
55
|
+
text: JSON.stringify({ error: { code, message, ...(hint ? { hint } : {}) } }),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return createMcpServer({
|
|
63
|
+
name: 'c4s-tools',
|
|
64
|
+
tools: [ask],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=c4s-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"c4s-tools.js","sourceRoot":"","sources":["../../../src/server/mcp/c4s-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type RuntimeAdapter, type StreamObserver, type UserInputHandler, type UserInputResponse } from '@inharness-ai/agent-adapters';
|
|
3
|
+
import type { ChatService } from '../services/chat.js';
|
|
4
|
+
import type { PagesService } from '../services/pages.js';
|
|
5
|
+
import type { TagsService } from '../services/tags.js';
|
|
6
|
+
import type { SectionsService } from '../services/sections.js';
|
|
7
|
+
import type { PlanService } from '../services/plan.js';
|
|
8
|
+
import type { BriefService } from '../services/brief.js';
|
|
9
|
+
import type { PatchService } from '../services/patch.js';
|
|
10
|
+
import type { PageVersionService } from '../services/page-version.js';
|
|
11
|
+
import type { SkillResolver, SkillRegistry } from '../services/skill-registry.js';
|
|
12
|
+
import type { Annotation, ChatThread } from '../../shared/entities.js';
|
|
13
|
+
import type { WsGateway } from '../ws/gateway.js';
|
|
14
|
+
import type { Db } from '../db/index.js';
|
|
15
|
+
/** Deps potrzebne do uruchomienia tury agenta. Wspolne dla `POST /api/chat`
|
|
16
|
+
* (SSE) i `POST /api/threads/:id/ask` (headless). */
|
|
17
|
+
export interface AgentTurnDeps {
|
|
18
|
+
chatService: ChatService;
|
|
19
|
+
pagesService: PagesService;
|
|
20
|
+
tagsService: TagsService;
|
|
21
|
+
sectionsService: SectionsService;
|
|
22
|
+
planService: PlanService;
|
|
23
|
+
briefService: BriefService;
|
|
24
|
+
patchService: PatchService;
|
|
25
|
+
pageVersions: PageVersionService;
|
|
26
|
+
skillResolver: SkillResolver;
|
|
27
|
+
skillRegistry: SkillRegistry;
|
|
28
|
+
ws: WsGateway;
|
|
29
|
+
cwd: string;
|
|
30
|
+
pagesDir: string;
|
|
31
|
+
mode: 'dev' | 'prod';
|
|
32
|
+
db: Db;
|
|
33
|
+
}
|
|
34
|
+
export declare const ALLOWED_MODELS: readonly ["sonnet-4.6", "opus-4.7", "haiku-4.5"];
|
|
35
|
+
export type Model = (typeof ALLOWED_MODELS)[number];
|
|
36
|
+
interface PendingInput {
|
|
37
|
+
resolve: (response: UserInputResponse) => void;
|
|
38
|
+
reject: (reason: unknown) => void;
|
|
39
|
+
requestIdsForRequest: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ActiveAdapter {
|
|
42
|
+
requestId: string;
|
|
43
|
+
adapter: RuntimeAdapter;
|
|
44
|
+
emitter: EventEmitter;
|
|
45
|
+
}
|
|
46
|
+
export declare const activeAdapters: Map<string, ActiveAdapter>;
|
|
47
|
+
export declare const pendingInputs: Map<string, PendingInput>;
|
|
48
|
+
export declare function cancelPendingForRequest(requestId: string): void;
|
|
49
|
+
/** Typed blad tury — pozwala konsumentom (headless `ask`) zmapowac powod
|
|
50
|
+
* zakonczenia na status HTTP. Te same kody co SSE `event: error`. */
|
|
51
|
+
export declare class AgentTurnError extends Error {
|
|
52
|
+
code: 'ABORTED' | 'TIMEOUT' | 'AGENT_UNAVAILABLE' | 'AGENT_ERROR';
|
|
53
|
+
constructor(code: 'ABORTED' | 'TIMEOUT' | 'AGENT_UNAVAILABLE' | 'AGENT_ERROR', message: string);
|
|
54
|
+
}
|
|
55
|
+
type TurnEvent = {
|
|
56
|
+
type: string;
|
|
57
|
+
} & Record<string, unknown>;
|
|
58
|
+
export interface AgentTurnInput {
|
|
59
|
+
/** Watek juz rozwiazany przez callera (+ planMode zaaplikowany). */
|
|
60
|
+
thread: ChatThread;
|
|
61
|
+
prompt: string;
|
|
62
|
+
annotations?: Annotation[];
|
|
63
|
+
model: Model;
|
|
64
|
+
currentPage?: string | null;
|
|
65
|
+
architectureConfig: Record<string, unknown>;
|
|
66
|
+
requestId: string;
|
|
67
|
+
consoleObserver: StreamObserver | null;
|
|
68
|
+
/** Transport callera — SSE forwarder dla `POST /api/chat`, no-op dla `ask`. */
|
|
69
|
+
onEvent: (event: TurnEvent) => void;
|
|
70
|
+
/** Interaktywny kanal user-input. Brak = headless (np. `ask`). */
|
|
71
|
+
onUserInput?: UserInputHandler;
|
|
72
|
+
}
|
|
73
|
+
export interface AgentTurnResult {
|
|
74
|
+
threadId: string;
|
|
75
|
+
answer: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Uruchamia jedna ture agenta dla istniejacego watku: wstawia wiadomosc user,
|
|
79
|
+
* buduje runtime przez rejestr `context_type`, wykonuje `adapter.execute(...)`,
|
|
80
|
+
* persystuje eventy mapperem `UnifiedEvent → chat_message`. Caller wybiera
|
|
81
|
+
* transport przez `onEvent` (SSE vs collapse). Zwraca `{ threadId, answer }`,
|
|
82
|
+
* gdzie `answer` to skolapsowany tekst assistant tej tury.
|
|
83
|
+
*
|
|
84
|
+
* Pre-flight `activeAdapters.has` (→ 409) NALEZY do callera; ta funkcja
|
|
85
|
+
* rejestruje adapter w `activeAdapters` i zwalnia go w `finally`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function runAgentTurn(deps: AgentTurnDeps, input: AgentTurnInput): Promise<AgentTurnResult>;
|
|
88
|
+
export declare function countPages(tree: Array<{
|
|
89
|
+
type: string;
|
|
90
|
+
children?: unknown[];
|
|
91
|
+
}>): number;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { createAdapter, observeStream, AdapterAbortError, AdapterInitError, AdapterTimeoutError, } from '@inharness-ai/agent-adapters';
|
|
3
|
+
import { buildPlanToolsServer } from '../mcp/plan-tools.js';
|
|
4
|
+
import { buildBriefToolsServer } from '../mcp/brief-tools.js';
|
|
5
|
+
import { buildC4sToolsServer } from '../mcp/c4s-tools.js';
|
|
6
|
+
import { buildSystemPrompt } from '../services/chat-context.js';
|
|
7
|
+
import { readConfig } from '../config.js';
|
|
8
|
+
import { pluginHost } from '../core/plugin-host/host.js';
|
|
9
|
+
/** M21 m05ctxreg: tools whitelist per context_type. Brief threads get only
|
|
10
|
+
* brief-tools (per-thread, mounted below) + release-tools (read-only).
|
|
11
|
+
* All other plugin servers (endpoint-tools, dto-tools, ui-view-tools,
|
|
12
|
+
* database-table-tools, plan-tools, reference-tools) are NOT mounted. */
|
|
13
|
+
const BRIEF_ALLOWED_PLUGIN_MCP = new Set(['release-tools']);
|
|
14
|
+
export const ALLOWED_MODELS = ['sonnet-4.6', 'opus-4.7', 'haiku-4.5'];
|
|
15
|
+
// Wspoldzielone rejestry — keyed by threadId, jeden aktywny adapter per watek.
|
|
16
|
+
// `POST /api/chat` i `POST /api/threads/:id/ask` importuja te same instancje:
|
|
17
|
+
// drugi POST/ask na ten sam watek dostaje 409, a GET /api/chat/stream/:threadId
|
|
18
|
+
// dolacza do zywego streamu niezaleznie od tego, ktory endpoint go uruchomil.
|
|
19
|
+
export const activeAdapters = new Map();
|
|
20
|
+
export const pendingInputs = new Map();
|
|
21
|
+
export function cancelPendingForRequest(requestId) {
|
|
22
|
+
for (const [inputId, pending] of pendingInputs) {
|
|
23
|
+
if (pending.requestIdsForRequest === requestId) {
|
|
24
|
+
pending.reject(new Error('stream aborted'));
|
|
25
|
+
pendingInputs.delete(inputId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Typed blad tury — pozwala konsumentom (headless `ask`) zmapowac powod
|
|
30
|
+
* zakonczenia na status HTTP. Te same kody co SSE `event: error`. */
|
|
31
|
+
export class AgentTurnError extends Error {
|
|
32
|
+
code;
|
|
33
|
+
constructor(code, message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.code = code;
|
|
36
|
+
this.name = 'AgentTurnError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Uruchamia jedna ture agenta dla istniejacego watku: wstawia wiadomosc user,
|
|
41
|
+
* buduje runtime przez rejestr `context_type`, wykonuje `adapter.execute(...)`,
|
|
42
|
+
* persystuje eventy mapperem `UnifiedEvent → chat_message`. Caller wybiera
|
|
43
|
+
* transport przez `onEvent` (SSE vs collapse). Zwraca `{ threadId, answer }`,
|
|
44
|
+
* gdzie `answer` to skolapsowany tekst assistant tej tury.
|
|
45
|
+
*
|
|
46
|
+
* Pre-flight `activeAdapters.has` (→ 409) NALEZY do callera; ta funkcja
|
|
47
|
+
* rejestruje adapter w `activeAdapters` i zwalnia go w `finally`.
|
|
48
|
+
*/
|
|
49
|
+
export async function runAgentTurn(deps, input) {
|
|
50
|
+
const { thread, prompt, requestId } = input;
|
|
51
|
+
const annotations = input.annotations ?? [];
|
|
52
|
+
const currentPage = input.currentPage ?? null;
|
|
53
|
+
const planMode = thread.planMode;
|
|
54
|
+
const adapter = createAdapter('claude-code');
|
|
55
|
+
const emitter = new EventEmitter();
|
|
56
|
+
activeAdapters.set(thread.id, { requestId, adapter, emitter });
|
|
57
|
+
// `onEvent` to transport callera; `emitter` zasila GET /api/chat/stream/:threadId.
|
|
58
|
+
const emit = (event) => {
|
|
59
|
+
input.onEvent(event);
|
|
60
|
+
emitter.emit('event', event);
|
|
61
|
+
};
|
|
62
|
+
emit({ type: 'connected', requestId, threadId: thread.id });
|
|
63
|
+
let assistantBuf = '';
|
|
64
|
+
let thinkingBuf = '';
|
|
65
|
+
// Skolapsowana odpowiedz tury — wszystkie main-agent `text_delta` zlaczone.
|
|
66
|
+
// Nie resetowane przy flush; zwracane jako `answer` po terminalnym evencie.
|
|
67
|
+
let answerBuf = '';
|
|
68
|
+
const subagentBuffers = new Map();
|
|
69
|
+
let lastMainAssistantRowId = null;
|
|
70
|
+
let lastToolResultRowId = null;
|
|
71
|
+
let lastTurnUsage = null;
|
|
72
|
+
const getSubBuf = (taskId) => {
|
|
73
|
+
let buf = subagentBuffers.get(taskId);
|
|
74
|
+
if (!buf) {
|
|
75
|
+
buf = { text: '', thinking: '' };
|
|
76
|
+
subagentBuffers.set(taskId, buf);
|
|
77
|
+
}
|
|
78
|
+
return buf;
|
|
79
|
+
};
|
|
80
|
+
const flushSubBuf = (taskId) => {
|
|
81
|
+
const buf = subagentBuffers.get(taskId);
|
|
82
|
+
if (!buf)
|
|
83
|
+
return;
|
|
84
|
+
if (buf.text) {
|
|
85
|
+
deps.chatService.addMessage(thread.id, 'assistant', JSON.stringify({ text: buf.text }), null, null, taskId);
|
|
86
|
+
buf.text = '';
|
|
87
|
+
}
|
|
88
|
+
if (buf.thinking) {
|
|
89
|
+
deps.chatService.addMessage(thread.id, 'assistant', JSON.stringify({ text: buf.thinking, thinking: true }), null, null, taskId);
|
|
90
|
+
buf.thinking = '';
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const flushMainBuf = () => {
|
|
94
|
+
if (assistantBuf) {
|
|
95
|
+
const row = deps.chatService.addMessage(thread.id, 'assistant', JSON.stringify({ text: assistantBuf }));
|
|
96
|
+
lastMainAssistantRowId = row.id;
|
|
97
|
+
assistantBuf = '';
|
|
98
|
+
}
|
|
99
|
+
if (thinkingBuf) {
|
|
100
|
+
deps.chatService.addMessage(thread.id, 'assistant', JSON.stringify({ text: thinkingBuf, thinking: true }));
|
|
101
|
+
thinkingBuf = '';
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
deps.chatService.addMessage(thread.id, 'user', JSON.stringify({ text: prompt, annotations }), null, null, null, planMode);
|
|
106
|
+
if (!thread.title) {
|
|
107
|
+
const title = prompt.slice(0, 60) + (prompt.length > 60 ? '...' : '');
|
|
108
|
+
deps.chatService.updateTitle(thread.id, title || '(annotations only)');
|
|
109
|
+
}
|
|
110
|
+
const isBrief = thread.contextType === 'brief';
|
|
111
|
+
// M23: patch threads keep the FULL spec-editing toolset (isBrief stays
|
|
112
|
+
// false) — their job is to edit the spec. Only the system prompt differs.
|
|
113
|
+
const isPatch = thread.contextType === 'patch';
|
|
114
|
+
// M21: dla brief context czytamy aktualny snapshot brief'u (frontmatter+body+hash)
|
|
115
|
+
// i wkladamy do system promptu. Skip kosztownych obliczen pageCount/entityCounts.
|
|
116
|
+
let briefSnapshot = null;
|
|
117
|
+
if (isBrief && thread.briefPath) {
|
|
118
|
+
try {
|
|
119
|
+
briefSnapshot = await deps.briefService.getBrief(thread.briefPath);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.warn(`[chat] brief read failed for ${thread.briefPath}:`, err.message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// M23: dla patch context czytamy snapshot patcha i wkladamy do system promptu.
|
|
126
|
+
let patchSnapshot = null;
|
|
127
|
+
if (isPatch && thread.patchPath) {
|
|
128
|
+
try {
|
|
129
|
+
patchSnapshot = await deps.patchService.getPatch(thread.patchPath);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.warn(`[chat] patch read failed for ${thread.patchPath}:`, err.message);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let currentPageBody = null;
|
|
136
|
+
if (!isBrief && currentPage) {
|
|
137
|
+
try {
|
|
138
|
+
const page = await deps.pagesService.read(currentPage);
|
|
139
|
+
currentPageBody = page.body;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
currentPageBody = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Stale-plan reminder MUSI byc sprawdzony PRZED markPlanSeenByThread —
|
|
146
|
+
// ten ostatni bumpuje last_seen_plan_version i zapodaje sygnal "synced".
|
|
147
|
+
const isFirstTurn = !thread.hasSystemPrompt;
|
|
148
|
+
const stalePlanReminder = isBrief ? null : deps.planService.getStalePlanReminder(thread.id);
|
|
149
|
+
const currentPlan = isBrief ? null : deps.planService.getByThread(thread.id);
|
|
150
|
+
// Skill resolution: writing-style (config.writingStyle, M15) ladowany
|
|
151
|
+
// niezaleznie od kontekstu. Dla brief context dokladamy bundled `brief-author`.
|
|
152
|
+
const writingStyleSkills = deps.skillResolver.resolve();
|
|
153
|
+
const writingStyle = writingStyleSkills[0]
|
|
154
|
+
? {
|
|
155
|
+
slug: writingStyleSkills[0].name,
|
|
156
|
+
title: String(writingStyleSkills[0].metadata?.title ?? writingStyleSkills[0].name),
|
|
157
|
+
}
|
|
158
|
+
: null;
|
|
159
|
+
const inlineSkills = [...writingStyleSkills];
|
|
160
|
+
if (isBrief && deps.skillRegistry.has('brief-author')) {
|
|
161
|
+
const skill = deps.skillRegistry.resolve('brief-author');
|
|
162
|
+
inlineSkills.push({
|
|
163
|
+
name: skill.metadata.slug,
|
|
164
|
+
description: skill.metadata.description,
|
|
165
|
+
content: skill.content,
|
|
166
|
+
files: skill.files,
|
|
167
|
+
metadata: {
|
|
168
|
+
version: skill.metadata.version,
|
|
169
|
+
language: skill.metadata.language,
|
|
170
|
+
title: skill.metadata.title,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const pageCount = isBrief ? 0 : countPages(await deps.pagesService.listTree());
|
|
175
|
+
const systemPrompt = buildSystemPrompt({
|
|
176
|
+
projectName: readConfig(deps.cwd).name,
|
|
177
|
+
cwd: deps.cwd,
|
|
178
|
+
pagesDir: deps.pagesDir,
|
|
179
|
+
currentPagePath: currentPage,
|
|
180
|
+
currentPageBody,
|
|
181
|
+
pageCount,
|
|
182
|
+
entityCounts: isBrief ? {} : pluginHost.computeEntityCounts(deps.db.handle),
|
|
183
|
+
tagCount: isBrief ? 0 : deps.tagsService.list().length,
|
|
184
|
+
sectionCount: isBrief ? 0 : deps.sectionsService.count(),
|
|
185
|
+
annotations,
|
|
186
|
+
planMode,
|
|
187
|
+
currentPlan,
|
|
188
|
+
planToolsAvailable: !isBrief,
|
|
189
|
+
c4sToolsAvailable: !isBrief,
|
|
190
|
+
writingStyle,
|
|
191
|
+
contextType: thread.contextType,
|
|
192
|
+
brief: briefSnapshot,
|
|
193
|
+
patch: patchSnapshot,
|
|
194
|
+
});
|
|
195
|
+
// claude-code CLI po resumeSessionId ignoruje kolejne systemPrompty —
|
|
196
|
+
// wiążący dla audytu jest tylko pierwszy. UPDATE idempotentny (no-op na 2.+ turze).
|
|
197
|
+
deps.chatService.setInitialSystemPrompt(thread.id, systemPrompt);
|
|
198
|
+
if (isFirstTurn && currentPlan) {
|
|
199
|
+
deps.planService.markPlanSeenByThread(thread.id);
|
|
200
|
+
}
|
|
201
|
+
// M21 m05ctxreg: per-thread MCP servers depend on context_type.
|
|
202
|
+
const planTools = isBrief
|
|
203
|
+
? null
|
|
204
|
+
: buildPlanToolsServer({
|
|
205
|
+
threadId: thread.id,
|
|
206
|
+
planService: deps.planService,
|
|
207
|
+
});
|
|
208
|
+
const briefTools = isBrief && thread.briefPath
|
|
209
|
+
? buildBriefToolsServer({
|
|
210
|
+
threadId: thread.id,
|
|
211
|
+
briefPath: thread.briefPath,
|
|
212
|
+
briefService: deps.briefService,
|
|
213
|
+
})
|
|
214
|
+
: null;
|
|
215
|
+
// M24 c4s-tools: cross-cutting MCP exposing `c4s ask` flow. Stateless,
|
|
216
|
+
// fresh factory per request. Whitelist: chat + patch contexts; brief is
|
|
217
|
+
// intentionally narrow (brief-tools + read-only release-tools only).
|
|
218
|
+
const c4sTools = isBrief ? null : buildC4sToolsServer();
|
|
219
|
+
const pluginMcpEntries = pluginHost
|
|
220
|
+
.buildMcpServers()
|
|
221
|
+
.filter(({ name }) => (isBrief ? BRIEF_ALLOWED_PLUGIN_MCP.has(name) : true))
|
|
222
|
+
.map(({ name, server }) => [name, server.config]);
|
|
223
|
+
const mcpServers = Object.fromEntries(pluginMcpEntries);
|
|
224
|
+
if (planTools)
|
|
225
|
+
mcpServers['plan-tools'] = planTools.config;
|
|
226
|
+
if (briefTools)
|
|
227
|
+
mcpServers['brief-tools'] = briefTools.config;
|
|
228
|
+
if (c4sTools)
|
|
229
|
+
mcpServers['c4s-tools'] = c4sTools.config;
|
|
230
|
+
const effectivePrompt = stalePlanReminder ? `${stalePlanReminder}\n\n${prompt}` : prompt;
|
|
231
|
+
const stream = adapter.execute({
|
|
232
|
+
prompt: effectivePrompt,
|
|
233
|
+
systemPrompt,
|
|
234
|
+
model: input.model,
|
|
235
|
+
cwd: deps.cwd,
|
|
236
|
+
mcpServers,
|
|
237
|
+
skills: inlineSkills,
|
|
238
|
+
resumeSessionId: thread.lastSessionId ?? undefined,
|
|
239
|
+
architectureConfig: input.architectureConfig,
|
|
240
|
+
planMode,
|
|
241
|
+
onUserInput: input.onUserInput,
|
|
242
|
+
});
|
|
243
|
+
const observed = input.consoleObserver
|
|
244
|
+
? observeStream(stream, [input.consoleObserver])
|
|
245
|
+
: stream;
|
|
246
|
+
for await (const event of observed) {
|
|
247
|
+
emit(event);
|
|
248
|
+
switch (event.type) {
|
|
249
|
+
case 'text_delta':
|
|
250
|
+
if (event.isSubagent && event.subagentTaskId) {
|
|
251
|
+
getSubBuf(event.subagentTaskId).text += event.text;
|
|
252
|
+
}
|
|
253
|
+
else if (!event.isSubagent) {
|
|
254
|
+
assistantBuf += event.text;
|
|
255
|
+
answerBuf += event.text;
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
case 'thinking':
|
|
259
|
+
if (event.isSubagent && event.subagentTaskId) {
|
|
260
|
+
getSubBuf(event.subagentTaskId).thinking += event.text;
|
|
261
|
+
}
|
|
262
|
+
else if (!event.isSubagent) {
|
|
263
|
+
thinkingBuf += event.text;
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
case 'assistant_message': {
|
|
267
|
+
if (!event.message.subagentTaskId && event.message.usage) {
|
|
268
|
+
lastTurnUsage = event.message.usage;
|
|
269
|
+
deps.chatService.setLastUsage(thread.id, event.message.usage);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'tool_use': {
|
|
274
|
+
const taskId = event.subagentTaskId ?? null;
|
|
275
|
+
if (taskId)
|
|
276
|
+
flushSubBuf(taskId);
|
|
277
|
+
else
|
|
278
|
+
flushMainBuf();
|
|
279
|
+
deps.chatService.addMessage(thread.id, 'tool_use', JSON.stringify({ input: event.input }), event.toolName, event.toolUseId, taskId, planMode, 'streaming');
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case 'tool_result': {
|
|
283
|
+
const taskId = event.subagentTaskId ?? null;
|
|
284
|
+
deps.chatService.markToolUseComplete(thread.id, event.toolUseId);
|
|
285
|
+
const row = deps.chatService.addMessage(thread.id, 'tool_result', JSON.stringify({ summary: event.summary, isError: event.isError }), null, event.toolUseId, taskId);
|
|
286
|
+
if (taskId === null)
|
|
287
|
+
lastToolResultRowId = row.id;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'subagent_started':
|
|
291
|
+
flushMainBuf();
|
|
292
|
+
deps.chatService.startSubagentTask(thread.id, event.taskId, event.description, event.toolUseId ?? null);
|
|
293
|
+
break;
|
|
294
|
+
case 'subagent_progress':
|
|
295
|
+
deps.chatService.updateSubagentTaskProgress(thread.id, event.taskId, event.description);
|
|
296
|
+
break;
|
|
297
|
+
case 'subagent_completed':
|
|
298
|
+
flushSubBuf(event.taskId);
|
|
299
|
+
deps.chatService.completeSubagentTask(thread.id, event.taskId, event.status, event.summary ?? null);
|
|
300
|
+
break;
|
|
301
|
+
case 'result': {
|
|
302
|
+
flushMainBuf();
|
|
303
|
+
for (const tid of Array.from(subagentBuffers.keys()))
|
|
304
|
+
flushSubBuf(tid);
|
|
305
|
+
if (event.sessionId)
|
|
306
|
+
deps.chatService.setLastSessionId(thread.id, event.sessionId);
|
|
307
|
+
const turnAnchor = lastMainAssistantRowId ?? lastToolResultRowId;
|
|
308
|
+
if (lastTurnUsage) {
|
|
309
|
+
deps.chatService.setLastUsage(thread.id, lastTurnUsage);
|
|
310
|
+
if (turnAnchor !== null) {
|
|
311
|
+
deps.chatService.attachTurnUsage(thread.id, turnAnchor, lastTurnUsage);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (typeof event.contextSize === 'number') {
|
|
315
|
+
deps.chatService.setLastContextSize(thread.id, event.contextSize);
|
|
316
|
+
if (turnAnchor !== null) {
|
|
317
|
+
deps.chatService.attachTurnContextSize(thread.id, turnAnchor, event.contextSize);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case 'todo_list_updated':
|
|
323
|
+
if (!event.isSubagent) {
|
|
324
|
+
deps.chatService.updateCurrentTodoItems(thread.id, event.items);
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
case 'warning':
|
|
328
|
+
console.warn('[agent warning]', event.message);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Terminal `done`/`error` ida tylko transportem callera (`onEvent`).
|
|
333
|
+
// Emitter dla GET /stream/:threadId dostaje `done` raz, w `finally`.
|
|
334
|
+
input.onEvent({ type: 'done' });
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
// Mapuj na typed blad — `onEvent` dostaje `event: error` (parytet z SSE),
|
|
338
|
+
// a caller (headless `ask`) lapie `AgentTurnError` i mapuje na status HTTP.
|
|
339
|
+
let turnErr;
|
|
340
|
+
if (err instanceof AdapterAbortError) {
|
|
341
|
+
turnErr = new AgentTurnError('ABORTED', 'Aborted by user');
|
|
342
|
+
}
|
|
343
|
+
else if (err instanceof AdapterTimeoutError) {
|
|
344
|
+
turnErr = new AgentTurnError('TIMEOUT', 'Agent took too long to respond');
|
|
345
|
+
}
|
|
346
|
+
else if (err instanceof AdapterInitError) {
|
|
347
|
+
turnErr = new AgentTurnError('AGENT_UNAVAILABLE', 'Claude CLI not found or not logged in. Run `claude login` first.');
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
turnErr = new AgentTurnError('AGENT_ERROR', err instanceof Error ? err.message : String(err));
|
|
351
|
+
}
|
|
352
|
+
input.onEvent({ type: 'error', code: turnErr.code, error: turnErr.message });
|
|
353
|
+
throw turnErr;
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
try {
|
|
357
|
+
flushMainBuf();
|
|
358
|
+
for (const tid of Array.from(subagentBuffers.keys()))
|
|
359
|
+
flushSubBuf(tid);
|
|
360
|
+
}
|
|
361
|
+
catch (flushErr) {
|
|
362
|
+
console.error('[chat] final flush failed', flushErr);
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
deps.chatService.finalizeStreamingRows(thread.id);
|
|
366
|
+
}
|
|
367
|
+
catch (finalizeErr) {
|
|
368
|
+
console.error('[chat] finalizeStreamingRows failed', finalizeErr);
|
|
369
|
+
}
|
|
370
|
+
emitter.emit('event', { type: 'done' });
|
|
371
|
+
activeAdapters.delete(thread.id);
|
|
372
|
+
cancelPendingForRequest(requestId);
|
|
373
|
+
}
|
|
374
|
+
return { threadId: thread.id, answer: answerBuf.trim() };
|
|
375
|
+
}
|
|
376
|
+
export function countPages(tree) {
|
|
377
|
+
let n = 0;
|
|
378
|
+
for (const node of tree) {
|
|
379
|
+
if (node.type === 'file')
|
|
380
|
+
n++;
|
|
381
|
+
else if (Array.isArray(node.children))
|
|
382
|
+
n += countPages(node.children);
|
|
383
|
+
}
|
|
384
|
+
return n;
|
|
385
|
+
}
|
|
386
|
+
//# sourceMappingURL=agent-turn.js.map
|