chainlesschain 0.40.3 → 0.41.0
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/README.md +3 -3
- package/package.json +1 -1
- package/src/commands/serve.js +34 -1
- package/src/lib/agent-core.js +361 -62
- package/src/lib/slot-filler.js +133 -0
- package/src/lib/ws-agent-handler.js +17 -0
- package/src/lib/ws-server.js +30 -2
- package/src/repl/agent-repl.js +57 -808
package/src/lib/slot-filler.js
CHANGED
|
@@ -446,6 +446,139 @@ Keep values concise (single words or short strings).`;
|
|
|
446
446
|
this._tableCreated = true;
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Detect intent type from a user message using keyword/regex matching.
|
|
451
|
+
*
|
|
452
|
+
* @param {string} userMessage
|
|
453
|
+
* @returns {{ type: string, entities: object } | null} Detected intent or null
|
|
454
|
+
*/
|
|
455
|
+
static detectIntent(userMessage) {
|
|
456
|
+
if (!userMessage || typeof userMessage !== "string") return null;
|
|
457
|
+
|
|
458
|
+
const msg = userMessage.toLowerCase().trim();
|
|
459
|
+
|
|
460
|
+
// Intent detection patterns — ordered by specificity
|
|
461
|
+
const patterns = [
|
|
462
|
+
{
|
|
463
|
+
type: "create_file",
|
|
464
|
+
keywords: [
|
|
465
|
+
/\bcreate\s+(a\s+)?file\b/,
|
|
466
|
+
/\bnew\s+file\b/,
|
|
467
|
+
/\bscaffold\b/,
|
|
468
|
+
/\bgenerate\s+(a\s+)?file\b/,
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
type: "deploy",
|
|
473
|
+
keywords: [/\bdeploy\b/, /\bship\s+(it|this)\b/, /\bpush\s+to\s+prod/],
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
type: "refactor",
|
|
477
|
+
keywords: [/\brefactor\b/, /\brestructure\b/, /\breorganize\b/],
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
type: "test",
|
|
481
|
+
keywords: [
|
|
482
|
+
/\bwrite\s+tests?\b/,
|
|
483
|
+
/\badd\s+tests?\b/,
|
|
484
|
+
/\btest\s+(this|it|the)\b/,
|
|
485
|
+
/\bunit\s+test\b/,
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
type: "analyze",
|
|
490
|
+
keywords: [/\banalyze\b/, /\baudit\b/, /\breview\s+(the\s+)?code\b/],
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
type: "search",
|
|
494
|
+
keywords: [
|
|
495
|
+
/\bsearch\s+for\b/,
|
|
496
|
+
/\bfind\s+(all|the|every)\b/,
|
|
497
|
+
/\bgrep\b/,
|
|
498
|
+
/\blook\s+for\b/,
|
|
499
|
+
],
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
type: "install",
|
|
503
|
+
keywords: [
|
|
504
|
+
/\binstall\b/,
|
|
505
|
+
/\badd\s+(a\s+)?package\b/,
|
|
506
|
+
/\badd\s+(a\s+)?dependency\b/,
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
type: "generate",
|
|
511
|
+
keywords: [
|
|
512
|
+
/\bgenerate\b/,
|
|
513
|
+
/\bcreate\s+(a\s+)?(component|test|api|config)\b/,
|
|
514
|
+
],
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
type: "edit_file",
|
|
518
|
+
keywords: [
|
|
519
|
+
/\bedit\s+(the\s+)?file\b/,
|
|
520
|
+
/\bmodify\s+(the\s+)?file\b/,
|
|
521
|
+
/\bchange\s+(the\s+)?file\b/,
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
|
|
526
|
+
for (const { type, keywords } of patterns) {
|
|
527
|
+
for (const re of keywords) {
|
|
528
|
+
if (re.test(msg)) {
|
|
529
|
+
// Try to extract entities from the message
|
|
530
|
+
const entities = CLISlotFiller._extractEntities(type, msg);
|
|
531
|
+
return { type, entities };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Extract entities from a matched message.
|
|
541
|
+
* @private
|
|
542
|
+
*/
|
|
543
|
+
static _extractEntities(intentType, msg) {
|
|
544
|
+
const entities = {};
|
|
545
|
+
|
|
546
|
+
// Try to extract file path references
|
|
547
|
+
const pathMatch = msg.match(
|
|
548
|
+
/(?:in|at|to|from)\s+["`']?([./\w-]+\.\w+)["`']?/,
|
|
549
|
+
);
|
|
550
|
+
if (pathMatch) {
|
|
551
|
+
if (intentType === "create_file") {
|
|
552
|
+
entities.path = pathMatch[1];
|
|
553
|
+
} else {
|
|
554
|
+
entities.target = pathMatch[1];
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Try to extract file type from extension mentions
|
|
559
|
+
const extMatch = msg.match(/\.(js|ts|py|json|md|html|css|vue|yaml|yml)\b/);
|
|
560
|
+
if (extMatch && intentType === "create_file") {
|
|
561
|
+
entities.fileType = EXT_TO_FILE_TYPE[`.${extMatch[1]}`] || extMatch[1];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Try to extract platform for deploy
|
|
565
|
+
if (intentType === "deploy") {
|
|
566
|
+
if (msg.includes("docker")) entities.platform = "docker";
|
|
567
|
+
else if (msg.includes("vercel")) entities.platform = "vercel";
|
|
568
|
+
else if (msg.includes("aws")) entities.platform = "aws";
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Try to extract package name for install
|
|
572
|
+
if (intentType === "install") {
|
|
573
|
+
const pkgMatch = msg.match(/install\s+(\S+)/);
|
|
574
|
+
if (pkgMatch && !["a", "the", "this", "it"].includes(pkgMatch[1])) {
|
|
575
|
+
entities.package = pkgMatch[1];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return entities;
|
|
580
|
+
}
|
|
581
|
+
|
|
449
582
|
/**
|
|
450
583
|
* Get slot definitions for an intent type.
|
|
451
584
|
*/
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { agentLoop, formatToolArgs } from "./agent-core.js";
|
|
10
10
|
import { detectTaskType, selectModelForTask } from "./task-model-selector.js";
|
|
11
11
|
import { PlanState } from "./plan-mode.js";
|
|
12
|
+
import { CLISlotFiller } from "./slot-filler.js";
|
|
12
13
|
|
|
13
14
|
export class WSAgentHandler {
|
|
14
15
|
/**
|
|
@@ -67,6 +68,12 @@ export class WSAgentHandler {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// Create slot filler for interactive parameter collection
|
|
72
|
+
const slotFiller = new CLISlotFiller({
|
|
73
|
+
interaction: this.interaction,
|
|
74
|
+
db: this.db,
|
|
75
|
+
});
|
|
76
|
+
|
|
70
77
|
// Run agent loop
|
|
71
78
|
const loopOptions = {
|
|
72
79
|
provider: session.provider,
|
|
@@ -76,10 +83,20 @@ export class WSAgentHandler {
|
|
|
76
83
|
contextEngine: session.contextEngine,
|
|
77
84
|
hookDb: this.db,
|
|
78
85
|
cwd: session.projectRoot,
|
|
86
|
+
slotFiller,
|
|
87
|
+
interaction: this.interaction,
|
|
79
88
|
};
|
|
80
89
|
|
|
81
90
|
for await (const event of agentLoop(session.messages, loopOptions)) {
|
|
82
91
|
switch (event.type) {
|
|
92
|
+
case "slot-filling":
|
|
93
|
+
this.interaction.emit("slot-filling", {
|
|
94
|
+
requestId,
|
|
95
|
+
slot: event.slot,
|
|
96
|
+
question: event.question,
|
|
97
|
+
});
|
|
98
|
+
break;
|
|
99
|
+
|
|
83
100
|
case "tool-executing":
|
|
84
101
|
this.interaction.emit("tool-executing", {
|
|
85
102
|
requestId,
|
package/src/lib/ws-server.js
CHANGED
|
@@ -270,7 +270,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
270
270
|
await this._handleSessionCreate(id, ws, message);
|
|
271
271
|
break;
|
|
272
272
|
case "session-resume":
|
|
273
|
-
this._handleSessionResume(id, ws, message);
|
|
273
|
+
await this._handleSessionResume(id, ws, message);
|
|
274
274
|
break;
|
|
275
275
|
case "session-message":
|
|
276
276
|
this._handleSessionMessage(id, ws, message);
|
|
@@ -557,7 +557,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
/** @private */
|
|
560
|
-
_handleSessionResume(id, ws, message) {
|
|
560
|
+
async _handleSessionResume(id, ws, message) {
|
|
561
561
|
if (!this.sessionManager) {
|
|
562
562
|
this._send(ws, {
|
|
563
563
|
id,
|
|
@@ -581,6 +581,34 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
581
581
|
return;
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
+
// Rebuild interaction adapter and handler for the resumed session
|
|
585
|
+
if (!this.sessionHandlers.has(sessionId)) {
|
|
586
|
+
try {
|
|
587
|
+
const { WebSocketInteractionAdapter } =
|
|
588
|
+
await import("./interaction-adapter.js");
|
|
589
|
+
session.interaction = new WebSocketInteractionAdapter(ws, sessionId);
|
|
590
|
+
|
|
591
|
+
let handler;
|
|
592
|
+
if (session.type === "chat") {
|
|
593
|
+
const { WSChatHandler } = await import("./ws-chat-handler.js");
|
|
594
|
+
handler = new WSChatHandler({
|
|
595
|
+
session,
|
|
596
|
+
interaction: session.interaction,
|
|
597
|
+
});
|
|
598
|
+
} else {
|
|
599
|
+
const { WSAgentHandler } = await import("./ws-agent-handler.js");
|
|
600
|
+
handler = new WSAgentHandler({
|
|
601
|
+
session,
|
|
602
|
+
interaction: session.interaction,
|
|
603
|
+
db: this.sessionManager.db,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
this.sessionHandlers.set(sessionId, handler);
|
|
607
|
+
} catch (_err) {
|
|
608
|
+
// Handler creation failed — session resumed without handler
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
584
612
|
// Filter out system messages for history
|
|
585
613
|
const history = (session.messages || []).filter((m) => m.role !== "system");
|
|
586
614
|
|