crewly 1.8.4 → 1.8.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/config/roles/_common/wiki-instructions.md +33 -0
- package/config/roles/orchestrator/prompt.md +66 -4
- package/config/roles/team-leader/prompt.md +38 -0
- package/config/skills/agent/core/wiki-query/SKILL.md +66 -0
- package/config/skills/agent/core/wiki-query/execute.sh +107 -0
- package/config/skills/orchestrator/wiki-bookkeep/SKILL.md +71 -0
- package/config/skills/orchestrator/wiki-bookkeep/execute.sh +72 -0
- package/config/skills/orchestrator/wiki-ingest/SKILL.md +63 -0
- package/config/skills/orchestrator/wiki-ingest/execute.sh +113 -0
- package/config/skills/orchestrator/wiki-process-queue/SKILL.md +71 -0
- package/config/skills/orchestrator/wiki-process-queue/execute.sh +93 -0
- package/config/skills/orchestrator/wiki-queue-add/SKILL.md +89 -0
- package/config/skills/orchestrator/wiki-queue-add/execute.sh +115 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +20 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +15 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +134 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +718 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts +23 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +43 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +65 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +142 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js +265 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.js +162 -4
- package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts +69 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js +174 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts +57 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.js +183 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +86 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +187 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts +116 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js +299 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +100 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js +212 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.js +138 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts +115 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.js +291 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts +115 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.js +261 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.js +10 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.js.map +1 -0
- package/frontend/dist/assets/{index-b279da34.js → index-cc115bb4.js} +246 -246
- package/frontend/dist/assets/{index-c07e04c0.css → index-db3f5041.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiChatSubscriberService — listens on chat-v2's `chat_message` event
|
|
3
|
+
* and fires `WikiIngestService.ingest` for every user-authored message.
|
|
4
|
+
*
|
|
5
|
+
* Phase A (rules-only routing):
|
|
6
|
+
* senderType === 'user' → ingest into the Crewly project vault.
|
|
7
|
+
* senderType === 'agent' | 'system' → skip.
|
|
8
|
+
*
|
|
9
|
+
* Phase B (per spec §4.5) will replace the project-only routing with an
|
|
10
|
+
* LLM-driven gate that picks team / project / global per source.
|
|
11
|
+
*
|
|
12
|
+
* Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
|
|
13
|
+
* or filesystem error MUST NOT block the live chat flow. Errors are
|
|
14
|
+
* logged at warn-level and swallowed.
|
|
15
|
+
*
|
|
16
|
+
* @module services/wiki/wiki-chat-subscriber.service
|
|
17
|
+
*/
|
|
18
|
+
import type { ChatV2Service } from '../chat-v2/chat-v2.service.js';
|
|
19
|
+
import { WikiIngestService } from './wiki-ingest.service.js';
|
|
20
|
+
/**
|
|
21
|
+
* Shape we consume from chat-v2's `chat_message` payload. Kept narrow so
|
|
22
|
+
* we don't take a heavy structural dep on the wire DTO; matches the
|
|
23
|
+
* fields produced by `toMessageDTO` (chat-v2.service.ts:1537).
|
|
24
|
+
*/
|
|
25
|
+
interface MinimalChatMessageDTO {
|
|
26
|
+
id: string;
|
|
27
|
+
channelId: string;
|
|
28
|
+
senderType: 'user' | 'agent' | 'system';
|
|
29
|
+
senderId: string;
|
|
30
|
+
content: string;
|
|
31
|
+
contentType: string;
|
|
32
|
+
createdAt: string | number;
|
|
33
|
+
}
|
|
34
|
+
export interface WikiChatSubscriberOptions {
|
|
35
|
+
/** chat-v2 singleton — `getChatV2Service()` at composition root. */
|
|
36
|
+
chatService: ChatV2Service;
|
|
37
|
+
/** Absolute path to the project vault. Default falls back to env / well-known location. */
|
|
38
|
+
projectVaultPath?: string;
|
|
39
|
+
/** Optional override for tests. */
|
|
40
|
+
ingestService?: WikiIngestService;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Process-singleton subscriber. Call `start()` once at server boot,
|
|
44
|
+
* `stop()` at shutdown.
|
|
45
|
+
*/
|
|
46
|
+
export declare class WikiChatSubscriberService {
|
|
47
|
+
private readonly logger;
|
|
48
|
+
private readonly chatService;
|
|
49
|
+
private readonly projectVaultPath;
|
|
50
|
+
private readonly ingestService;
|
|
51
|
+
private boundHandler;
|
|
52
|
+
private started;
|
|
53
|
+
constructor(opts: WikiChatSubscriberOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Subscribe to `chat_message`. Idempotent — calling start() twice
|
|
56
|
+
* leaves a single listener.
|
|
57
|
+
*/
|
|
58
|
+
start(): void;
|
|
59
|
+
/** Detach. Safe to call when not started. */
|
|
60
|
+
stop(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Handle a single chat message. Public for tests.
|
|
63
|
+
*/
|
|
64
|
+
handle(dto: MinimalChatMessageDTO): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Decide whether a chat-v2 message should drive a wiki write.
|
|
67
|
+
* Public for tests; logic is conservative — only ingest user-authored
|
|
68
|
+
* text messages, skip agents/system, mentions-only pings, and slash
|
|
69
|
+
* commands.
|
|
70
|
+
*/
|
|
71
|
+
isIngestable(dto: MinimalChatMessageDTO): boolean;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=wiki-chat-subscriber.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-chat-subscriber.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-chat-subscriber.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAsB,MAAM,0BAA0B,CAAC;AAEjF;;;;GAIG;AACH,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACxC,oEAAoE;IACpE,WAAW,EAAE,aAAa,CAAC;IAC3B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED;;;GAGG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,YAAY,CAAuD;IAC3E,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,yBAAyB;IAO3C;;;OAGG;IACH,KAAK,IAAI,IAAI;IAgBb,6CAA6C;IAC7C,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmDvD;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO;CAYlD"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiChatSubscriberService — listens on chat-v2's `chat_message` event
|
|
3
|
+
* and fires `WikiIngestService.ingest` for every user-authored message.
|
|
4
|
+
*
|
|
5
|
+
* Phase A (rules-only routing):
|
|
6
|
+
* senderType === 'user' → ingest into the Crewly project vault.
|
|
7
|
+
* senderType === 'agent' | 'system' → skip.
|
|
8
|
+
*
|
|
9
|
+
* Phase B (per spec §4.5) will replace the project-only routing with an
|
|
10
|
+
* LLM-driven gate that picks team / project / global per source.
|
|
11
|
+
*
|
|
12
|
+
* Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
|
|
13
|
+
* or filesystem error MUST NOT block the live chat flow. Errors are
|
|
14
|
+
* logged at warn-level and swallowed.
|
|
15
|
+
*
|
|
16
|
+
* @module services/wiki/wiki-chat-subscriber.service
|
|
17
|
+
*/
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
20
|
+
import { WikiIngestService, detectMessageShape } from './wiki-ingest.service.js';
|
|
21
|
+
/**
|
|
22
|
+
* Process-singleton subscriber. Call `start()` once at server boot,
|
|
23
|
+
* `stop()` at shutdown.
|
|
24
|
+
*/
|
|
25
|
+
export class WikiChatSubscriberService {
|
|
26
|
+
logger;
|
|
27
|
+
chatService;
|
|
28
|
+
projectVaultPath;
|
|
29
|
+
ingestService;
|
|
30
|
+
boundHandler = null;
|
|
31
|
+
started = false;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiChatSubscriber');
|
|
34
|
+
this.chatService = opts.chatService;
|
|
35
|
+
this.projectVaultPath = opts.projectVaultPath ?? resolveDefaultProjectVaultPath();
|
|
36
|
+
this.ingestService = opts.ingestService ?? WikiIngestService.getInstance();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to `chat_message`. Idempotent — calling start() twice
|
|
40
|
+
* leaves a single listener.
|
|
41
|
+
*/
|
|
42
|
+
start() {
|
|
43
|
+
if (this.started) {
|
|
44
|
+
this.logger.debug('start() called twice — ignoring');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.boundHandler = (dto) => {
|
|
48
|
+
// Fire-and-forget; any thrown error is logged + swallowed in handle().
|
|
49
|
+
void this.handle(dto);
|
|
50
|
+
};
|
|
51
|
+
this.chatService.on('chat_message', this.boundHandler);
|
|
52
|
+
this.started = true;
|
|
53
|
+
this.logger.info('WikiChatSubscriber started', {
|
|
54
|
+
vaultPath: this.projectVaultPath,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Detach. Safe to call when not started. */
|
|
58
|
+
stop() {
|
|
59
|
+
if (!this.started || !this.boundHandler)
|
|
60
|
+
return;
|
|
61
|
+
this.chatService.off('chat_message', this.boundHandler);
|
|
62
|
+
this.boundHandler = null;
|
|
63
|
+
this.started = false;
|
|
64
|
+
this.logger.info('WikiChatSubscriber stopped');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handle a single chat message. Public for tests.
|
|
68
|
+
*/
|
|
69
|
+
async handle(dto) {
|
|
70
|
+
try {
|
|
71
|
+
if (!this.isIngestable(dto)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const ingestInput = {
|
|
75
|
+
vaultPath: this.projectVaultPath,
|
|
76
|
+
sourceType: 'user_chat',
|
|
77
|
+
sourceRef: `chat:${dto.channelId}:${dto.id}`,
|
|
78
|
+
sourceBody: dto.content,
|
|
79
|
+
callerSession: `user/${dto.senderId}`,
|
|
80
|
+
};
|
|
81
|
+
// Phase A rules-based promotion: decision-shaped chats land in
|
|
82
|
+
// BOTH log.md (audit) and llm-curated/decisions/<date>-<slug>.md
|
|
83
|
+
// (canonical). Phase B will replace `detectMessageShape` with an
|
|
84
|
+
// LLM gate per v2.1 §4.
|
|
85
|
+
const shape = detectMessageShape(dto.content);
|
|
86
|
+
if (shape === 'decision') {
|
|
87
|
+
const result = await this.ingestService.ingestDecision(ingestInput);
|
|
88
|
+
if (!result.log.ok) {
|
|
89
|
+
this.logger.warn('WikiChatSubscriber decision-log ingest refused', {
|
|
90
|
+
reason: result.log.reason,
|
|
91
|
+
messageId: dto.id,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (!result.decision.ok) {
|
|
95
|
+
this.logger.warn('WikiChatSubscriber decisions/ promotion refused', {
|
|
96
|
+
reason: result.decision.reason,
|
|
97
|
+
messageId: dto.id,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const outcome = await this.ingestService.ingest(ingestInput);
|
|
103
|
+
if (!outcome.ok) {
|
|
104
|
+
this.logger.warn('WikiChatSubscriber ingest refused', {
|
|
105
|
+
reason: outcome.reason,
|
|
106
|
+
messageId: dto.id,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// Swallow — chat flow must not block on wiki write failures.
|
|
112
|
+
this.logger.warn('WikiChatSubscriber.handle threw (swallowed)', {
|
|
113
|
+
messageId: dto.id,
|
|
114
|
+
error: err.message,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Decide whether a chat-v2 message should drive a wiki write.
|
|
120
|
+
* Public for tests; logic is conservative — only ingest user-authored
|
|
121
|
+
* text messages, skip agents/system, mentions-only pings, and slash
|
|
122
|
+
* commands.
|
|
123
|
+
*/
|
|
124
|
+
isIngestable(dto) {
|
|
125
|
+
if (dto.senderType !== 'user')
|
|
126
|
+
return false;
|
|
127
|
+
if (!dto.content || dto.content.trim().length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
// Skip slash commands — they're orc-controls, not knowledge.
|
|
130
|
+
if (dto.content.trimStart().startsWith('/'))
|
|
131
|
+
return false;
|
|
132
|
+
// chat-v2 defaults `contentType` to 'markdown' when sendMessage doesn't
|
|
133
|
+
// supply one (chat-v2.service.ts ~line 1240). Accept the two readable
|
|
134
|
+
// text-bearing types; reject binary/system flavors like 'system_note'.
|
|
135
|
+
const acceptable = new Set(['text', 'markdown']);
|
|
136
|
+
if (dto.contentType && !acceptable.has(dto.contentType))
|
|
137
|
+
return false;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve a sensible default vault path:
|
|
143
|
+
* 1. `CREWLY_PROJECT_VAULT_PATH` env var (explicit override)
|
|
144
|
+
* 2. `<cwd>/.crewly/wiki` if it exists
|
|
145
|
+
* 3. The Crewly repo's own project vault (the only one with SCHEMA.md today)
|
|
146
|
+
*/
|
|
147
|
+
function resolveDefaultProjectVaultPath() {
|
|
148
|
+
const fromEnv = process.env['CREWLY_PROJECT_VAULT_PATH'];
|
|
149
|
+
if (fromEnv && path.isAbsolute(fromEnv)) {
|
|
150
|
+
return fromEnv;
|
|
151
|
+
}
|
|
152
|
+
return path.join('/Users/yellowsunhy/Desktop/projects/crewly-projects/crewly', '.crewly/wiki');
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=wiki-chat-subscriber.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-chat-subscriber.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-chat-subscriber.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiIngestService — write canonical pages into a vault's `llm-curated/`.
|
|
3
|
+
*
|
|
4
|
+
* Phase A scope per v2.1 spec: append entries to `llm-curated/log.md` for
|
|
5
|
+
* every chat/spec/learning source. Decisions/pattern pages (separate files
|
|
6
|
+
* under `llm-curated/decisions/`) are gated behind the LLM routing layer
|
|
7
|
+
* which lands in Phase B.
|
|
8
|
+
*
|
|
9
|
+
* Frozen-path contract (§2): refuses ANY write whose target lives under a
|
|
10
|
+
* `hardcoded:` folder. SchemaLoaderService.isFrozenPath() is the gate.
|
|
11
|
+
*
|
|
12
|
+
* @module services/wiki/wiki-ingest.service
|
|
13
|
+
*/
|
|
14
|
+
import { SchemaLoaderService } from './schema-loader.service.js';
|
|
15
|
+
/**
|
|
16
|
+
* Categories of sources that trigger ingest. Mirrors §4 (ingest trigger
|
|
17
|
+
* taxonomy) plus `user_chat` for the demo path.
|
|
18
|
+
*/
|
|
19
|
+
export type WikiSourceType = 'user_chat' | 'slack_message' | 'spec_file' | 'pr_merge' | 'record_learning' | 'task_verified';
|
|
20
|
+
export interface WikiIngestInput {
|
|
21
|
+
/** Absolute path to the vault root (containing SCHEMA.md). */
|
|
22
|
+
vaultPath: string;
|
|
23
|
+
/** Where the content came from. */
|
|
24
|
+
sourceType: WikiSourceType;
|
|
25
|
+
/** Stable reference for audit (URL, file path, WI id, chat msg id). */
|
|
26
|
+
sourceRef: string;
|
|
27
|
+
/** Body content to ingest. Empty bodies are rejected. */
|
|
28
|
+
sourceBody: string;
|
|
29
|
+
/** Session/user that authored the source — appears in the log header. */
|
|
30
|
+
callerSession?: string;
|
|
31
|
+
/** Optional override of the relative target path; defaults to `llm-curated/log.md`. */
|
|
32
|
+
targetRelativePath?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface WikiIngestResult {
|
|
35
|
+
ok: true;
|
|
36
|
+
pagesWritten: string[];
|
|
37
|
+
logEntry: string;
|
|
38
|
+
frozenPathsTouched: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface WikiIngestRefusedFrozen {
|
|
41
|
+
ok: false;
|
|
42
|
+
reason: 'frozen_path';
|
|
43
|
+
attemptedPath: string;
|
|
44
|
+
frozenFolders: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface WikiIngestRefusedInvalid {
|
|
47
|
+
ok: false;
|
|
48
|
+
reason: 'invalid_input' | 'schema_missing' | 'empty_body';
|
|
49
|
+
message: string;
|
|
50
|
+
}
|
|
51
|
+
export type WikiIngestOutcome = WikiIngestResult | WikiIngestRefusedFrozen | WikiIngestRefusedInvalid;
|
|
52
|
+
/**
|
|
53
|
+
* Writes ingest pages to a vault. Construct one per process; stateless.
|
|
54
|
+
*/
|
|
55
|
+
export declare class WikiIngestService {
|
|
56
|
+
private static instance;
|
|
57
|
+
private readonly logger;
|
|
58
|
+
private readonly schemaLoader;
|
|
59
|
+
constructor(schemaLoader?: SchemaLoaderService);
|
|
60
|
+
static getInstance(): WikiIngestService;
|
|
61
|
+
/** Test-only reset. */
|
|
62
|
+
static _resetForTesting(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Ingest a single source into the vault.
|
|
65
|
+
*
|
|
66
|
+
* Errors are returned as structured outcomes — this method never throws
|
|
67
|
+
* on legitimate refusals (frozen path, missing schema, empty body) so
|
|
68
|
+
* callers can fold the result into their normal control flow.
|
|
69
|
+
*/
|
|
70
|
+
ingest(input: WikiIngestInput): Promise<WikiIngestOutcome>;
|
|
71
|
+
private validateInput;
|
|
72
|
+
/**
|
|
73
|
+
* Build the markdown entry. Format is chosen to be safely append-only
|
|
74
|
+
* (no preceding section to rewrite) and grep-friendly:
|
|
75
|
+
*
|
|
76
|
+
* ## [<ISO>] <sourceType> | <callerSession or sourceRef>
|
|
77
|
+
*
|
|
78
|
+
* ref: <sourceRef>
|
|
79
|
+
*
|
|
80
|
+
* <body>
|
|
81
|
+
*/
|
|
82
|
+
private formatLogEntry;
|
|
83
|
+
private appendOrCreate;
|
|
84
|
+
/**
|
|
85
|
+
* Pick the right header for a freshly-created page:
|
|
86
|
+
* - `log.md` → audit-log preamble (append-only)
|
|
87
|
+
* - `decisions/*.md` → decision-page title + provenance block
|
|
88
|
+
* - anything else → minimal title block from the source ref
|
|
89
|
+
*/
|
|
90
|
+
private buildPageHeader;
|
|
91
|
+
/**
|
|
92
|
+
* Defuse markers a future skill might mis-route on: `[CHAT]`, `[NOTIFY]`,
|
|
93
|
+
* `[EVENT]`, `[ESCALATION]` get a zero-width space inserted so they're
|
|
94
|
+
* still human-readable but don't trigger regex matchers downstream.
|
|
95
|
+
* (Mirrors the escalation-router sanitizer from PR #606.)
|
|
96
|
+
*/
|
|
97
|
+
private sanitizeBody;
|
|
98
|
+
private sanitizeOneLine;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=wiki-ingest.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-ingest.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-ingest.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,iBAAiB,GACjB,eAAe,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,UAAU,EAAE,cAAc,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,IAAI,CAAC;IACT,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,eAAe,GAAG,gBAAgB,GAAG,YAAY,CAAC;IAC1D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,GAChB,uBAAuB,GACvB,wBAAwB,CAAC;AAc7B;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;gBAEvC,YAAY,CAAC,EAAE,mBAAmB;IAK9C,MAAM,CAAC,WAAW,IAAI,iBAAiB;IAOvC,uBAAuB;IACvB,MAAM,CAAC,gBAAgB,IAAI,IAAI;IAI/B;;;;;;OAMG;IACG,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAqDhE,OAAO,CAAC,aAAa;IAgCrB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;YAgBR,cAAc;IAa5B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAuBvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;CAKxB"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiIngestService — write canonical pages into a vault's `llm-curated/`.
|
|
3
|
+
*
|
|
4
|
+
* Phase A scope per v2.1 spec: append entries to `llm-curated/log.md` for
|
|
5
|
+
* every chat/spec/learning source. Decisions/pattern pages (separate files
|
|
6
|
+
* under `llm-curated/decisions/`) are gated behind the LLM routing layer
|
|
7
|
+
* which lands in Phase B.
|
|
8
|
+
*
|
|
9
|
+
* Frozen-path contract (§2): refuses ANY write whose target lives under a
|
|
10
|
+
* `hardcoded:` folder. SchemaLoaderService.isFrozenPath() is the gate.
|
|
11
|
+
*
|
|
12
|
+
* @module services/wiki/wiki-ingest.service
|
|
13
|
+
*/
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import * as fs from 'fs/promises';
|
|
16
|
+
import { existsSync } from 'fs';
|
|
17
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
18
|
+
import { SchemaLoaderService } from './schema-loader.service.js';
|
|
19
|
+
const DEFAULT_LOG_RELATIVE_PATH = 'llm-curated/log.md';
|
|
20
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
21
|
+
// Spec note (Steve, 2026-05-22): the earlier keyword-based
|
|
22
|
+
// `detectMessageShape` heuristic + `buildDecisionSlug` + `ingestDecision`
|
|
23
|
+
// dual-write were REMOVED. Routing into `llm-curated/<folder>/<page>.md`
|
|
24
|
+
// is now agent-driven via `wiki-process-queue` + the agent's own LLM gate
|
|
25
|
+
// — see WikiQueueService for the queue + the orchestrator system prompt
|
|
26
|
+
// for the rule that says "queue worth-saving content as you see it." This
|
|
27
|
+
// service stays low-level: it only writes the path the caller (skill,
|
|
28
|
+
// route, queue processor) specifies, after the frozen-path gate.
|
|
29
|
+
/**
|
|
30
|
+
* Writes ingest pages to a vault. Construct one per process; stateless.
|
|
31
|
+
*/
|
|
32
|
+
export class WikiIngestService {
|
|
33
|
+
static instance = null;
|
|
34
|
+
logger;
|
|
35
|
+
schemaLoader;
|
|
36
|
+
constructor(schemaLoader) {
|
|
37
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiIngest');
|
|
38
|
+
this.schemaLoader = schemaLoader ?? new SchemaLoaderService();
|
|
39
|
+
}
|
|
40
|
+
static getInstance() {
|
|
41
|
+
if (!this.instance) {
|
|
42
|
+
this.instance = new WikiIngestService();
|
|
43
|
+
}
|
|
44
|
+
return this.instance;
|
|
45
|
+
}
|
|
46
|
+
/** Test-only reset. */
|
|
47
|
+
static _resetForTesting() {
|
|
48
|
+
this.instance = null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Ingest a single source into the vault.
|
|
52
|
+
*
|
|
53
|
+
* Errors are returned as structured outcomes — this method never throws
|
|
54
|
+
* on legitimate refusals (frozen path, missing schema, empty body) so
|
|
55
|
+
* callers can fold the result into their normal control flow.
|
|
56
|
+
*/
|
|
57
|
+
async ingest(input) {
|
|
58
|
+
const validation = this.validateInput(input);
|
|
59
|
+
if (validation) {
|
|
60
|
+
return validation;
|
|
61
|
+
}
|
|
62
|
+
let schema;
|
|
63
|
+
try {
|
|
64
|
+
schema = await this.schemaLoader.load(input.vaultPath);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
reason: 'schema_missing',
|
|
70
|
+
message: err.message,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const target = input.targetRelativePath ?? DEFAULT_LOG_RELATIVE_PATH;
|
|
74
|
+
if (this.schemaLoader.isFrozenPath(schema, target)) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
reason: 'frozen_path',
|
|
78
|
+
attemptedPath: target,
|
|
79
|
+
frozenFolders: this.schemaLoader.getFrozenPaths(schema),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const absoluteTarget = path.join(input.vaultPath, target);
|
|
83
|
+
await fs.mkdir(path.dirname(absoluteTarget), { recursive: true });
|
|
84
|
+
const logEntry = this.formatLogEntry(input);
|
|
85
|
+
await this.appendOrCreate(absoluteTarget, logEntry, input);
|
|
86
|
+
this.logger.info('WikiIngest wrote log entry', {
|
|
87
|
+
vault: input.vaultPath,
|
|
88
|
+
target,
|
|
89
|
+
sourceType: input.sourceType,
|
|
90
|
+
sourceRef: input.sourceRef,
|
|
91
|
+
bodyBytes: input.sourceBody.length,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
pagesWritten: [target],
|
|
96
|
+
logEntry,
|
|
97
|
+
frozenPathsTouched: [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Internals
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
validateInput(input) {
|
|
104
|
+
if (!input.vaultPath || !path.isAbsolute(input.vaultPath)) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
reason: 'invalid_input',
|
|
108
|
+
message: `vaultPath must be an absolute path, got "${input.vaultPath ?? ''}"`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (!input.sourceType) {
|
|
112
|
+
return { ok: false, reason: 'invalid_input', message: 'sourceType is required' };
|
|
113
|
+
}
|
|
114
|
+
if (!input.sourceRef) {
|
|
115
|
+
return { ok: false, reason: 'invalid_input', message: 'sourceRef is required' };
|
|
116
|
+
}
|
|
117
|
+
const body = input.sourceBody ?? '';
|
|
118
|
+
if (body.trim().length === 0) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
reason: 'empty_body',
|
|
122
|
+
message: 'sourceBody is empty after trimming whitespace',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (Buffer.byteLength(body, 'utf8') > MAX_BODY_BYTES) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
reason: 'invalid_input',
|
|
129
|
+
message: `sourceBody exceeds ${MAX_BODY_BYTES} bytes`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Build the markdown entry. Format is chosen to be safely append-only
|
|
136
|
+
* (no preceding section to rewrite) and grep-friendly:
|
|
137
|
+
*
|
|
138
|
+
* ## [<ISO>] <sourceType> | <callerSession or sourceRef>
|
|
139
|
+
*
|
|
140
|
+
* ref: <sourceRef>
|
|
141
|
+
*
|
|
142
|
+
* <body>
|
|
143
|
+
*/
|
|
144
|
+
formatLogEntry(input) {
|
|
145
|
+
const ts = new Date().toISOString();
|
|
146
|
+
const header = this.sanitizeOneLine(input.callerSession ?? input.sourceRef, 80);
|
|
147
|
+
const body = this.sanitizeBody(input.sourceBody);
|
|
148
|
+
const ref = this.sanitizeOneLine(input.sourceRef, 200);
|
|
149
|
+
return [
|
|
150
|
+
'',
|
|
151
|
+
`## [${ts}] ${input.sourceType} | ${header}`,
|
|
152
|
+
'',
|
|
153
|
+
`ref: ${ref}`,
|
|
154
|
+
'',
|
|
155
|
+
body,
|
|
156
|
+
'',
|
|
157
|
+
].join('\n');
|
|
158
|
+
}
|
|
159
|
+
async appendOrCreate(absolutePath, entry, input) {
|
|
160
|
+
if (!existsSync(absolutePath)) {
|
|
161
|
+
const header = this.buildPageHeader(absolutePath, input);
|
|
162
|
+
await fs.writeFile(absolutePath, header + entry, 'utf8');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await fs.appendFile(absolutePath, entry, 'utf8');
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Pick the right header for a freshly-created page:
|
|
169
|
+
* - `log.md` → audit-log preamble (append-only)
|
|
170
|
+
* - `decisions/*.md` → decision-page title + provenance block
|
|
171
|
+
* - anything else → minimal title block from the source ref
|
|
172
|
+
*/
|
|
173
|
+
buildPageHeader(absolutePath, input) {
|
|
174
|
+
const basename = absolutePath.split(/[/\\]/).pop() ?? '';
|
|
175
|
+
if (basename === 'log.md') {
|
|
176
|
+
return '# Activity log\n\nAppend-only log of ingested sources. Each entry: `## [<ISO>] <sourceType> | <caller>`.\n';
|
|
177
|
+
}
|
|
178
|
+
if (absolutePath.includes('/decisions/')) {
|
|
179
|
+
const title = this.sanitizeOneLine(input.sourceBody, 80);
|
|
180
|
+
const caller = input.callerSession ?? input.sourceRef;
|
|
181
|
+
return [
|
|
182
|
+
`# ${title}`,
|
|
183
|
+
'',
|
|
184
|
+
`> **source:** \`${this.sanitizeOneLine(input.sourceRef, 200)}\` `,
|
|
185
|
+
`> **caller:** \`${this.sanitizeOneLine(caller, 80)}\` `,
|
|
186
|
+
`> **recorded:** ${new Date().toISOString()}`,
|
|
187
|
+
'',
|
|
188
|
+
'---',
|
|
189
|
+
'',
|
|
190
|
+
].join('\n');
|
|
191
|
+
}
|
|
192
|
+
// Generic non-log target: minimal title block.
|
|
193
|
+
return `# ${this.sanitizeOneLine(input.sourceRef, 80)}\n\n`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Defuse markers a future skill might mis-route on: `[CHAT]`, `[NOTIFY]`,
|
|
197
|
+
* `[EVENT]`, `[ESCALATION]` get a zero-width space inserted so they're
|
|
198
|
+
* still human-readable but don't trigger regex matchers downstream.
|
|
199
|
+
* (Mirrors the escalation-router sanitizer from PR #606.)
|
|
200
|
+
*/
|
|
201
|
+
sanitizeBody(body) {
|
|
202
|
+
const trimmed = body.replace(/\r\n/g, '\n').trim();
|
|
203
|
+
return trimmed.replace(/\[(CHAT|NOTIFY|EVENT|ESCALATION)\]/g, (_match, tag) => `[${tag}]`);
|
|
204
|
+
}
|
|
205
|
+
sanitizeOneLine(value, maxLen) {
|
|
206
|
+
const flat = value.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
207
|
+
if (flat.length <= maxLen)
|
|
208
|
+
return flat;
|
|
209
|
+
return flat.slice(0, maxLen - 1) + '…';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=wiki-ingest.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-ingest.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-ingest.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiProcessService — bundles "claim a pending item + build the
|
|
3
|
+
* system-context the agent's LLM needs to classify it."
|
|
4
|
+
*
|
|
5
|
+
* Per Steve's 2026-05-22 redesign:
|
|
6
|
+
* - the skill DOES NOT pick a target folder. The agent picks.
|
|
7
|
+
* - there is NO preset taxonomy beyond the frozen folders (sop/,
|
|
8
|
+
* team-norm/, memory/, sop-overrides/). Everything under
|
|
9
|
+
* llm-curated/* is fair game; the agent invents folder names.
|
|
10
|
+
* - the agent reads the queue item's `reason` (which we forced it to
|
|
11
|
+
* write at queue-add time) PLUS the existing vault state (so it
|
|
12
|
+
* doesn't re-classify into a duplicate of an existing page).
|
|
13
|
+
*
|
|
14
|
+
* Lifecycle this service participates in:
|
|
15
|
+
* pending → (this service) → claimed + context payload returned
|
|
16
|
+
* → (agent's LLM, outside the skill) picks target path
|
|
17
|
+
* → (agent calls wiki-ingest) → page written
|
|
18
|
+
* → (agent calls queue/:id/process) → status=processed
|
|
19
|
+
*
|
|
20
|
+
* @module services/wiki/wiki-process.service
|
|
21
|
+
*/
|
|
22
|
+
import { WikiQueueService, WikiQueueItem } from './wiki-queue.service.js';
|
|
23
|
+
import { WikiQueryService, WikiQuerySystemContext } from './wiki-query.service.js';
|
|
24
|
+
export interface WikiProcessClaimInput {
|
|
25
|
+
/** Agent session claiming the item. */
|
|
26
|
+
claimedBy: string;
|
|
27
|
+
/** Filter the queue to a specific vault (default: any). */
|
|
28
|
+
vaultPath?: string;
|
|
29
|
+
/** Skip this many leading pending items. Lets the agent re-roll after a skip. */
|
|
30
|
+
offset?: number;
|
|
31
|
+
/** For the vault context: how many candidate pages to include. */
|
|
32
|
+
topK?: number;
|
|
33
|
+
/** For the vault context: how many recent log entries. */
|
|
34
|
+
recentLogEntries?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface WikiProcessClaimResult {
|
|
37
|
+
/** The claimed queue item. */
|
|
38
|
+
item: WikiQueueItem;
|
|
39
|
+
/**
|
|
40
|
+
* System-context the agent's LLM should classify against. Includes the
|
|
41
|
+
* vault SCHEMA, frozen-path list, recent log, and top-K candidate pages
|
|
42
|
+
* by keyword overlap with the queue item's content.
|
|
43
|
+
*
|
|
44
|
+
* `null` if the vault has no SCHEMA.md (rare — vault wasn't initialized).
|
|
45
|
+
*/
|
|
46
|
+
vaultContext: WikiQuerySystemContext | null;
|
|
47
|
+
/** Synthesis instructions for the agent's LLM. */
|
|
48
|
+
classifierNotes: string[];
|
|
49
|
+
}
|
|
50
|
+
export type WikiProcessClaimOutcome = {
|
|
51
|
+
ok: true;
|
|
52
|
+
result: WikiProcessClaimResult;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
reason: 'no_pending_items' | 'vault_query_failed';
|
|
56
|
+
message: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Stateless façade — both backing services (queue, query) are singletons.
|
|
60
|
+
*/
|
|
61
|
+
export declare class WikiProcessService {
|
|
62
|
+
private readonly queue;
|
|
63
|
+
private readonly query;
|
|
64
|
+
private static instance;
|
|
65
|
+
private readonly logger;
|
|
66
|
+
constructor(queue?: WikiQueueService, query?: WikiQueryService);
|
|
67
|
+
static getInstance(): WikiProcessService;
|
|
68
|
+
/** Test-only reset. */
|
|
69
|
+
static _resetForTesting(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Claim the next pending item + return the vault context the agent
|
|
72
|
+
* needs to classify it. The caller (agent runtime) then runs an LLM
|
|
73
|
+
* call against this context + the item's content/reason, picks a
|
|
74
|
+
* target page, and calls `wiki-ingest` + `/queue/:id/process` to
|
|
75
|
+
* commit.
|
|
76
|
+
*/
|
|
77
|
+
claimNext(input: WikiProcessClaimInput): Promise<WikiProcessClaimOutcome>;
|
|
78
|
+
/**
|
|
79
|
+
* The contract the agent's LLM must honor when classifying. Kept
|
|
80
|
+
* short so it can be inlined into the runtime's task-instruction.
|
|
81
|
+
*/
|
|
82
|
+
private classifierNotes;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=wiki-process.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-process.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-process.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;;OAMG;IACH,YAAY,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC5C,kDAAkD;IAClD,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAA;CAAE,GAC5C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,kBAAkB,GAAG,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF;;GAEG;AACH,qBAAa,kBAAkB;IAK3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAGtB,KAAK,GAAE,gBAAiD,EACxD,KAAK,GAAE,gBAAiD;IAK3E,MAAM,CAAC,WAAW,IAAI,kBAAkB;IAKxC,uBAAuB;IACvB,MAAM,CAAC,gBAAgB,IAAI,IAAI;IAI/B;;;;;;OAMG;IACG,SAAS,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA2E/E;;;OAGG;IACH,OAAO,CAAC,eAAe;CAUxB"}
|